Photon Mapping for General Shaders

June 2007


1 Introduction

Photon mapping has been a part of PRMan since version 11.0. However, the light sources had to have hard-coded distance fall-offs and the surface materials were restricted to a fixed, limited set of materials (matte, translucent, chrome, transparent, glass, and water). Perhaps the biggest limitation was that textures could not be used in the photon tracing phase.

PRMan 13.5 introduces a new and much more general and flexible approach to photon mapping. The new approach works with very general shaders, including area light sources, surface textures and displacement shaders. The main drawback is that it requires the direct illumination and the surface colors to be baked into point cloud files. The purpose of this application note is to provide examples for the new use of photon maps for caustics and global illumination with very general light source and surface shaders.


2 Sampling illumination and scattering coefficients

In order to control the emission and and tracing of photons, we first need to bake the the illumination and surface colors.

2.1 Baking direct illumination

Photon emission corresponding to simple light sources (point lights, spot lights, and directional lights -- even with "barn doors", cucoloris, and projected textures) is fairly straightforward. However, general light source shaders can also have unnatural distance fall-off, fake shadow positions, fake position of the highlights, etc. It is difficult to make the photon emission match such shaders. However, here we will use a different approach: evaluate the light source shaders at the surface points by rendering the direct illumination from the light sources. This basically treats each light source shader as a "black box" that is fully characterized by its illumination on the surfaces in the scene.

In order to specify the direct illumination, we need to render the scene with a light source shader that bakes _irradiance, L, and _area using the bake3d() function. (L is not necessary if all surfaces are diffuse -- more on this below.)

As an example, we'll use a box with two teapots and texture and displacement maps. The scene is illuminated by three light sources. They store illumination for use in photon emission -- three light sources bake into the same point cloud file. Below are the two light source shaders used. The shader 'myspotlight' is a spot light with no distance fall-off by default; 'projectorlightxz' projects a light texture (in the x-z plane) similar to a slide projector. The shadows are computed with ray tracing. The line "L = L;" is a necessary trick to avoid an unwanted optimization, thus ensuring that the light source shader is being run on all shading grids.

    light
    myspotlight(
      float intensity = 1;
      color lightcolor = 1;
      float falloff = 0;   // default: no fall-off
      point from = point "shader" (0,0,0);   // light position
      vector dir = (0, -1, 0);   // light direction (center of cone)
      float coneangle = radians(30);
      float conedeltaangle = radians(5);
      string filename = "";)   // point cloud file
    {
      uniform float cosoutside = cos(coneangle);
      uniform float cosinside = cos(coneangle-conedeltaangle);
      vector dirn, Ln;
      float dist, cosangle, atten;
      float a = area(Ps, "dicing"); // micropolygon area

      dirn = normalize(dir);

      illuminate(from, dirn, coneangle) {
        L = L; // trick to avoid skipping light source shader call !!
        dist = length(L);
        Ln = L / dist;
        cosangle = dirn.Ln;
        atten = pow(dist, -falloff);   // distance fall-off
        atten *= smoothstep(cosoutside, cosinside, cosangle); // cone edge
	Cl = atten * intensity * lightcolor;
	Cl *= transmission(Ps, from);   // ray traced shadow

        // Compute irradiance to bake
        float dot = -(Ln.N) / length(N);
        color irrad = Cl * abs(dot);

        // Bake the direct illum, illum direction, and micropolygon area
        if (filename != "" && (irrad[0] > 0 || irrad[1] > 0 || irrad[2] > 0))
            bake3d(filename, "_irradiance,L,_area", Ps, N, "interpolate", 1,
                   "_irradiance", irrad, "L", Ln, "_area", a);
      }
    }
    light
    projectorlightxz(
      float intensity = 1;
      color lightcolor = 1;
      float minx = -1, maxx = 1, minz = -1, maxz = 1;
      string texturename = "";
      point from = point "shader" (0,0,0);   // light position
      vector dir = (0, -1, 0);   // light direction (don't illum behind)
      string filename = "";)   // point cloud file name
    {
      uniform float dx = maxx - minx, dz = maxz - minz;
      vector Ln;
      float x = Ps[0], z = Ps[2], s, t;
      float a = area(Ps, "dicing"); // micropolygon area

      illuminate(from, dir, PI/2) {
        if (minx <= x && x <= maxx && minz <= z && z <= maxz) {

          // Compute illumination (ignoring shadow)
          Cl = intensity * lightcolor;
          if (texturename != "") {
            s = (x - minx) / dx;
            t = (z - minz) / dz;
            Cl *= texture(texturename, s, t);
          }

          // Compute ray-traced shadow
          Cl *= transmission(Ps, from);

          // Compute irradiance to bake
          Ln = normalize(L);
          float dot = -(Ln.N) / length(N);
          color irrad = Cl * abs(dot);

          // Bake the direct illum, illum direction, and micropolygon area
          if (filename != "" && (irrad[0] > 0 || irrad[1] > 0 || irrad[2] > 0))
            bake3d(filename, "_irradiance,L,_area", Ps, N, "interpolate", 1,
                   "_irradiance", irrad, "L", Ln, "_area", a);
        }
      }
    }

For completeness, the three displacement shaders are listed in appendix A. The use of displacement shaders here demonstrates that displacement works fine with photon mapping.

Here is the rib file for the scene:

    FrameBegin 1

      Format 400 400 1
      ShadingInterpolation "smooth"
      PixelSamples 4 4
      Display "box_bake_direct" "it" "rgba"
      Projection "perspective" "fov" 30
      Translate 0 0 5

      DisplayChannel "color _irradiance"
      DisplayChannel "vector L"
      DisplayChannel "float _area"

      WorldBegin

        Attribute "cull" "hidden" 0   # ensure illum is baked behind objects
        Attribute "cull" "backfacing" 0   # ensure illum is baked on backsides
        Attribute "dice" "rasterorient" 0   # view-independent dicing

        # Turn on ray-traced shadows
        Attribute "visibility" "transmission" "opaque" 
        Attribute "trace" "bias" 0.0001

        # Turn on displacements
        Attribute "trace" "displacements" 1
        Attribute "displacementbound" "sphere" 0.1 "coordinatesystem" "world"

        # Light sources (each bakes its own illumination)
        LightSource "myspotlight" 1   # wide white spot
           "from" [0.5 0.999 0] "dir" [0 -1 0] "coneangle" 1.2
           "filename" "box_direct.ptc"
        LightSource "myspotlight" 2   # narrow red spot
           "from" [0.5 0.999 -0.5] "dir" [-0.3 -1 0.3]
           "intensity" 0.5 "lightcolor" [1 0 0] "coneangle" 0.3
           "filename" "box_direct.ptc"
        LightSource "projectorlightxz" 3   # project bright prman logo texture
           "from" [-0.7 0 0.75] "dir" [0 1 0]
           "minx" -0.9 "maxx" -0.4 "minz" 5.5 "maxz" 5.99 "intensity" 2
           "texturename" "prman_logo.tex"
           "filename" "box_direct.ptc"

        Surface "matte"

        # Left wall
        AttributeBegin
          Displacement "dispsinz" "freq" 22 "scale" 0.1
          Polygon "P" [ -1 1 -1  -1 1 1  -1 -1 1  -1 -1 -1 ]
            "s" [0 1 1 0] "t" [0 0 1 1]
        AttributeEnd

        AttributeBegin
          Polygon "P" [ 1 -1 -1  1 -1 1  1 1 1  1 1 -1 ]   # right wall
          Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]   # floor
          Polygon "P" [ -1 1 -1  1 1 -1  1 1 1  -1 1 1 ]   # ceiling
        AttributeEnd

        # Back wall
        AttributeBegin
          Attribute "displacementbound" "sphere" 0.01 "coordinatesystem" "world"
          Displacement "dispmap" "texturename" "prman_logo.tex" "scale" -0.01
          Polygon "P" [ -1 1 1  1 1 1  1 -1 1  -1 -1 1 ]
            "s" [-1 2 2 -1] "t" [-1 -1 2 2]
        AttributeEnd

        # Left teapot (chrome, but matte in this pass)
        AttributeBegin
          Translate -0.35 -0.999 0.35
          Scale 0.18 0.18 0.18
          Rotate -30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

        # Right teapot (displaced matte)
        AttributeBegin
          ShadingRate 0.25   # dense tessellation for fine displacement details
          Displacement "disppumpkin" "freq" 9 "scale" 0.05
          Translate 0.35 -0.999 -0.35
          Scale 0.18 0.18 0.18
          Rotate 30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

      WorldEnd
    FrameEnd

Running this rib file gives the following illumination image and point cloud "box_direct.ptc":


Direct illumination (on matte shader)

Direct illumination point cloud (channel _irradiance)

The red illumination is less visible in the image to the right since ptviewer happens to display the gray illumination points on top of the red illumination points. Also note that no points are baked in areas with no illumination.

Special case: If all surfaces are purely diffuse, the L direction does not need to be baked. In that case, it is sufficient to bake the total Cl (the sum of illumination from all light sources) along with the micropolygon area; this can can be done in either the surface shader or in an atmosphere shader.

2.2 Baking scattering coefficients

To specify the scattering coefficients, the scene must be rendered with surface shaders that bake the scattering coefficients for the surface materials of the objects.

The surface shader 'bake_scattercoeffs' stores scatter coefficients for use in photon tracing: diffuse reflection, specular reflection, diffuse refraction, and specular refraction. (The baking and later lookups rely on the surface normals pointing outward.)

    surface
    bake_scattercoeffs(string filename = "", displaychannels = "";
                       color specrefl = 0, diffrefr = 0, specrefr = 0;
                       float ior = 1;
                       string texturename = "")
    {
      color diffrefl = Cs;
      normal Nn = normalize(N);

      // Multiply diffuse color by texture, if present
      if (texturename != "")
        diffrefl *= texture(texturename);        

      // Store scattering coefficients in point cloud file
      bake3d(filename, displaychannels, P, Nn, "interpolate", 1,
             "diffrefl", diffrefl, "specrefl", specrefl,
             "diffrefr", diffrefr, "specrefr", specrefr, "ior", ior);

      Ci = diffrefl * Os;
      Oi = Os;
    }
Here is the rib file:
    FrameBegin 1

      Format 400 400 1
      ShadingInterpolation "smooth"
      PixelSamples 4 4
      Display "box_bake_scattercoeffs" "it" "rgba"
      Projection "perspective" "fov" 30
      Translate 0 0 5

      DisplayChannel "color diffrefl"
      DisplayChannel "color specrefl"
      DisplayChannel "color diffrefr"   # unused in this example
      DisplayChannel "color specrefr"   # unused in this example
      DisplayChannel "float ior"        # unused in this example

      WorldBegin

        Attribute "cull" "hidden" 0   # ensure illum is baked behind objects
        Attribute "cull" "backfacing" 0   # ensure illum is baked on backsides
        Attribute "dice" "rasterorient" 0   # view-independent dicing

        # Turn on displacements
        Attribute "trace" "displacements" 1
        Attribute "displacementbound" "sphere" 0.1 "coordinatesystem" "world"

        # Left wall
        AttributeBegin
          Displacement "dispsinz" "freq" 22 "scale" 0.1
          Surface "bake_scattercoeffs"
            "filename" "box_scattercoeffs.ptc"
            "displaychannels" "diffrefl,specrefl"
            "texturename" "irma.tex"
          Polygon "P" [ -1 1 -1  -1 1 1  -1 -1 1  -1 -1 -1 ]
            "s" [0 1 1 0] "t" [0 0 1 1]
        AttributeEnd

        # Right wall
        AttributeBegin
          Surface "bake_scattercoeffs"
            "filename" "box_scattercoeffs.ptc"
            "displaychannels" "diffrefl,specrefl"
            "texturename" "tinny.tex"
          Polygon "P" [ 1 -1 -1  1 -1 1  1 1 1  1 1 -1 ]
            "s" [0 1 1 0] "t" [1 1 0 0]
        AttributeEnd

        # Floor
        AttributeBegin
          Surface "bake_scattercoeffs"
            "filename" "box_scattercoeffs.ptc"
            "displaychannels" "diffrefl,specrefl"
            "texturename" "checkerboard10.tex"
          Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]
            "s" [0 0.995 0.995 0] "t" [0.005 0.005 0.995 0.995]
        AttributeEnd

        # Ceiling
        AttributeBegin
          Color [0.8 0.8 0.8]
          Surface "bake_scattercoeffs"
            "filename" "box_scattercoeffs.ptc"
            "displaychannels" "diffrefl,specrefl"
          Polygon "P" [ -1 1 -1  1 1 -1  1 1 1  -1 1 1 ]
        AttributeEnd

        # Back wall
        AttributeBegin
          Color [0.8 0.8 0.8]
          Attribute "displacementbound" "sphere" 0.01 "coordinatesystem" "world"
          Displacement "dispmap" "texturename" "prman_logo.tex" "scale" -0.01
          Surface "bake_scattercoeffs"
            "filename" "box_scattercoeffs.ptc"
            "displaychannels" "diffrefl,specrefl"
          Polygon "P" [ -1 1 1  1 1 1  1 -1 1  -1 -1 1 ]
            "s" [-1 2 2 -1] "t" [-1 -1 2 2]
        AttributeEnd

        # Left teapot (chrome)
        AttributeBegin
          Color [0 0 0]
          Surface "bake_scattercoeffs" "specrefl" [1 1 1]
            "filename" "box_scattercoeffs.ptc"
            "displaychannels" "diffrefl,specrefl"
          Translate -0.35 -0.999 0.35
          Scale 0.18 0.18 0.18
          Rotate -30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

        # Right teapot (matte)
        AttributeBegin
          Color [0.8 0.8 0.8]
          ShadingRate 0.25
          Displacement "disppumpkin" "freq" 9 "scale" 0.05
          Surface "bake_scattercoeffs"
            "filename" "box_scattercoeffs.ptc"
            "displaychannels" "diffrefl,specrefl"
          Translate 0.35 -0.999 -0.35
          Scale 0.18 0.18 0.18
          Rotate 30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

      WorldEnd
    FrameEnd

Running this rib file gives the following image:


Scattering coefficients (diffuse surface color)

And here are the diffuse and specular reflection coefficients of the generated point cloud "box_scattercoeffs.ptc":


Channel 'diffrefl'

Channel 'specrefl'

In this example, we only baked diffuse and specular reflection coefficients. It is also possible to bake diffuse and specular refraction coefficients as well as the index of refraction (ior).

In this example, all scattering coefficients were baked to a single point cloud file. It is also possible to bake several point clouds, for example one per object in the scene.


3 Photon emission and tracing

The next step is photon emission and tracing. The photon emission and tracing is guided by the point clouds baked in section 2.

3.1 Specifying photon emission

Photons are emitted in the same way as before: with a photon hider. The new thing is that the photon emission can be specified by a point cloud that is passed with the parameter "emissionpointcloud". For example:

  Hider "photon" "emit" 100000 "emissionpointcloud" "box_direct.ptc"

If no emissionpointcloud is specified, PRMan will revert to the old behavior: it will emit photons corresponding (as much as possible) to the light sources in the scene.

3.2 Specifying surface materials

The point cloud to use for surface scattering values (diffrefl, diffrefr, specrefl, specrefr, ior) is specified with the Attribute "photon" "shadingmodel" and a filename prepended with "pointcloud:" or "brickmap:". For example:

  Attribute "photon" "shadingmodel" "pointcloud:foo.ptc"
  Attribute "photon" "shadingmodel" "brickmap:bar.bkm"
The old, predefined surface materials can still be used, too: Attribute "photon" "shadingmodel" ["matte" | "translucent" | "chrome" | "transparent" | "glass" | "water"].

3.3 Example: photon emission and tracing in the box scene

In the following rib file, the illumination point cloud "box_direct.ptc" is used to guide the photon emission and the scattering coefficients in "box_scattercoeffs.ptc" are used for photon tracing.

    FrameBegin 1
      Hider "photon" "emit" 1000000 "emissionpointcloud" "box_direct.ptc"

      Format 400 400 1   # not necessary, but makes ptviewer display nicer
      Projection "perspective" "fov" 30   # ditto

      Translate 0 0 5

      WorldBegin

        # The name of the global photon map and caustic photon map files
        Attribute "photon" "globalmap" "box_gpm.ptc"
        Attribute "photon" "causticmap" "box_cpm.ptc"

        # Ray tracing attributes
        Attribute "trace" "maxspeculardepth" 5
        Attribute "trace" "maxdiffusedepth" 5
        Attribute "trace" "bias" 0.0001
        Attribute "trace" "displacements" 1
        Attribute "displacementbound" "sphere" 0.1 "coordinatesystem" "world"

        Attribute "dice" "rasterorient" 0 # view-indep dicing to match ptcloud

        # Use surface scattering values in point cloud box_scattercoeffs.ptc
        Attribute "photon" "shadingmodel" "pointcloud:box_scattercoeffs.ptc"

        # Matte box
        AttributeBegin
          Displacement "dispsinz" "freq" 22 "scale" 0.1
          Polygon "P" [ -1 1 -1  -1 1 1  -1 -1 1  -1 -1 -1 ]   # left wall
            "s" [0 1 1 0] "t" [0 0 1 1]
        AttributeEnd
        AttributeBegin
          Polygon "P" [ 1 -1 -1  1 -1 1  1 1 1  1 1 -1 ]   # right wall
          Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]   # floor
          Polygon "P" [ -1 1 -1  1 1 -1  1 1 1  -1 1 1 ]   # ceiling
        AttributeEnd
        AttributeBegin
          Attribute "displacementbound" "sphere" 0.01 "coordinatesystem" "world"
          Displacement "dispmap" "texturename" "prman_logo.tex" "scale" -0.01
          Polygon "P" [ -1 1 1  1 1 1  1 -1 1  -1 -1 1 ]   # back wall
            "s" [-1 2 2 -1] "t" [-1 -1 2 2]
        AttributeEnd

        # Left teapot (chrome)
        AttributeBegin
          Translate -0.35 -0.999 0.35
          Scale 0.18 0.18 0.18
          Rotate -30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

        # Right teapot (matte)
        AttributeBegin
          ShadingRate 0.25   # to match pointcloud
          Displacement "disppumpkin" "freq" 9 "scale" 0.05
          Translate 0.35 -0.999 -0.35
          Scale 0.18 0.18 0.18
          Rotate 30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

      WorldEnd
    FrameEnd

Running this rib file generates a global photon map, "box_gpm.ptc", with approximately 2.6 million photons. The photon map powers and diffuse surface colors look like this (powers displayed with ptviewer -multiply 50000 since they are very dim):


Photon powers

Cs colors at photon positions


4 Computing the radiosity map

Using the photon map's powers and diffuse surface colors, we can compute a radiosity estimate at each photon position. The resulting point cloud is often called a radiosity map. The radiosity map can be computed using the ptfilter stand-alone program. For example:

  ptfilter -photonmap -nphotons 200 box_gpm.ptc box_rad.ptc

The image below shows the computed radiosity map (box_rad.ptc) for the box scene example:


Radiosity map (point cloud)


5 Rendering with final gathering

All that remains is to render the final image. The radiosity map shown above is obviously too noisy to render directly, so we need to do a final gather to improve the quality. The final gather can be done in two ways:

  1. Create a brick map from the radiosity map point cloud and use ray traced final gathering. This approach is described in the PRMan Application Note "Ambient occlusion, image-based illumination, and global illumination", section 5.3.
  2. point-based color bleeding. This is similar to the color bleeding approach described in PRMan Application Note "Point-based approximate ambient occlusion and color bleeding", section 3.2. The only difference is that the point cloud in this case represents global illumination, not direct illumination.

In this example we'll use the point-based approach since it is usually by far the fastest. This is a shader that computes point-based color bleeding (final gathering):

    surface 
    matte_ptbased_globillum(uniform string texturename = "";
                            uniform string globillumfile = "", causticfile = "";
                            float Kd = 1;
                            float causticphotons = 100)
    {
      color irrad = 0, tex = 1;
      normal Nn = normalize(N);

      // Compute direct illumination
      irrad = diffuse(Nn);

      // Add point-based color bleeding
      if (globillumfile != "")
        irrad += indirectdiffuse(P, Nn, 0, "pointbased", 1, "clamp", 1,
                                 "sortbleeding", 1, "samplebase", 1.0,
                                 "filename", globillumfile);

      // Add caustic
      if (causticfile != "")
        irrad += photonmap(causticfile, P, Nn, "estimator", causticphotons);

      // Lookup texture
      if (texturename != "")
        tex = texture(texturename);

      // Set Ci and Oi
      Ci = Kd * Cs * tex * irrad;
      Ci *= Os;   // premultiply opacity
      Oi = Os;
    }

Notice that to get the best quality point-based color bleeding, the indirectdiffuse() parameters "clamp" and "sortbleeding" are on.

The following is the rib file for rendering the final high-quality image of global illumination in the box:

    FrameBegin 1
      Format 400 400 1
      ShadingInterpolation "smooth"
      PixelSamples 4 4
      Display "box_render_gi" "it" "rgba"
      Projection "perspective" "fov" 30
      Translate 0 0 5

      WorldBegin

        # Turn on ray-traced shadows and reflections
        Attribute "visibility" "transmission" "opaque" 
        Attribute "visibility" "specular" 1 # make objects visible to refl. rays
        Attribute "trace" "bias" 0.001   # incr. if fewer photons in photon map

        # Turn on displacements
        Attribute "trace" "displacements" 1
        Attribute "displacementbound" "sphere" 0.1 "coordinatesystem" "world"

        # Light sources (no baking)
        LightSource "myspotlight" 1
          "from" [0.5 0.999 0] "dir" [0 -1 0] "coneangle" 1.2
        LightSource "myspotlight" 2
          "from" [0.5 0.999 -0.5] "dir" [-0.3 -1 0.3]
          "intensity" 0.5 "lightcolor" [1 0 0] "coneangle" 0.3
        LightSource "projectorlightxz" 3
          "from" [-0.7 0 0.75] "dir" [0 1 0]
          "minx" -0.9 "maxx" -0.4 "minz" 5.5 "maxz" 5.99
          "intensity" 2
          "texturename" "prman_logo.tex"

        # Left wall (displaced and texture mapped)
        AttributeBegin
          Attribute "dice" "rasterorient" 0 # view-indep dicing avoids skinny mps
          Displacement "dispsinz" "freq" 22 "scale" 0.1
          Surface "matte_ptbased_globillum" 
            "texturename" "irma.tex"
            "globillumfile" "box_rad.ptc"
            #"causticfile" "box_cpm.ptc" "causticphotons" 100
          Polygon "P" [ -1 1 -1  -1 1 1  -1 -1 1  -1 -1 -1 ]
            "s" [0 1 1 0] "t" [0 0 1 1]
        AttributeEnd

        # Right wall (texture mapped)
        AttributeBegin
          Surface "matte_ptbased_globillum" 
            "texturename" "tinny.tex"
            "globillumfile" "box_rad.ptc"
            #"causticfile" "box_cpm.ptc" "causticphotons" 100
          Polygon "P" [ 1 -1 -1  1 -1 1  1 1 1  1 1 -1 ]
            "s" [0 1 1 0] "t" [1 1 0 0]
        AttributeEnd

        # Floor (texture mapped)
        AttributeBegin
          Surface "matte_ptbased_globillum" 
            "texturename" "checkerboard10.tex"
            "globillumfile" "box_rad.ptc"
            #"causticfile" "box_cpm.ptc" "causticphotons" 100
          Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]
            "s" [0 0.995 0.995 0] "t" [0.005 0.005 0.995 0.995]
        AttributeEnd

        # Ceiling
        AttributeBegin
          Color [0.8 0.8 0.8]
          Surface "matte_ptbased_globillum" 
            "globillumfile" "box_rad.ptc"
            #"causticfile" "box_cpm.ptc" "causticphotons" 100
          Polygon "P" [ -1 1 -1  1 1 -1  1 1 1  -1 1 1 ]
        AttributeEnd

        # Back wall (displaced)
        AttributeBegin
          Color [0.8 0.8 0.8]
          Attribute "displacementbound" "sphere" 0.01 "coordinatesystem" "world"
          Displacement "dispmap" "texturename" "prman_logo.tex" "scale" -0.01
          Surface "matte_ptbased_globillum"
            "globillumfile" "box_rad.ptc"
            #"causticfile" "box_cpm.ptc" "causticphotons" 100
          Polygon "P" [ -1 1 1  1 1 1  1 -1 1  -1 -1 1 ]
            "s" [-1 2 2 -1] "t" [-1 -1 2 2]
        AttributeEnd

        # Left teapot (chrome)
        AttributeBegin
          Surface "aachrome" "samples" 4
          Translate -0.35 -0.999 0.35
          Scale 0.18 0.18 0.18
          Rotate -30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

        # Right teapot (displaced matte)
        AttributeBegin
          Color [0.8 0.8 0.8]
          ShadingRate 0.25
          Displacement "disppumpkin" "freq" 9 "scale" 0.05
          Surface "matte_ptbased_globillum" 
            "globillumfile" "box_rad.ptc"
            #"causticfile" "box_cpm.ptc" "causticphotons" 100
          Translate 0.35 -0.999 -0.35
          Scale 0.18 0.18 0.18
          Rotate 30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

      WorldEnd
    FrameEnd

(When running this rib file, ignore the warning about the point cloud file not containing float _area data. The disc radii will be used instead which is fine for this purpose.)

Here is the final image with global illumination. Notice that all illumination on the ceiling (except for the bright "bouncing r" square) is indirect.


Final image with global illumination

If the lines starting with "causticfile" are un-commented, the computed image will also contain caustics caused by specular reflection of light in the chrome teapot.


6 A caustics example

The box example shown in the previous sections has caustics from the specular teapot. However, the caustics are not very distinctive. Here is a simpler example with clearer caustics. The scene consists of a textured, specular teapot on a diffuse plane.

Here is the rib file for baking the direct illumination:

    FrameBegin 1

      Format 400 300 1
      ShadingInterpolation "smooth"
      PixelSamples 4 4
      Display "teapotcaustic_bake_direct" "it" "rgba"
      Projection "perspective" "fov" 18
      Translate 0 0.75 5
      Rotate -45 1 0 0

      DisplayChannel "color _irradiance"
      DisplayChannel "vector L"
      DisplayChannel "float _area"

      WorldBegin

        Attribute "cull" "hidden" 0   # ensure illum is baked behind objects
        Attribute "cull" "backfacing" 0   # ensure illum is baked on backsides
        Attribute "dice" "rasterorient" 0   # view-independent dicing

        # Turn on ray-traced shadows
        Attribute "visibility" "transmission" "opaque" 
        Attribute "trace" "bias" 0.0001

        # Light source (white spot that bakes its own illumination)
        LightSource "myspotlight" 1
           "from" [1 1 -1] "dir" [-1 -1.5 1] "coneangle" 0.25
           "filename" "teapotcaustic_direct.ptc"

        Surface "matte"

        # Teapot (chrome, but matte in this pass)
        AttributeBegin
          Translate -0.35 -1 0.35
          Scale 0.18 0.18 0.18
          Rotate -30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

	# Ground plane
        AttributeBegin
          Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]
        AttributeEnd

      WorldEnd
    FrameEnd


Direct illumination point cloud (channel _irradiance)

Here is the rib file for baking the scattering coefficients from the textured specular teapot:

    FrameBegin 1

      Format 400 300 1
      ShadingInterpolation "smooth"
      PixelSamples 4 4
      Display "teapotcaustic_bake_scattercoeffs" "it" "rgba"
      Projection "perspective" "fov" 18
      Translate 0 0.75 5
      Rotate -45 1 0 0

      DisplayChannel "color diffrefl"   # unused in this example
      DisplayChannel "color specrefl"
      DisplayChannel "color diffrefr"   # unused in this example
      DisplayChannel "color specrefr"   # unused in this example
      DisplayChannel "float ior"        # unused in this example

      WorldBegin

        Attribute "cull" "hidden" 0   # ensure illum is baked behind objects
        Attribute "cull" "backfacing" 0   # ensure illum is baked on backsides
        Attribute "dice" "rasterorient" 0   # view-independent dicing

        # Teapot (textured chrome)
        AttributeBegin
          Surface "bake_scattercoeffs" "specrefl" [1 1 1]
            "filename" "teapotcaustic_scattercoeffs.ptc"
            "displaychannels" "specrefl"
            "spectexturename" "irma.tex"
            "texturename" "irma.tex"   # not necessary for caustic
          Translate -0.35 -1 0.35
          Scale 0.18 0.18 0.18
          Rotate -30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

        # Floor
        AttributeBegin
          Surface "constant"
          Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]
        AttributeEnd

      WorldEnd
    FrameEnd


Scattering coefficients point cloud (channel 'specrefl')

In this example, we will use a brick map for the scattering coefficients. The brick map is generated from the point cloud using the brickmake program:

  brickmake teapotcaustic_scattercoeffs.ptc teapotcaustic_scattercoeffs.bkm

Next it is time to trace photons. We'll emit 1 million photons with a distribution corresponding to the illumination baked into the teapotcaustic_direct.ptc point cloud file. The photon scattering is controlled by the scattering coefficients stored in the brick map file teapotcaustic_scattercoeffs.bkm. Here is the rib file for photon emission and scattering:

    FrameBegin 1
      Hider "photon" "emissionpointcloud" "teapotcaustic_direct.ptc"
        "emit" 1000000

      Format 400 300 1   # not necessary, but makes ptviewer display nicer
      Projection "perspective" "fov" 18   # ditto

      Translate 0 0.75 5
      Rotate -45 1 0 0

      WorldBegin

        # The name of the caustic photon map file to be written
        Attribute "photon" "causticmap" "teapotcaustic_cpm.ptc"

        # Ray tracing attributes
        Attribute "trace" "maxspeculardepth" 5
        Attribute "trace" "maxdiffusedepth" 5
        Attribute "trace" "bias" 0.0001

        Attribute "dice" "rasterorient" 0 # view-indep dicing to match ptcloud

        # Use surface scattering values in pointcloud or brickmap
        Attribute "photon" "shadingmodel"
          #"pointcloud:teapotcaustic_scattercoeffs.ptc"
          "brickmap:teapotcaustic_scattercoeffs.bkm"

        # Teapot (textured chrome)
        AttributeBegin
          Translate -0.35 -1 0.35
          Scale 0.18 0.18 0.18
          Rotate -30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

        # Matte ground plane
        AttributeBegin
          Attribute "photon" "shadingmodel" "matte"
          Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]
        AttributeEnd

      WorldEnd
    FrameEnd

The result is a caustic photon map file (called teapotcaustic_cpm.ptc) with around 196,000 photons. Displaying the caustic photon map with 'ptviewer -multiply 2000000 teapotcaustic_cpm.ptc' gives the figure below. The colored caustics caused by the texture on the teapot are clearly visible.


Caustic photon map (channel '_power')

The shaders used for rendering the final image are 'paintedchrome' (used for the textured chrome teapot) and 'matte_and_caustic' (used for the ground plane):

    surface
    paintedchrome (float Kr = 1, Ks = 0, shinyness = 50, samples = 4, blue = 0;
                   string texturename = "")
    {
      color Crefl, Cspec = 0, tex;
      float depth, s;
      float diffuseraydepth = 0;

      rayinfo("diffusedepth", diffuseraydepth);

      rayinfo("depth", depth);
      s = (depth == 0) ? samples : 1; // only 1 ray for secondary reflections

      if (Kr > 0 && N.I < 0 && diffuseraydepth == 0) {
        normal Nn = normalize(N);
        vector In = normalize(I);
        vector reflDir = reflect(In,Nn);
        Crefl = environment("raytrace", reflDir, "samples", s);
      } else { // don't reflect inside object
        Crefl = 0;
      }

      // Specular highlights
      if (Ks > 0 && diffuseraydepth == 0) {
        vector V = -normalize(I);   // view direction
        normal Nn = normalize(N);
        Cspec = specular(Nn, V, 1/shinyness);
      }

      if (texturename != "") {
        tex = texture(texturename);
        Crefl *= tex;
        Cspec *= tex;
      }

      Ci = Kr * Crefl + Ks * Cspec + color(0, 0, blue);
      Oi = 1;
    }
    surface 
    matte_and_caustic(uniform string filename = "";
                      float Ka = 1, Kd = 1, Kc = 1, nphotons = 50)
    {
      color irrad = 0;
      normal Nn = normalize(N);

      irrad = Ka * ambient() + Kd * diffuse(Nn);
      irrad += Kc * photonmap(filename, P, Nn, "estimator", nphotons);

      Ci = Kd * Cs * irrad;
      Ci *= Os; // premultiply opacity
      Oi = Os;
    }

Here is the rib file for rendering the computed caustics:

    FrameBegin 1

      Format 400 300 1
      ShadingInterpolation "smooth"
      PixelSamples 4 4
      Display "teapotcaustic_render" "it" "rgba"
      Projection "perspective" "fov" 18
      Translate 0 0.75 5
      Rotate -45 1 0 0

      WorldBegin

        # Turn on ray-traced reflections and shadows
        Attribute "visibility" "specular" 1
        Attribute "visibility" "transmission" "opaque" 
        Attribute "trace" "bias" 0.0001

        # Light source
        LightSource "myspotlight" 1   # white spot
           "from" [1 1 -1] "dir" [-1 -1.5 1] "coneangle" 0.25
        LightSource "ambientlight" 2 "intensity" 0.1

        # Teapot (textured chrome)
        AttributeBegin
          Surface "paintedchrome" "Kr" 1 "Ks" 1 "shinyness" 25
            "texturename" "irma.tex"
          Translate -0.35 -1 0.35
          Scale 0.18 0.18 0.18
          Rotate -30 0 1 0
          Rotate -90 1 0 0
          Geometry "teapot"
        AttributeEnd

	# Ground plane
        AttributeBegin
          Surface "matte_and_caustic" "filename" "teapotcaustic_cpm.ptc"
           "nphotons" 200 "Kc" 6
          Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]
        AttributeEnd

      WorldEnd
    FrameEnd

Since the caustic is spread out it is rather dim (unlike a focused caustic). For that reason we used a "Kc" value of 6 to brighten it artificially. Here is the caustic by itself and with direct illumination:


Caustic

Caustic plus direct illumination

In this example neither the caustic casting object nor the caustic receivers are displacement mapped, but they could easily have been.


7 Known limitations

The implementation in PRMan 13.5 has the following known limitations:


Appendix A: Displacement shaders

The shader 'dispmap' uses a texture map for displacement, 'dispsinz' computes a wavy displacement depending on the z position (depth), and 'disppumpkin' computes a displacement that makes a teapot look like a pumpkin.

    displacement
    dispmap (string texturename = ""; float scale = 1.0, invert = 0)
    {
      color tex;
      vector Nn = normalize(N);
      float avetex, disp;

      // Compute displacement
      tex = texture(texturename);
      avetex = (tex[0] + tex[1] + tex[2]) / 3;
      if (invert != 0) // convert bright to dark and vice versa
        avetex = 1 - avetex;
      disp = scale * avetex;

      // Displace P and calculate the new normal
      P = P + disp * Nn;
      N = calculatenormal(P);
    }
    displacement
    dispsinz (float freq = 1.0, scale = 1.0)
    {
      // Calculate displacement
      point Pobject = transform("object", P);
      float z = zcomp(Pobject);
      float disp = scale * sin(freq*z);

      // Displace position and calculate the new normal
      P = P + disp * normalize(N);
      N = calculatenormal(P);
    }
    displacement
    disppumpkin (float freq = 1, scale = 0.1)
    {
      // Convert N from current (camera) space to object space
      vector Nobj = normalize(vtransform("object", N));

      // Compute displacement
      float angle = atan(ycomp(Nobj), xcomp(Nobj));
      float disp = scale * cos(freq*angle);

      // Displace position and calculate the new normal
      P = P + disp * normalize(N); 
      N = calculatenormal(P);
    }

 

Pixar Animation Studios
(510) 752-3000 (voice)  (510) 752-3151 (fax)
Copyright © 1996- Pixar. All rights reserved.
RenderMan® is a registered trademark of Pixar.