Radiance‎ > ‎

Stencil method revisited

In march 2007 John Mardaljevic posted a technique to calculate illuminance distributions on arbitrarily shaped surfaces without suffering from "light leaks". His original messages can be found here: 


Back then I tried this method, had some success and resolved to analyse the details of his command. Recently I finally had a reason to look at his proposal again and here is my explanation and improvement (so I hope) of this process.

Picture used as 'stencil'

To be able to use the command line we will discuss below we have to prepare two different scene:
  1. stencil.oct which contains only the work plane polygon(s) and
  2. scene.oct which contains the geometry we want to simulate.

Creating stencil.pic

The stencil.oct scene contains no sky or other light sources so we have to assign a 'glow' material to the polygons to make them visible. The polygons have to be oriented correctly otherwise they will not show in our image. From this very simple scene we now create the stencil images stencil.pic which is later used to mask the points of the image where we do not want to calculate the light levels. The stencil image is created with the same view specification and dimensions as the final rendering so it's just convenient to keep it in a view file (best.vf).

rpict -x 1024 -y 1024 -vf best.vf stencil.oct > stencil.pic

We do not need to set any ambient parameters because the polygons we want to see glow on their own and do not need to be illuminated by a light source.


The stencil command

Below is the command line copied from John's email:
vwrays -ff -x 1024 -y 1024 -vf best.vf \
    | rlam -if6 - -if1 '\!pvalue -h -H -df -b stencil.pic' \
    | rcalc -if7 -of -e '$1=$1;$2=$2;$3=$3' \
-e 'out(v)=if($7-.5,v,0)'
-e '$4=out($4);$5=out($5);$6=out($6)' \
    | rtrace -ffc `vwrays -d -x 1024 -y 1024 -vf best.vf` -i -ab 4 scene.oct \
    > stencilled_ill.pic
Let's go through this step by step:

vwrays -ff -x 1024 -y 1024 -vf best.vf \
  • This command will generate view rays for the view specification defined in best.vf to fit an image with a maximum dimension of 1024 pixels (if the view spec is not square one side of the image will be smaller).
  • The -ff option switches the output to a more efficient binary float format. If you want to see the output of vwrays remove the -ff option and reduce the image resolution to ie. 10px. This will create a number of lines with 6 values: the x, y and z coordinates of the ray origin and its dx, dy and dz direction.
  • The backslash ('\') at the end of the line tell your shell that the command is not finished yet and will continue on the next line.
| rlam -if6 - -if1 '\!pvalue -h -H -b -df stencil.pic' \
  • The first character in this line is the Unix 'pipe' symbol ('|'). This takes the output of the previous command and feeds it to the following command.
  • rlam is part of the Radiance tools. It reads lines from different input sources and combines them into a single output line. In this case we have two sources of input:
  • -if6 - means read 6 float values ('-if6') from the standard input ('-') which is the output of the vwrays command.
  • -if1 '\!pvalue -h -H -df -b stencil.pic' The second source is an inline command that should be read one value ('-if1') at a time.
  • If you use a csh you have to hide the exclamation mark from your shell with and '\'. Bash users don't need to do this.
  • The command pvalue prints the values of every pixel in our stencil.pic image. '-h -H' are options to tell pvalue to print only the pixel values (no header or image size) as a single brightness value ('-b') instead of the red, green and blue channel separately.
  • The result of the rlam command are lines containing the view origin, direction and brightness value for each pixel in the stencil.pic image.
  • Again we are not done yet and continue on the next line.
| rcalc -if7 -of
  • rcalc allows us to modify the values it reads as input based on some expressions we specify on the command line ('-e ...').
  • Again we have to state that the input should be read 7 float values at a time ('-if7') and that the output should also be in float format ('-of').
-e '$1=$1;$2=$2;$3=$3'
  • This first expression tells rcalc to pass on the first three values unmodified. The first three values were the origin of the view ray which we will use as is.
-e 'out(v)=if($7-.5,v,0)'
  • The next expression defines a function that can be used later on in the command line. This function takes the a value as input ('v') and returns either the value or 0 if the 7th argument to rcalc is bigger than 0.5. In other words: If the pixel in stencil.pic is dark it will return 0, otherwise it returns the input value.
-e '$4=out($4);$5=out($5);$6=out($6)' \
  • In this last piece we use the previously defined 'out(v)' function to modify the 4th to 6th value of the input based on the value of the 7th. After this expression rcalc will output lines with the view origin and either the view direction if the pixel was bright or '0 0 0' if the pixel was dark.
The result of the command line so far is a new set of valid view rays for every bright pixel in stencil.pic. The dark pixels will produce a view direction of '0 0 0' which will be ignored by the following rtrace command.

| rtrace -ffc `vwrays -d -x 1024 -y 1024 -vf best.vf` -i -ab 4 scene.oct \
  • The new view rays are used as input to rtrace which will perform an irradiance calculation for each of these view rays.
  • Our end result should be an image so we tell rtrace to produce values in a format that can be saved as a Radiance picture ('-ffc').
  • To create a valid picture file rtrace needs to know the dimensions of the image. The second vwrays call is enclosed in 'back ticks' and will be executed by the shell separately. The result is the image resultion in '-x ... -y ...' format which is used by rtrace to produce the right image size.
  • The remaining arguments ('-i -ab 4') are calculation options for rtrace. Here you can add all the necessary ambient parameters to for an accurate simulation of scene.oct.
> stencilled_ill.pic
  • Finally we direct the output of rtrace to a new picture file.

Processing the output image

The resulting image is an illuminance image of the surface under our stencil plane (the floor). This image can be used like any other Radiance illuminance image to calculate a numeric value or to create falsecolor maps.

Numeric average

The stencilled_ill.pic contains only the illuminance values of the floor and nothing else. To calculate the average of all values we just have to remove the pixels with 0 value (black) and take the average of the rest. On a Unix system we can use grep to remove the black pixels and total to calculate the average in a straight forward command:

pvalue -h -H -b -d stencilled_ill.pic | grep -v 0.000e+00 | total -m

Falsecolor maps

In contrast to a true cutting plane view (-vo option of rpict) it is missing the context of the scene. To add the rest of the scene we can use pcomb to add another image with the same view specs as a background. This can be an image that was created with Radiance or a screenshot of a 3D modeler; we only have to be able to reproduce the same view perspective as used in the rtrace command (best.vf).

If we want to add contour lines to our image we can easily use the preset of the pcomb to add the images together. However, if we want a full falsecolor map as shown above in our combined image we have to remove the violet background from the map or our context will get this color tint. Again we can use a form of stencil map to tell pcomb where is should take the pixels from the falsecolor map and where it should ignore them.

First we have to add a white border to stencil.pic and background.pic to account for the legend added by falsecolor.

pcompos -x 1124 -b 1 1 1 stencil.pic 100 0 > stencil_b.pic
pcompos -x 1124 -b 1 1 1 background.pic 100 0 > background_b.pic

pcompos -x 1124
  • The default width of the legend created by falsecolor is 100 pixels. Therefore our final image width is 1024+100 pixels.
-b 1 1 1
  • When there is no image date for pcompos to use it will fill the are with values of 1 1 1.
stencil.pic 100 0
  • The input for pcompos is our original stencil.pic image. To keep the space for the legend at the right margin we add an offset of 100 pixels as the x position and 0 for y (there is no change in height).
> stencil_b.pic
  • The output is saved as a new stencil image.
This will create a border on the left side of our stencil_b.pic image where the pixel values of each color will be 1. This corresponds usually to a white color although this depends on the value range of the image. If you specified a modest glow intensity for the glow material of the stencil plane you should have a clearly white border. Either way we only have to specify a value that is different enough from the background to be identified correctly in the next step.

Now we have the raw images we need to combine our final image. Similar to rcalc above we start pcomb with an expression:

pcomb -e 'ro=if(ri(1)-.5,ri(2),ri(3));go=if(gi(1)-.5,gi(2),gi(3));bo=if(bi(1)-.5,bi(2),bi(3));' \
         stencil_b.pic falsecolor.pic background_b.pic > combined.pic

pcomb -e 'ro=...;go=...;bo=...;'
  • For pcomb to work on a coloured image we have to specify expressions for each colour channel separately. Each channel in the output is identified by 'ro=' for red, 'go=' for green and 'bo=' for blue.
  • In our example the expressions for each channel are identical but they could be more sophisticated.
  • We define an if() statement that consist of 3 elements: the first is the condition that is evaluated ('ri(1)-.5'), the second is the value that should be used if the statement is true ('ri(2)'), the third is used if the expression is false ('ri(3)').
  • ri(n) is a short hand notation in pcomb that refers to the value of the red channel or the n-th input image.
  • 'ri(1)-.5' tests the red value of a pixel in the first input image if it's bigger than 0.5 or not.
  • The whole if() expression therefore means: if the red value in the first image is bigger than 0.5 use the red value of the 2nd image, otherwise the red value of the 3rd image.
  • We repeat the same expression for the green and blue channel of the image.
stencil_b.pic falsecolor.pic background_b.pic
  • We use stencil_b.pic as the first input image to mask out the violet areas of the falsecolor.pic and replace them with the information from background_b.pic.
The final combined.pic images shows the falsecolor map only where our stencil plane was before. The white border in stencil_b.pic will keep the falsecolor legend in the final image.


Because the viewpoint and view direction are unchanged this command line can only be used where the stencil plane and the area of interest coincide from the given view point. It also only creates values for the scene surfaces (like the floor or walls of a room) because that's where the new rays intersect with the scene.

But what if you want to calculate values on a virtual plane like the work plane or a vertical section through the scene? To achieve this we have to use the octree of the stencil plane directly instead of a 2-dimensional image of it.

Octree used as stencil mask

This method combines the above stencil image and another technique John presented earlier to create arbitrary clipping planes. In short we replace the pvalue expression above with an additional invocation of rtrace. With the right output format option we can produce more information about the stencil plane (or object in general) than just its x and y extend in the image. 

vwrays -fd -x 1024 -y 1024 -vf best.vf \
        | rtrace -w -h -fd -opv stencil.oct \
        | rcalc -id6 -od -e '$1=$1;$2=$2;$3=$3;$4=0;$5=0;$6=if($6-0.5,1,0)' \
        | rtrace -fdc `vwrays -d -x 1024 -y 1024 -vf best.vf` -I -ab 4 scene.oct \
        > plane_ill.hdr

I will just discuss the changes to the previous command:

vwrays -fd -x 1024 -y 1024 -vf best.vf \
  • One global change here is the use of floating point double values (options -fd, -id6, -od and -fdc) as in and output between the individual tools.
| rtrace -w -h -fd -opv stencil.oct \
  • rlam and pvalue are replaced with a first rtrace command that reads the view rays generated by vwrays and starts to trace these rays in the scene stencil.oct which contains the objects that represent our sample plane. In the most common application this could be the working plane in an office or a vertical section through an atrium.
  • With the -opv option modifying its output rtrace calculates the point of intersection in the scene and the radiance ('value') at that point instead of only the radiance. The -o.. switch is a very powerful option to rtrace and allows us to generate a lot of information about a point in the scene like the material or surface normal. See the man page for further details.
  • Note that we do not specify any ambient parameters for this rtrace call. The surfaces in stencil.oct are self illuminated so we don't need to consider ambient illumination.
| rcalc -id6 -od -e '$1=$1;$2=$2;$3=$3;
  • Again we evaluate the point and value information from rtrace with rcalc. The first three coordinates are used without modifications because they represent the 3D coordinates of all visible points in the work plane.
$4=0;$5=0;$6=if($6-0.5,1,0)' \
  • We ignore the red and blue component of the radiance value and replace them with zeros.
  • The green component is compared to a threshold value of 0.5 which eliminates rounding errors in the process. If rtrace has calculated a real radiance value at this point we convert the output to 1 otherwise its 0.
  • As a result of this expression we get a vector of '0 0 1' for every point in our work plane while the rest will produce '0 0 0' (just as in the previous command).
| rtrace -fdc `vwrays -d -x 1024 -y 1024 -vf best.vf-I -ab 4 scene.oct \
  • The new view rays are processed by a second rtrace command with the ambient values appropriate for the scene.
  • Note that this time we use '-I' (uppercase) because we want to calculate the irradiance at the specified point and not the point our new view vector points at. This is the main difference to the previous command line: It allows us to sample any point in 3D space and not just the surfaces of the scene.
> plane_ill.hdr
  • Output to picture as before.