GDC 2001: Implementing Multi-colored Volumetric Fog Without Using up Texture Stages

Volumetric Fog Raycasting

We will distinguish three possible implementations, ranging in difficulty: when there is only one fog volume, when there are several separate fog volumes, and when there are several interpenetrating fog volumes.

Only One Fog Volume

If we make a scene where there is a fog volume, defined by its volume (which we use to calculate t1 and t2), density (volFogDensity) and color (volFogColor), the technique for rendering is quite obvious:

1. Loop for each vertex with world position V.

    2. Calculate D = VO.3. If the ray intersects the volume.

    4. Calculate the points where the fog begins and ends: t1 and t2, where t1 < t2.

    5. Clamp those values between 0 and 1.

    6. Calculate distance = magnitude( D * (t2 – t1) ).

    7. Else.

    8. Set distance = 0.

    9. End if.

    10. Calculate volFog = 1 - clamp01( distance * volFogDensity )

    11. Set fog = volFog. Set fogColor = volFogColor.

12. End loop.

Several fog volumes

Now, the problem comes when trying to mix different types of fog, or more than one fog volume. In this case, we will have to sort all the (t1, t2) ranges for the different fog volumes, and apply them in front-to-back order:

1. Set fog = 1, fogColor = (0, 0, 0)

2. Loop for each fog volume.

3. Calculate volFog and volFogColor as indicated above.

4. Set fogColor = volFogColor * fog + fogColor * (1 – fog)

5. Set fog = fog + volFog – 1.

6. If fog <= 0.

7. Set fog = 0.

8. Stop the loop.

9. End if.

10. End loop.

Sorting front-to-back allows us to have an early exit in case a volume completely fogs out what it has behind.

The formulas from points 4 and 5 come from applying the normal fog formula to the new fog volume:

Result = color*fog + fogColor*(1–fog)

and adding the fog coefficients:

fog = 1 – [ (1 – fog) + (1 - fog) ]

to ensure that the fog stays linear with the distance across the different volumes.

Interpenetrating Fog Volumes

The most complex setup happens when two fog volumes share a portion of the range between the observer and the object being rendered.

Figure 6. Interpenetrating fog volumes.

In Figure 6 we see what’s probably the most common case where interpenetrating happens: a fog volume that is inside of the distance fog. In this case, there’s no way to properly sort the two distinct volumes. It’s not possible to assert that the volume is "behind" or "in front of" the distance fog.

Intuitively, in this case the solution lies in sorting three ranges (t21,t11), (t11,t12) and (t12,t21), which can indeed be sorted without problems. For the first and third ranges, the color and density values are taken from the distance fog, but for the range where the fogs get mixed, we will have to properly mix the values for both fogs. A very intuitive way to mix them, which is the one I’ve used, is by calculating the color of the mix using a weighted average on the relative densities. So, if volume 1 has twice as much density as volume 2, then the resulting color should be (2*vol1FogColor + vol2FogColor) / 3. This is achieved with the formulas:

mixFogDensity = vol1FogDensity + vol2FogDensity
mixFogColor = (vol1FogDensity*vol1FogColor+vol2FogDensity*vol2FogColor) /
/ (vol1FogDensity + vol2FogDensity)

Of course, the thing can become pretty complicated really quickly, as shown in Figure 7. So, how can we generalize this to many volumes?

Figure 7. Complex case of multiple interpenetrating fog volumes.

The algorithm I found makes this task pretty easy and fast. It consists in having an array of (t, fogColor, fogDensity) tuples, and filling it in with proper values for the entry and exit points of the visibility ray into the different fog volumes:

1. Loop for each fog volume.

2. Calculate t1 and t2, and clamp them to the range (0, 1).

3. Add to the array: (t1, volFogColor*volFogDensity, volFogDensity).

4. Add to the array: (t2, -volFogColor*volFogDensity, -volFogDensity).

5. End loop.

6. Sort the array on ascending values of t.

7. Initialize accumDensity = 0 and accumColor = (0, 0, 0).

8. Set fog = 1, fogColor = (0, 0, 0) and lastT = 0.

9. Loop for each entry in the array.

10. If accumDensity is greater than 0.

11. Set distance = (entryT – lastT) / magnitude(D).

12. Calculate volFog = 1 - clamp01( distance * accumDensity ).

13. Calculate volFogColor = accumColor / accumDensity.

14. Set fogColor = volFogColor * fog + fogColor * (1 – fog)

15. Set fog = fog + volFog – 1.

16. If fog <= 0

17. Set fog = 0.

18. Stop the loop.

19. Endif.

20. Endif.

21. Set accumDensity = accumDdensity + entryDensity.

22. Set accumColor = accumColor + entryColor.

23. Set lastT = entryT.

24. End loop.

The sorting can be done very fast because the list is always likely to be already sorted. A bubble sort will do very nicely. The distance fog values are the ones that can easily be out of order, so it’s best to add them after the list has been sorted, using insertion sort.

Implementation

Implementation details will be discussed in the lecture during the GDC. Also, demos will be shown, demonstrating the different features and problems that this technique presents, and a short study on how some of this could be implemented using DirectX 8 vertex shaders.

Also, alternative math formulas and adjustments will be discussed, with the aim of improving the visual quality and reducing the impact of visual artifacts.

All demos, slides and additional material will be put available for download after the conference.

Conclusion

The volumetric fog technique presented is an interesting application of the concepts of raycasting, renderer interpolators and 3D geometry math, and the range of effects that can be achieved is very encouraging.

Nevertheless, the technique isn’t free of problems. The most important, visually, is that small fog volumes can reveal the polygon mesh layout along their edges. In order to avoid this, the fog volumes must be sensibly larger than the polygons rendered. Also, computationally, this technique puts a heavy strain on the vertex processing pipeline. This can make it unsuitable for some purposes.

In any case, the technique is proven (it was used in the Ripcord Games title "Armor Command") and definitely worth studying in detail.

Discuss this article in Gamasutra's discussion forum

_______________________________________________________

Introduction