Radiosity Using Photon Maps
James L. Bigler - Peter Shirley - Steven G. Parker

Abstract

The effect of global illumination is necessary for realistic images. In this paper we explain a method of generating photon maps that can later be used to texture surfaces. These texture maps are generated by tracing the light emitted from light sources and recording where the light is absorbed. This enables the textures to have all the illumination properties of shadows and global lighting encoded in them.

Background

We will assume the reader has a basic knowledge of how to implement a simple ray tracer with parallelogram primitives. We also assume an understanding of what global illumination is as well as how to implement texture mapping to view the results.

Global illumination is achieved by tracing the path of rays emitted by the light source and keeping track of where the light went. As light strikes a surface, energy from the light is absorbed. If the surface is broken into smaller regions we can keep track of how much light hits each region. These regions correspond to the texels of the photon map.

As a post process operation, we normalize the amount of light in each texel by the area. We also normalize by the scene's maximum light value to give the brightest texel the value of 1 or the brightest color representation. The texels are then written out to image files to be used as texture maps in a rendering system.

Implementation

The implementation can be thought of in three main parts:

  1. Tracing the light emitted from the light source
  2. Keeping track of how much light hit each surface
  3. Generation of the texture maps to be used in a rendering system

Tracing the light emitted from the light source

From the light source you can generate a light ray that has both direction and color. You may keep track of the color as a RGB unit or trace each color independently. Using the ray from the light you compute the intersection in your scene. Once computed, you contribute light to the corresponding texel of the surface. Since the objects in the scene absorb light we need to determine when the light should no longer contribute. We compute the probability of terminating the ray, by using the components of the light color, probability = (red + green + blue)/3. We then take a random number (r) from [0..1] and if the probability is less than r we terminate the ray. One could also compute probability by using probability = max (red, green, blue).

If the ray is not terminated, the ray continues and two things must be calculated, the new direction and color. The direction is computed using the normal of the surface and a random direction in the hemisphere made by that normal. The color is computed using the product of the surfaces' color with the light's scaled by the dot product of the incoming ray with the intersecting polygon (New_Color = RGB(light.r * surface.r, light.g * surface.g, light.b * surface.b) *-light.direction . normal).

This process is repeated until the light is terminated. When the light is terminated another ray from the light source is generated.

Keeping track of how much light hit each surface

The light is kept track of using a two-dimensional array corresponding to the u-v parameterization of the surface. Any surface may be used provided a u-v parameterization exists and the area per texel is known.

All surfaces in the scene are apportioned a 2D texel map initialized to 0. When an intersection is obtained the corresponding texel's value must be incremented by the per color product of the lights color with that of the surface's.

Generation of the texture maps to be used in a rendering system

Once a predetermined number of rays from the light sources have been traced, the process of texture creation can commence. Because the maximum value of a texel can depend on the number of light rays traced in a scene, the values of the texels must be normalized by the maximum of each color. Looping over the set of texels in the scene the maximum values for each color component is determined. A second pass is done to normalize each color value with its corresponding maximum. During this process the value of the texel is also divided by its area.

The texture maps can then be saved or used appropriate for the rendering system. We chose to use the real time ray tracer developed by Steve Parker and Pete Shirley, because of the capabilities of using large amounts of texture memory. The files were saved on a per polygon basis and read in on visualization start up.

Results

Only rectangular primitives were used in the creation of our scenes. As stated earlier other primitives with adequate u-v parameterizations and known texel areas such as triangular meshes or NURBS could be used.

As this is a scene that depends on even distribution of random rays, quality of the textures increases with the number of rays per texel. This helps reduce the speckling seen below. For decent results 100 to 1000 rays per texel is recommended.

10 rays per texel 100 rays per texel
1000 rays per texel

Even though this is an "offline" preprocess it can be quite time consuming to generate these texture maps. Using a naive implementation on an Origin 3000 it took 240 processing hours to trace 100 billion rays with a scene that had 2.2 million texels. Some sample images are below.

More images for caparison can be found here.

Conclusions

This is straightforward implementation for global illumination. Unlike computing the form-factor which scales O(n^2) based on the number of discrete surfaces, this approach scales by the cost of computing intersections with the primitives. By using ray tracing optimizations such as a bounding hierarchy, the scaling can be O(n log n).

One of the drawbacks to this implementation is the cost of texture memory. With relatively complex scenes the use of OpenGL texture memory becomes impossible as the amount of texture required surpasses that in memory. For this reason we chose to use the real time ray tracer, because texture memory was only limited by main memory.