Pull the Render Apart
What AOVs are, how a CG beauty splits into passes and adds back up, and the habits that keep a multi-pass composite fast, exact and easy to change when the notes come in.
Compositing · Render passes · 17 min
When a 3D renderer finishes a frame, the picture you see is only one of the things it can save. Along the way it worked out how much light hit each surface directly, how much bounced in from other surfaces, where the shiny highlights fell, what colour each material was, how far away every point sat and which way it faced. An **AOV**, short for arbitrary output variable, is any of those in-between results written out as an image of its own. Compositors also call them render passes, render elements or render layers, depending on the software. With the right AOVs, a compositor can change the brightness of one light, the colour of one object or the amount of motion blur without sending the shot back to be rendered again. This post walks through the main passes, shows how they rebuild the final image, and finishes with the best practices that keep a multi-pass composite honest. Every figure is drawn from one small room that is ray traced in your browser, and its AOVs are real: they really do add back up to the picture.
The renderer's working notes
In practice the AOVs of one frame usually travel together in a single **multi-channel OpenEXR** file. EXR can store any number of named channels, so the beauty sits in the familiar R, G, B and A, and every pass sits next to it as its own layer, such as diffuse_direct.R or P.X. In Nuke, one Read node brings in the whole file and a Shuffle node pulls out the layer you want. Point at the picture above and look at the numbers. The same pixel holds a colour in the lighting layers, a distance in depth, a direction in the normals and a 3D coordinate in position. They are all just numbers in channels. What changes is what the numbers mean, and so what you are allowed to do with them.
Passes come in two families, and keeping them apart is the first best practice. **Lighting passes** (also called beauty or shading passes) are slices of the light in the picture: diffuse light that arrives straight from a lamp, indirect light that bounced off another surface first, specular highlights and reflections, emission from things that glow, and in real productions subsurface scattering, transmission, coat and sheen as well. Put them back together with a plus and you get the beauty. **Data passes** (also called utility or technical passes) are not light at all. Albedo is the surface colour, depth is distance, normals are directions, position is a coordinate, motion vectors are a pixel offset and an ID pass says which object is which. You never add these into the picture. You use them to make decisions about it.
Every renderer has its own names for the same ideas, and the table above is a phrasebook for four popular ones. The row that matters most is the highlighted one. In Arnold, V-Ray and Redshift the main diffuse passes already include the surface colour, so you simply add them up. In Blender's Cycles the direct and indirect diffuse passes are pure light, and you multiply them by the Diffuse Color pass before adding. The Blender manual spells the recipe out: each light type is colour times the sum of direct and indirect. Get this wrong and the rebuild comes out grey or doubly saturated. So before you build anything, read your renderer's documentation for the exact formula, and write it on a sticky note on the script.
Add it back up, then prove it
The first thing to do with lighting passes is to put them back together and check that nothing is missing. In Nuke that is a Shuffle for each pass and a chain of Merge nodes set to **plus**, all in linear light, before any grading. Then subtract the result from the original beauty with a Merge set to difference, or just flip between the two. A perfect rebuild is black everywhere. If you see a glow in the difference, a pass is missing: often transmission, subsurface, a coat layer or the environment light. If you see shapes along the edges, the renderer filtered some passes differently from the beauty. Only once the difference is black do you know the passes are the whole picture, and only then is it safe to grade them. Switch off a pass above and watch exactly that light turn up in the difference.
Albedo is the most useful data pass for grading light. Diffuse light is the surface colour multiplied by the light that reached it, so dividing a coloured diffuse pass by albedo leaves the **raw light** on its own. Now the two can be changed separately. Grade the raw light and every material reacts to the new light correctly, the blue painting and the orange ball alike. Change the albedo and an object takes a new colour while keeping its shadows and shading, which is a quick way to answer "can we see the crate in red?" without a re-render. Two cautions. Where albedo is zero or nearly zero the division blows up, so clamp or guard it, and leave the pixels alone where albedo is black. And the multiply-back has to use exactly the same albedo you divided by, or edges and textures pick up halos.
Once the rebuild is exact, every pass is a handle. Turn down the indirect pass and the room gets moodier and more contrasty. Push the specular pass and the ball and crate look glossier. Brighten emission and the panel on the back wall burns hotter. The bars beside the picture are the statistic worth keeping in your head: in this room direct diffuse light carries most of the frame, so small changes there move the whole image, while the specular pass is a small slice of the energy that the eye still notices first. Grade passes with gain, in stops, rather than with lift or gamma, because a multiply keeps the passes adding up the way light really adds. Keep each grade small, too. A pass pushed many stops away from where lighting left it usually means the shot needs a lighting note, not a comp fix.
Lighting passes can also be split a second way: by light instead of by type. A **light group** (Arnold calls it an AOV light group, V-Ray a Light Select element, Redshift and Blender a Light Group) is a beauty made by one light or one set of lights only. Add all the groups together and you are back at the beauty. Our room has a warm key from the right, a cool fill from the left and a sky above, and you can now balance them like dimmers on a lighting desk. This is the pass that saves the most re-renders, because the commonest note on a CG shot is "a touch less fill" or "make the key warmer". Renderers that support light path expressions, such as Arnold, RenderMan and Houdini's Karma, let you go further and combine the two splits, for example the specular from the key light alone, but every extra pass is more disk, so ask for groups where the notes are likely to land.
Masks, fog, blur and light from numbers
The **depth pass**, Z, stores how far each pixel is from the camera. Its two classic uses are atmosphere and depth of field. Fog, haze and aerial perspective are a mix towards a fog colour that grows with distance, as in the figure, and Nuke's ZDefocus node uses the same pass to blur each pixel by its distance from a focal plane. Look at the histogram: most of this room's pixels sit between about three and nine metres away, so that is where the fog controls do their work. Two best practices go with Z. First, know your convention. Some renderers write true distance and some write a normalised or inverted depth, so check the values with the viewer before trusting them. Second, depth has no meaningful in-between value at an edge, which is a problem we come back to in Part 4.
The **normals pass**, N, stores which way each surface faces, as a vector of three numbers. Take the dot product of that vector with a light direction and you get how much each pixel faces that light, which is exactly the shape of simple diffuse lighting. Multiply by albedo and a colour, add it on top, and you have a new light that never existed in the render. Nuke's Relight node does this with a 3D light, and the **position pass**, P, which stores each pixel's place in the 3D scene, lets that light fall off with distance and lets you draw 3D masks that stick to surfaces. Our earlier post *Pixels Are Vectors* walks through the maths. The catch is in the caption: there is no shadow and no bounce, because the pass only knows about the pixel it sits on, so treat this as a rim or a kiss of light, not a new key.
Mattes for single objects used to come from **ID passes**: the renderer painted each object a flat red, green or blue, and the compositor keyed one colour. It works until you look closely. An ID pass can hold only one object per pixel, so at every edge the matte is all or nothing, and a grade through it leaves a jagged line of untouched pixels. It also runs out of colours fast. **Cryptomatte**, published in 2015 by Jonah Friedman and Andy Jones at Psyop and now supported in Nuke, Fusion, Blender and other tools, solves both problems. It stores, for every pixel, a list of object IDs with how much of the pixel each one covers, so the matte is soft at edges, works through motion blur and transparency, and lets you pick any object in the scene with a click. Zoom on an edge above and switch between the two. Ask for Cryptomatte by object, by material and by asset as standard, and it will remove most roto requests for CG.
**Motion vectors** store, for every pixel, how far and in which direction it moved on screen between frames, usually as two channels for x and y. A node such as Nuke's VectorBlur smears each pixel along its vector to fake motion blur in comp. That is cheaper to render and lets you change the shutter later, but compare the three versions above. Vector blur only has the pixels the camera saw, so it cannot reveal what was behind a moving object: smear the ball over the finished beauty and the sharp ball is still there under the blur. The best practice follows from that. Render fast-moving objects as their own layer over a separate background, and check with your renderer whether its vectors are measured forwards, backwards or centred on the frame and in pixels or in normalised units, because the blur node has to be told. For very fast or spinning motion, or anything seen through transparency, real 3D motion blur is still the safer choice.
Precision, size, edges and order
EXR can store each channel as a 16-bit **half float** or a 32-bit **full float**, and choosing per pass is one of the easiest wins in a pipeline. Half float keeps about three significant digits at any brightness, which is plenty for light, so lighting passes and albedo can be half and take half the space. Data passes are different, because their values are measurements. Within a metre of the origin a half-float position is accurate to about a millimetre, but 512 metres away its steps are half a metre wide, and a P mask built on it breaks into blocks, as the figure shows. Depth has the same problem far from the camera, and Cryptomatte IDs are bit patterns that must be stored exactly. So: **lighting in half, P, Z and IDs in full float**, and keep CG scenes near the origin where you can.
Every pass costs disk, network and read time on every frame, for every artist who opens the shot, so ask for what the shot needs rather than everything the renderer offers. The calculator shows how fast it adds up: a typical set of lighting passes, light groups and data passes at UHD runs past a gigabyte a frame before compression. Compression helps a lot, and the choice of codec matters as much as the choice of passes. Lossless codecs such as ZIP and PIZ are the safe default for data passes, where every bit counts. Lossy DWAA and DWAB give much smaller files and are often fine for lighting passes and plates. Agree a standard AOV list for the show, add extras per shot when a note calls for them, and remove passes nobody has opened by the time the shot is final.
Edges are where data passes go wrong. A renderer antialiases by tracing many samples per pixel and filtering them into one value. For colour, averaging is correct: a pixel that is half ball and half wall should be half of each colour. For depth, position or normals it is nonsense. Average a ball four and a half metres away with the floor three metres behind it and you get a depth where nothing exists, and the fog, the defocus or the 3D mask then puts a halo exactly there. That is why renderers offer a **closest** filter for data AOVs (Arnold, for example, has a closest_filter for exactly these passes), and why you should never unpremultiply, grade or blur a data pass as if it were colour. Lighting passes, on the other hand, are premultiplied images like the beauty: grade them in linear light, keep them premultiplied when you add them, and unpremultiply only for operations that need it.
Finally, order. A tidy AOV script reads top to bottom in the same order every time, so anyone on the team can open it and find the light they were asked to change. Read the EXR once, Shuffle every lighting pass out in a row, grade each in its own column, add them back with plus merges, and keep the difference check against the original beauty in the script, switched off but ready, so you can prove the rebuild after every new render. Branch the data passes off to the side: Cryptomatte into the mask inputs, Z into fog and ZDefocus, and motion vectors into VectorBlur late in the chain, after the layers are combined. Name nodes after what they do, put each group in a labelled backdrop, and keep the renderer's own layer names so a new version of the EXR drops straight in. Do that, and a note that would have meant a re-render becomes a two-minute change.
Sources and further reading
- Blender Manual, "Render Passes" (Cycles light passes and how they combine): docs.blender.org
- Autodesk, Arnold User Guide, "AOVs" and "Light Path Expressions": help.autodesk.com
- Chaos, V-Ray documentation, "Render Elements": docs.chaos.com
- Maxon, Redshift documentation, "AOVs": help.maxon.net
- Foundry, Nuke Reference Guide: Shuffle, Merge, ZDefocus, VectorBlur, Relight, Cryptomatte: learn.foundry.com
- Jonah Friedman and Andy Jones, "Fully Automatic ID Mattes with Support for Motion Blur and Transparency", SIGGRAPH 2015 poster, and the Cryptomatte specification: github.com/Psyop/Cryptomatte
- OpenEXR documentation, half-float format and compression methods: openexr.com
*All figures are illustrations computed in your browser from one simple ray-traced room, not production renders or screenshots of any software. The passes, rebuilds, statistics, precision steps and file sizes are real calculations on that scene. Renderer pass names follow each maker's documentation as of October 2026.*