Equirectangular Diffuse Transform DLL: Equirectangular Diffuse Transform.zip
The user interface:
Summary: The Texture Shader has a mapping mode called Reflection Map (Equirectangular) which allows a panoramic image stored as an equirectangular (ER) map to be reflected onto a height map. Equirectangular Diffuse Transform provides a method by which the panoramic image can also be used to apply diffuse lighting consistent with the same image. The basic method is to use the original panoramic image to produce the reflection, then use the same image transformed with Equirectangular Diffuse Transform to produce the diffuse lighting, using the Texture Shader's Gradient Map (Equirectangular) mapping mode. The offset and rotation of the Texture Shader's clipboard image should remain the same for the two steps, and normally the light source should be Ambient, without Directional light.
The Help menu description:
Equirectangular Diffuse Transform transforms an equirectangular environment map into an equirectangular diffuse map that can be used to apply diffuse lighting to a height map. The effect uses the first five bands of spherical harmonics to approximate convolution with a clamped cosine function covering a hemisphere.
The resulting diffuse map map can be used by the Texture Shader effect, with the Clipboard Image Mapping Method set to Gradient Map (Equirectangular). Typically, the Directional Light Intensity should be set to zero, and the Ambient Light Color adjusted to determine the light intensity.
The width of an equirectangular map is normally twice the height, but any image will be treated as though it covers the entire sphere.
The controls are:
Pre-Scale Brightness to Maximum Range: When enabled, the brightness of the diffuse map is automatically adjusted so that the the maximum color is as bright as possible without overflowing. This provides the largest brightness range when shading.
When disabled, if Expansion Gamma equals Compression Gamma Reciprocal, the color of a constant-color map is unchanged when transformed; if Expansion Gamma does not equal Compression Gamma Reciprocal, a constant-white map is unchanged when transformed.
Brightness: Adjusts the brightness of the diffuse map. Brightness values greater than 1.0 should generally not be used when Pre-Scale Brightness to Maximum Range is enabled, since they will result in color overflow, and therefore clamping.
Expansion Gamma: The exponent to which the color components are raised before being transformed. When equal to Compression Gamma Reciprocal, this can be used to compensate for gamma compression. Gamma is typically around 2.2. When used with Compression Gamma Reciprocal set to 1.0, it provides a contrast adjustment for the pre-transformed map; increasing the upper-end contrast when greater than 1.0, and decreasing the upper-end contrast when less than 1.0. This can be useful for emphasizing the lights in the map over the background colors.
Compression Gamma Reciprocal: The reciprocal of the exponent to which the color components of the diffuse map are raised.
Link Expansion and Compression Gammas: When enabled, Expansion Gamma will always match Compression Gamma Reciprocal.
Original equirectangular map:
Transformed map (with gamma of 1.0):
Though I think theoretically using a gamma of 2.2 makes sense, the example above uses a gamma of 1.0. Until recently, I began to doubt the gamma control was very useful, except as a contrast adjustment; but now I'm beginning to think using a gamma of 2.2 might be better.
The plugin was originally inspired a a series of articles called Spherical Harmonics and applications in real time graphics. If you compare my transformed image to the article's, you'll see they are different. Part of the difference is that by default I scale the brightness to make the image as bright a possible, without overflowing. There are, however, other differences, and I don't know why. However, I've done many tests that pretty much convince me that my result is correct. The tests include writing a plugin that (very slowly) does a full convolution with a clamped cosine function. Though the algorithm was completely different, the results were almost identical. I wrote a fairly detailed comment about the differences on the article's website, which was flagged as spam by the blankety-blank spam filter. Unfortunately, the article is quite old, so the author hasn't responded to my earlier comments.
The ER map used for shading should generally represent light sources, not colored areas. If, for instance, the ER map has a large expanse of grass, the diffuse shading will be very green, even though the grass would actually emit, through reflection, only a limited amount of green light onto an object. In some cases, it would be desirable to adjust the map's contrast to emphasize the lights over other areas. (This can be done by setting the expansion gamma to a larger value than the compression reciprocal.) It may also be useful to decrease the map's color saturation, to reduce the effect when shading colored surfaces.
To keep my example simple and direct, and avoid confusing detours, I didn't make any post-shading modifications to the diffuse-shaded layer. However, in most cases I would try adjusting the brightness, contrast, saturation, and such to achieve the best result.
On the off chance another programmer wants to use my transform code for some other purpose (such as computing the spherical harmonic coefficients) , as with all my PDN code, I'd be happy to provide it.