Area Light and Area Shadowing Support

Area Light and Area Shadowing Support

April, 2011 (Revised: September, 2012)


PRMan 16 includes improved support for area shadowing. The new areashadow() function in RSL allows querying of the shadowing from a number of sampled locations on an area light. It supports both ray tracing and deep shadows generated using the areashadow display mode for the deepshad driver.

Users should note that there are correlating techniques described in the Physically Plausible Shading in RSL application note that may be preferred. Users should also consult the Importance of Importance application note for further discussion of ray tracing optimizations.

areashadow Deep Shadows

PRMan 16 includes a new deep shadow output mode that can record the opacity of multiple objects, including those hidden behind completely opaque objects. This new deep shadow map type can be used with the areashadow() shadeop to compute shadowing. Because it is a map, the technique can be used to compute shadowing without the need for traceable geometry. As such it may be appropriate for geometric types that are traditionally memory intensive, such as hair.

To generate a traceable deep shadow map, the areashadow display mode is used with the deepshad driver. The number of opaque objects to record into the map may be specified with the othreshold option, which should be set to a number greater than one to record geometry that is obscured from the point of view of the camera. Nearer geometry will have priority.

Unlike traditional shadow queries, which can only determine visibility from a texel of the map down its 'z' direction, traceable deep shadows can answer the visibility over a broader range of directions. This makes it possible for them to compute shadowing from a selection of positions (such as the samples of an area light), while only using a single map.

It is still important to disable culling of backfacing geometry and hidden geometry in order for the map to contain sufficient information, A typical RIB fragment might look like this:

Option "limits" "color othreshold" [9 9 9]
    # set how many opaque layers deep to record in the map
Option "limits" "float deepshadowerror" [0.01]
    # specify deep shadow compression metric
Display "null" "null" "z"
Display "+Shadow.tdsm" "deepshad" "areashadow"
    # specify the output file using the areashadow method
Attribute "cull" "int backfacing" [0] "int hidden" [0]
    # ensure all geometry is recorded in the map

Note that these new area shadow maps can be viewed with either the dsview or sho utility.

The areashadow shadeop

areashadow(), in its simplest form, computes the ability of light to flow between a specified point and a selection of points on an area light. This returns the average transmission over all the implied rays:

varying color areashadow ( string mapname; filterregion from;
                                 varying point tos[]  [parameterlist]  )

By supplying a filterregion rather than a point to specify the surface area over which you want to compute shadowing, PRMan can antialias the results. Supposing the resizable array tos contains points you have chosen on an arealight, and mapname is a valid tracable deep shadow map, the average transmission will be computed.

trans = areashadow(mapname,from,tos);

Ray-traced results may be computed by passing "raytrace",1 on the parameter list. If the map name is also not empty, tracing will be performed in addition to the map query. For example:

trans = areashadow(mapname,from,tos,"raytrace",1);

will query both the map and transmission visible geometry, whereas:

trans = areashadow("",from,tos,"raytrace",1);

will perform ray tracing only.

When maps and ray tracing are used together, a map will be queried first. If this misses, or produces only partial shadow, it will continue on to try ray tracing. Users should be aware that the result is cumulative and the same geometry may get double-counted. For this reason, you should consider using the trace "subset" parameter to exclude geometry in the shadow maps from also contributing to the ray tracing results.

It should be noted that areashadow() is considerably more efficient than calling transmission in a loop, and should be used wherever possible.

Automatic Bias Computation


As of PRMan 16.1, the areashadow() shadeop can automatically estimate an appropriately tuned bias for deep shadow maps in many situations. This estimate takes into account the map resolution and either map camera scale (for orthogonal shadow maps) or map camera field of view plus the distance of the from point from the shadow map camera (for perspective shadow maps). This is particularly useful in the case where multiple shadow maps are used. As the bias is estimated separately for each ray, it will take into account the particular map that was selected.

The automatic bias is controlled through the mapbias parameter, which scales the estimate. The default of 1 should be appropriate in most cases and will try to produce a bias small enough to preserve contact shadows while still being large enough to avoid self-shadowing artifacts. Occasionally, larger values such as 1.2 may be useful. Setting mapbias to 0 effectively disables the automatic bias.

Note that mapbias does not override the effect of the traditional bias parameter. If both are used, they will be applied in conjunction with each other. For this reason, you may wish to leave bias at 0 if you are not also using ray tracing in the same call.

Light Leaks

A related scale factor, mapbias2, can be used to help control light leaks when tracing rays through shadow maps. In some cases geometry that is seen obliquely from the shadow map camera may have gaps in 'z' between adjacent map texels, which could allow a ray to skip through without receiving the appropriate amount of shadowing. The closer a ray is to parallel with the shadow map camera's image plane, the worse it will suffer from this effect.

To mitigate this, mapbias2 expands the interval in 'z' examined at each texel by areashadow(). This wider filtering in the depth helps with rays that should be fully blocked but only register as partially blocked. Typical values are 1 to 5, with 5 being the default and 0 disabling it. Note that larger values may overly darken penumbras and the shadows of transparent or fine geometry.

More Complex Usage

Often an average transmission is not particularly useful. Far more helpful is the ability to query the transmission of each individual ray implied by the surface/volume position from and the set of area light samples in to. This allows proper integration with a brdf.

areashadow() can do this by supplying additional output parameters. For example:

color perRayTrans[];
color perRayLs[];
float perRayLMags[];

uniform float tolen = arraylength(tos);

This returns the transmissivity along each ray, as well as the L vector implied by it and its length. Note that the L vectors are already unit length and the magnitude of each individual L prior to normalization is returned; this is helpful and avoids having to normalize L, which is typically done prior to its use.

Helpers in stdrsl

PRMan 16 also introduces a set of helpful code headers provided to make things simpler for shader authoring. The header "stdrsl/AreaSampler.h" provides helper functions that sample various types of standard area light shapes. For example:

stdrsl_AreaSample areasampler;
    Ps, radius,
    sampleoffset, maxnsamples, realnsamples,

will distribute stratified samples into the samples array, beginning at index sampleoffset and continuing for up to maxnsamples. These samples can be passed directly to areashadow()'s tos parameter:

varying color areashadow ( string mapname; filterregion from;
                           varying __radiancesample tos[]  [parameterlist]  )

Support for the sampling of disks, rectangles, spheres, and hemispheres is included. The sample member functions provided are:

public void sampleDisk ( varying point Ps;
                         uniform float radius;
                         uniform float sampleoffset;
                         uniform float maxnsamples;
                         output uniform float realnsamples;
                         output __radiancesample samples[] );
public void sampleRectangle ( varying point Ps;
                              uniform float xsize;
                              uniform float ysize;
                              uniform float sampleoffset;
                              uniform float maxnsamples;
                              output uniform float realnsamples;
                              output __radiancesample samples[] );
public void sampleSphere ( varying point Ps;
                           uniform float radius;
                           uniform float sampleoffset;
                           uniform float maxnsamples;
                           output uniform float realnsamples;
                           output __radiancesample samples[] );
public void sampleHemisphere ( varying point Ps;
                               uniform float radius;
                               uniform float sampleoffset;
                               uniform float maxnsamples;
                               output uniform float realnsamples;
                               output __radiancesample samples[] );

The direction, distance, and lightPdf fields of the samples in the given range are available after one of the sample methods has been called. It is important to note that the number of samples may differ slightly from the requested number due to the stratification method. The actual number of samples produced will be returned in realnsamples.


Here's a scene containing hair and complex geometry:


Multiple Shadow Maps

The areashadow() shadeop accepts an array of deep shadow map names. This can be used to select the most efficient map that most accurately can answer the shadowing of a given ray.

While the area shadow maps do not need to be strictly positioned at each light source as with traditional shadow maps, the areashadow() shadeop will produce faster and more accurate results the more closely aligned one of the available maps is.

Our current heuristic simply chooses the map with the view direction that has the smallest angle difference from the ray direction. This ignores the shadow camera position, map resolution, ray origin, and other factors. As a result, all shadow maps used together in a single areashadow() call should include the same geometry at roughly the same resolution.

The following shows a scene and the two cameras from which areashadow-type deep shadows are rendered (though typically more would be needed). An example soft shadow is shown below.

images/figures.areashadow/twoshadows.png images/figures.areashadow/twoshadows_render.png

Rendering Hair

Please see the RiCurves application note for important information regarding rendering hair and shadow maps.