In this update on River's End (previously Rox 3D), I'll be talking about the process by which I arrived at a lighting model for the paper lanterns that feature prominently in my new game.
Early on, I decided I wanted to blur the line between realism and style for this title. Obviously, any game featuring ghosts and river spirits doesn't really necessitate rigid adherence to the laws of physical light, but for the lamps I wanted something both neat-looking and recognizable. I began my research by looking at about 200-300 pictures of Toro Nagashi festivals, and hanging Chochin (paper lanterns). This process took about 2 or 3 hours to find enough subject matter, at which point I brought out the graph paper and started to sketch down notes.
The key elements were:
- There is a view-dependant "hotspot" of light where the actual candle or light bulb sits in the lamp. It diffuses across the surface and creates a specular-like highlight.
- The “hostspot” falls off quickly relative to the distance between the candle and the paper surface. This means that in a cube with a centered candle, the hotspot effect would be weakest at the corners.
- The paper in the lanterns are effectively emissive as well as picking up color from other light sources in the scene. The effect is so subtle though, that without a really bright lightsource, it's almost unnoticeable.
- The paper has a very noticeable cloudy texture in some cases. In others it’s an anisotropic effect of fins like accordion blinds. This would require two separate approaches do differentiate those lamps.
This post talks about the shader used for "faceted" lamps, or lamps without curved surfaces.
I had to reduce my observations to equation form. My final lamp shader has variety of uniform data, but initially, the falloff + view dependant relationship of the hotspot was my primary interest.

As we see from this poorly constructed diagram that I made with Paint.Net, there are some obvious needs for uniform data. First we need to consider view and light position, adjusted for world coordinates.
First lets concentrate on light falloff. That can be expressed in terms of the pixel’s distance from the center. Since I’m working in Shader Model 2.0, and I want to get my pixel’s position in world coordinates, I have to pack the information into my vertex shader. In my shader code, that looks like this:
float4 worldPos = mul(float4(input.Pos, 1), world);
worldPos = worldPos / worldPos.w;
output.WorldPos = worldPos;
All I’m doing is transforming the vertex position into world coordinates, dividing out W (since I want coordinates in the form of (x,y,z,1)), and setting it in the output. Now the interpolator does the rest. I’m passing the WorldPos output using the TEXCOORD semantic, so the world space position will be properly represented at the pixel.
Now on to the pixel shader where the real work is being done. The next set of functions are used to calculate a light “intensity” on the paper. This is where the light falloff as a function of distance is represented.
float3 lightDelta = input.WorldPos.xyz - worldLightPos;
float lightIntensity = 1 - (saturate(pow(length(lightDelta), lightPower) * lightBrightness));
The lightPower and lightBrightness parameters give me a means to control the rate of falloff with distance. That way I can turn up the lightPower to get a more focused light, and increase lightBrightness to get a larger hotspot.
Now the last part of equation factors in the view-dependant portion of the lighting function. This function is not really based in reality – there’s no conservation of energy, I’m not calculating a proper light-scattering equation based on the light’s luminosity. This is a very unrealistic function that produces some spectacularly realistic results though, and that’s good enough for my purposes.
float3 worldLightDir = normalize(lightDelta);
float4 paper = tex2D(smp0, input.TexCoord);
float4 color = (dot(input.ViewDir, worldLightDir) * lightIntensity * paper * lightColor)
+ (.5 * lampColor ) * paper;
It’s not pretty, and the (.5 * lampColor * paper) is a contrivance. I’ve built a more “realistic” function to represent the scattered light, but the results have been unsatisfying unless the parameters to the equation are about .5 * lampColor for each pixel anyways. The paper texture I’m using is something I whipped up in Paint.Net using the Clouds and Pencil effects in a matter of about 5 minutes.
And now the proof; without seeing the effect in action, it’s tough to get a good idea of how it looks. I have provided a high-res screenshot though that should give some idea as to how the lighting plays out. I'm only using cubes as I haven't finished the look of the spheroid lamps yet.
Bloom Disabled
Bloom Enabled
I’m very happy with this part of the game's visuals now. The next big visual elements are my particle engine, my sky, and my “fog walls” which I hope to be demonstrating in my next post.