How to achieve light that changes color mid-way?

I thought of creating light sources, and some colored windows. Now, the windows are semi-transparent. How could I make it so that when the light (say, pure white) hits the glass and continues through it, but changes the color to the same color as the glass it passed?

I know the effect described here can be faked by using area lights on the "colored" side of the window, but what if I just wanted to have one white point light?

-
Instead of changing the light's colour, could you change the base-colour of the illuminated objects on the other side of the glass? maybe that would do the trick? – HumanCatfood Feb 7 '13 at 14:40
The transparent window is rendered as a second pass, its shader simply modifies the color balance of each pixel output. Would this not work? – Patrick Hughes Feb 7 '13 at 16:39
It seems to me that this is analogous to the problem of shadows; if you have a nice algorithm for shadows cast by semi-transparent objects (I don't know whether that's practical), then you just need to run it for three parallel color channels. – Kevin Reid Feb 7 '13 at 19:23

There are a huge number of ways of doing this. These will require the use of a shader and I am presuming that you are already doing per-pixel lighting. The following are some suggestions, however finding the technique that's right for you might take a lot more research.

Quick and Dirty

You can specify bounding boxes which define interior areas. If the light is outside the boxes, but the geometry (which is not in shadow) is inside the box, then the light must have been affected by passing through a window (this is also true if the geometry is outside the box and the light is inside). The only issue here is how to pass the box information to the shader and how state the effect on the light.

Another option is to specify windows as objects lying on planes. First test to see if the geometry and the light are on opposite sides of the plane and then if the way between them intersects the plane at a point within the boundaries of the window. This would be more accurate than the first method and it would be easier to have windows into the same interiors with different colors of glass.

Keep getting more detailed with the geometric representation of the windows and you will get more accurate results, but the computation will also get heavier.

These techniques would work reasonably well for a corridor shooter, but not so well for a dynamic or open world as a lot of tweaking would probably be required to get it looking right without too many artifacts. Also, these techniques could quickly become a little intense, so moving to a deferred shading pipeline would be advised.

Another option is to do something similar to shadow mapping.

In shadow mapping you generate some bitmap image of the world affected by a light. Each pixel, while still a color, is actually the distance of the nearest piece of non-transparent geometry (you use the four bytes for 1 float instead of 4 colours). If you calculate the distance from the light to a piece of geometry and it's further than the corresponding value in the shadow map then your piece of geometry is in shadow (you generally use the ray between the geometry and the light to index the map).

If you apply this idea to your problem then what you would do is store a map of the distance from the light of the nearest piece of transparent geometry, and then also make a second map of the color of the light after passing through that geometry (simply the color of the geometry if the light is white).

If your piece of geometry is further away than the distance in this map, then use the map color, if it's not then use the original light color.

The function for calculating the color for each pixel in the map should roughly be; lightColor - invertedWindowColor. So for a pure white light and a pure red window which absorbs none of the red spectrum we get; (255,255,255) - (0,255,255) = (255,0,0). So the color of light on the other side is pure red. For a more complex transparent object, such as a stained glass window, you might want to have a texture lookup to get the material color.

If you are looking for something similar then check out Reflective Shadow Maps.

This technique offers a great amount of fidelity and would possibly be the best if you wish to use complex transparent geometry such as stained glass windows.

Attempting a General Solution

It has recently become popular to encode lighting information in a voxel representation (they're not just for geometry). The latest crytek engine uses this kind of stratagy for advanced lighting (light propagation volumes).

Here is the general idea:

• Create a map of evenly spaced cubes which encompasses your scene (consider using z-order curves).
• Find a way of storing incident lighting information for each voxel (spherical harmonic representations are useful here).
• Find the voxel containing a light and represent that light in the voxel.
• Propagate the light outwards through the adjacent voxels (kind of like minecraft water)
• Calculate how the geometry in each voxel will affect the light passing through it (absorb, reflect, transmit)
• repeat until the light has faded

There are many ways of building this voxel information, but the list above gives a general idea. For example; you can start by generating shadow maps/volumes and then projecting that information into the voxel map so you can quickly make the map of direct lighting. Then you start the propagation from the voxels along the edge of the affected area. Keep in mine that, in this case, you will want to ignore whether geometry is transparent or not when generating the shadow map/volume.

In the final pass of a deferred renderer, when calculating the illumination of a point of geometry you simply use the geometry position to index your voxel map to find out what the incident lighting is like at that point in space. Then you can build a screen map of incident lighting cpu side, or possibly do this with cuda.

-
Thanks for the lengthy post! I'm currently investigating whether I'll be doing forward or deferred rendering for the current project I'm working on. At the moment it's probably going to be deferred. How would you do when the window is multi-colored (say, a stained glass in a church)? – manabreak Feb 7 '13 at 19:27
@manabreak - I would use the shadow map extension as it would offer the best level of fidelity for that case. I will edit the post to explain this. – OriginalDaemon Feb 7 '13 at 19:44
Gee, thanks! This post really made my day, I've probably never seen so much effort put into any single post. – manabreak Feb 8 '13 at 5:16