Alex Goldberg

Features of the final product include: caustic photon mapping with projection maps, area lights and soft shadows with adaptive sampling, jittered sampling with Gaussian filtering, absorption with Beer's law, two procedural textures, and an animation of the caustic patterns. To appease those of you with a shorter attention span, I'll start by displaying one of the final images:

From left to right: 1 light sample, 15 samples, and 150 samples

Noticing that rectangles in spherical coordinates map to rectangles in CRV space, my first approach was the following:

1: Obtain the bounding spheres for each caustic object

2: Determine the circle created by projecting the bounding sphere onto the unit hemisphere at the light point by computing the spherical coordinates of the direction to the center, and the angle to the edge

3: Compute the rectangle in spherical coordinates that bounds the projected circle

4: Use the inverse uniform hemispherical sampling transformation on two opposite corners to determine the mapped rectangle in CRV space.

All well and good in theory, but after implementing it I realized that I hadn't accounted for the case where the projected circle crosses the peak of the hemisphere. At that point the theta value in spherical coordinates becomes negative, but the uniform sampling transformation from CRV space only maps to positive theta values. The only easy solution was to use 2*PI for the phi value and the maximum theta value, greatly overestimating the coverage of caustics that cross the peak. After some thinking, I settled on a less direct approach:

1: Map the center of each bounding volume to CRV space and record the radius of the projected circle on the hemisphere

2: Loop through the grid corners, apply the uniform sampling transformation to obtain the vector direction for each.

3: Determine the distance from the vector to the projected center and test whether it's less than the radius

4: If so, mark the 4 surrounding grid cells as active.

After a grid has been created, the renderer loops through each active grid cell and chooses a random location within it to cast a caustic photon. After the photon casting phase, the radiance of all photons must be modulated by the ratio of active cells to total cells. With several optimizations, the projection mapping phase with 2500 grid cells in CRV space and multiple caustic objects takes less than about 1/2 a second (estimated with the ever-accurate human clock)

Below is a simple scene with a caustic object, and next to it is an ASCII output of the corresponding projection map (in CRV space, with spherical mappings noted) for the light in the scene. This scene was rendered with 10,000 photons, but the same scene without projection mapping would have required 343,000 photons for the same coverage!

From left to right: a*c = (0,0,0), a*c = (.1,.1,.05), and a*c = (.4,.4,.1)

From left to right: A polygonal field displaced by an fBm texture (somewhat similar to how my water surface would look displacement-mapped), a multi-fractal landscape, and an animation of fBm

For those interested, the specific parameters used in the water texture were octaves=2.0, lacunarity=2.0 (the most common value in fBm applications), and increment(H)=3.0. To animate the water surface, the hit location was increased in the lateral direction a specified increment every frame.

Here's a rendering of the scene using the fBm as a texture, rather than a bump map:

This image appears in virtually every introduction to cellular & Worley noise, but I've never seen the slightest hint of an implementation detail so I decided to see if I could mimic it. After playing with it for a while, I came up with the following:

+User Parameters: Low (grout) color, High (tile) color, low threshold, high threshold.

+Base texture: The flat texture itself is an implementation of Worley noise (duh), where the F2-F1 values are basically interpreted as the height of the tiles at each point. Any height under the low threshold is the 'grout', and is evaluated as the low color blended with Perlin noise. Any value above that gets the high color.

+Coloring: To create the varying colors, the high color is altered by a small percentage calculated using random numbers seeded by the index of the nearest point in the cellular grid(to ensure identical coloring per tile)

+Shading: The same cellular texture is evaluated as the bump map, with the added exception that any points above the low threshold and below the high threshold return a linear interpolation between the low and high colors.

+ =

+Consistency: To allow re-use of the same texture, I also added a parser parameter to output the random cellular grid values to a file, and a similar one to read them back in. Here are a few more samples with varying thresholds/colors:

+Displacement mapping of the fBm water texture for a more convincing water surface and interreflections

+A better projection mapping algorithm allowing for different bounding volumes (OABBs/AABBs for instance)

+Volumetric photon mapping using ray marching for beams of light in the water

+Optimization strategies to allow re-use of parts of the caustic photon map from frame-to-frame

+Bumpmapping of the tile surfaces for tiles looking less-new.