Caustics are light patterns that are created when light from a light source illuminates a diffuse surface via one or more specular reflections or transmissions.
In figure 19 you see a very simple geometry, a cylinder, being modified by a displacement shader to create an interesting pattern of caustics on the floor. The displacement shader for RenderMan is quite simple:
/* myDisplacement */
displacement
myDisplacement(float factor = 50)
{
float amp = 0.01 * sin(s*factor*PI);
P += amp * normalize(N);
N = calculatenormal(P);
}
Because ray tracing, global illumination, and photon mapping are
relative new features of Pixar's RenderMan implementation PRMan there
is no general documentation how to add the new features into the the
standard interface specified by the The RenderMan Interface.
Other renderers like BMRT had GI implemented for a long time and they
added their own extensions to the standard interface to deal with the
new features. Therefore I can't show how to create caustics with all
the RenderMan compatible renderers which do implement their own
method. I made a test with PRMan 11.0 which can be found in the
documentation of the RenderMan Artist Tools (RAT) but this
was a very basic test that caustics can be rendered with PRMan and for
the more advanced test I used AIR. However PRMan has a very
nice program called ptviewer which can be used to interactively
look at a photon map to rotate it and zoom in and out. The basic
approach is to render first a pass to create a photon map and store it
before you render the final image in the second pass.
So the main difference between the RIB file for the first pass and the RIB file used for the second pass is:
Hider:
Hider "photon" "emit" 1000000
This line defines how many photons are emitted into the scene. The following line defines that the photons are used for caustics and the filename where to store it:
Attribute "photon" "causticmap" "prman11caustics.cpm"
The same line is used in the second pass to read the photon map.
Instead of using a surface shader in the first pass an attribute is defined for the ground plane which receives the photons:
Attribute "photon" "shadingmodel" "matte"
For the geometry refracting or in this case reflecting the photons you define a similar attribute:
Attribute "photon" "shadingmodel" "chrome"
Unfortunately this ``shading models'' don't give you the freedom to use real shaders which specify how much photons are reflected or refracted, how many are absorbed or which additional parameters would influence the direction where the photons go.
LightSource "causticlight" 2
This defines an additional light source which is used to add the effect the photons have on the caustics. Beside of defining the filename for the photon map a few additional commands are used to make the objects visible to rays, for shadow creation -- basically ray tracing is activated with that -- and to define a number which is used for collecting the photons nearby.
Attribute "visibility" "trace" 1 # make objects visible to rays Attribute "visibility" "transmission" "opaque" # for shadows Attribute "photon" "causticmap" "prman11caustics.cpm" Attribute "photon" "estimator" 200
The ground plane is now using a real surface shader called
matte which has a diffuse component:
Surface "matte"
The geometry reflecting the photons gets a simple reflecting surface
shader called simplemirror which is taken from the
documentation coming with the RenderMan Artist Tools (RAT):
surface simplemirror ( )
{
normal Nn = normalize(N);
vector In = normalize(I);
color reflection = Cs;
if (Nn.In < 0) {
vector reflDir = reflect(In,Nn);
reflection = trace(P, reflDir);
}
Ci = Os * reflection;
Oi = Os;
}
This shader uses the trace function to shoot a ray into the
reflect direction and therefore ray tracing has to be
activated.
As I said before I did use AIR for the other tests and created a very similar scene to the one coming with the RAT documentation:
FrameBegin 1
Format 400 300 1
PixelSamples 4 4
ShadingInterpolation "smooth"
Display "aircaustics.tiff" "file" "rgba"
Projection "perspective" "fov" 22
Translate 0 -0.5 8
Rotate -40 1 0 0
Rotate -20 0 1 0
Option "render" "max_raylevel" [4]
Option "model" "float unitsize" [0.001]
Attribute "trace" "bias" [0.05]
WorldBegin
LightSource "caustic" 1
Attribute "light" "string shadows" "on"
Attribute "light" "integer nphotons" [ 100000 ]
LightSource "spotlight" 2 "from" [-4 7 -7] "to" [0 0 0]
"intensity" 100 "coneangle" 0.2
Attribute "render" "string casts_shadows" ["opaque"]
Attribute "visibility" "integer shadow" [1]
# Ground plane
AttributeBegin
Attribute "caustic" "float maxpixeldist" [ 20 ]
Attribute "caustic" "integer ngather" [ 75 ]
Attribute "visibility" "integer camera" [ 1 ]
Attribute "visibility" "integer reflection" [ 1 ]
Attribute "visibility" "integer shadow" [ 1 ]
Attribute "visibility" "indirect" [ 1 ]
Surface "matte"
Color [1 1 1]
Scale 3 3 3
Polygon "P" [ -1 0 1 1 0 1 1 0 -1 -1 0 -1 ]
AttributeEnd
# Box
AttributeBegin
Attribute "caustic" "color specularcolor" [ 0.4 0.4 0.4 ]
Attribute "caustic" "color refractioncolor" [ 0.0 0.0 0.0 ]
Attribute "caustic" "float refractionindex" [ 1.0 ]
Color [1 1 0]
Attribute "visibility" "integer camera" [ 1 ]
Attribute "visibility" "integer reflection" [ 1 ]
Attribute "visibility" "integer shadow" [ 1 ]
Attribute "visibility" "indirect" [ 1 ]
Translate 0.3 0 0
Rotate -30 0 1 0
Surface "simplemirror"
Polygon "P" [ 0 0 0 0 0 1 0 1 1 0 1 0 ] # left side
Polygon "P" [ 1 0 0 1 0 1 1 1 1 1 1 0 ] # right side
Polygon "P" [ 0 1 0 1 1 0 1 0 0 0 0 0 ] # front side
Polygon "P" [ 0 1 1 1 1 1 1 0 1 0 0 1 ] # back side
Polygon "P" [ 0 1 0 1 1 0 1 1 1 0 1 1 ] # top
AttributeEnd
WorldEnd
FrameEnd
The main difference between AIR and PRMan is that you can create the caustics in a single pass. Here in short the steps necessary to produce caustics with AIR. This is taken from the documentation coming with AIR:
LightSource "caustic" 1
Attribute "light" "integer nphotons" [ 100000 ]
Attribute "caustic" "integer ngather" [ 75 ]
and the maximum distance (in pixels) for gathering photons:
Attribute "caustic" "float maxpixeldist" [ 20 ]
Larger values produce smoother results and require that fewer photons be used overall.
Attribute "visibility" "indirect" [ 1 ]
Attribute "caustic" "color specularcolor" [ 0.4 0.4 0.4 ] Attribute "caustic" "color refractioncolor" [ 0.0 0.0 0.0 ] Attribute "caustic" "float refractionindex" [ 1.0 ]
The specularcolor and refractioncolor attributes determine what happens to photons that intersect a surface. Photons will be reflected, refracted or absorbed in proportion to (respectively), the average intensity of specularcolor, the average intensity of refractioncolor, and 1 minus the sum of the average intensities. The sum of specularcolor and refractioncolor should be less than or equal to 1. If the sum is greater than or equal to 1, no photons will be stored on that surface and no caustics will be visible.
In AIR photons can also be saved to a file in the first pass and reused in a second pass. It's in your responsibility to make sure that no photons are emitted in the second pass.
I managed to render a very similar picture to figure 19 with AIR. Nevertheless I have the feeling that mental ray's control over how many photons are considered within a certain radius is more powerful if it comes to creating fine details in the caustics pattern. AIR is measuring a similar distance in pixels which makes it harder to work on details which might be less than a pixel.
Before we talk about how to create caustics with mental ray I will give you the displacement shader for it:
/* myDisplacement.c */
#include <shader.h>
struct myDisplacement
{
miScalar factor;
};
DLLEXPORT miBoolean
myDisplacement(
miScalar* result,
miState* state,
struct myDisplacement* paras
)
{
miScalar factor;
/* get parameters */
factor = *mi_eval_scalar(¶s->factor);
/* result */
*result += (0.01 * sin(state->tex_list->x * factor * M_PI));
return(miTRUE);
}
It shouldn't be a problem to modify the Makefile I have shown earlier to compile this shader. To create the corresponding MI file where the shader and it's parameters are declared should be easy as well. Let's focus on the relevant parts of the MI file which was used to render figure 19:
options "#opt"
...
caustic on
caustic accuracy 700 .05
caustic filter cone 1.1
photonmap file "mrphotonmap.cpm"
photon trace depth 4 4
...
end options
Further options give finer control over the process and help to
fine-tune the result. By writing the photon map to a file it is
possible to create once a photon map with a lot of photons and change
later only parameters which do not effect the process of storing the
photons. This process takes a while and will be done before the actual
ray tracing of the scene is done. Because of it's nature of being a
post-process it's worth to make first a decision about the number of
photons to emit and the energy used in the lights by simple tests
without fine-tuning. Once that decision is made you can reuse the
photon map and play with the parameters like caustic accuracy.
energy statement:
light "#/obj/light1_obj"
"physical_light" (
"color" 1000.0 1000.0 1000.0,
"cone" 0.9867880932509171
)
origin 0 0 0
direction 0 0 -1
spread 0.96891242171064473
energy 1000.0 1000.0 1000.0
caustic photons 5000000
end light
It is advisable to use a light shader like physical_light that
ensures physical correctness. The energy is distance-dependent and
often has to be chosen quite large.
material "#mtl0"
"dgs_material" (
"diffuse" 0.8 0.8 0.8,
"lights" [ "/obj/light1" ]
)
photon "dgs_material_photon" ()
end material
...
material "#mtl1"
"dielectric_material" (
"ior" 0.8,
"col" 1.0 0.0 0.0,
"phong_coef" 0
)
displace "myDisplacement" ("factor" 60)
photon "dielectric_material_photon" ()
end material
Photon shaders may store and either absorb, reflect, or transmit photons.
To generate caustics more efficiently, objects can be flagged such that the photons are only emitted toward certain objects and stored only on selected objects. Objects are then divided into caustic-casting (caustic 1 flag) and caustic-receiving (caustic 2 flag), or both (caustic 3 flag), or neither (caustic 0 flag).