Photon Mapping for General ShadersJune 2007 |
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.
In order to control the emission and and tracing of photons, we first need to bake the the illumination and surface colors.
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":
|
|
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.
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:
|
And here are the diffuse and specular reflection coefficients of the generated point cloud "box_scattercoeffs.ptc":
|
|
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.
The next step is photon emission and tracing. The photon emission and tracing is guided by the point clouds baked in section 2.
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.
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"].
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):
|
|
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:
|
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:
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.
|
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.
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
|
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
|
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.
|
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:
|
|
In this example neither the caustic casting object nor the caustic receivers are displacement mapped, but they could easily have been.
The implementation in PRMan 13.5 has the following known limitations:
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
|