I knew about volumetric fog, but had never seen it in real time. Two weeks ago I have seen Unreal for the first time, and the fog effects impressed me. Then I saw screen shots of the upcoming Quake III : Arena. Since then, I want volumetric fog. I had no clues how to do it with a portal/cell world. That is, until I wrote down the fog equation on a sheet of paper.
Volumetric fog model
To use the word "volumetric" to describe fog is redundant: the visual appearance of fog is due to particles in suspension, and those particles occupy a volume in space. The more dense the particle distribution (i.e.: the more there is particles per area of space), the less you can see through the fog. The particles in suspension is what is blocking the light, and the fog color is nothing more then the particles color. If the particles are red, you see red fog. The thicker the fog volume is, the less you can see (more particles in the way). Effectively, the fog intensity (or the number of particles) visible is a function of the fog volume depth.
There is three well know functions to model fog: linear fog, exponential fog and exponential squared fog. For reasons that will come to light soon, I will only consider linear fog.
The linear fog function specify that the fog intensity is directly proportional to the depth of the fog volume:
I = density * ( Zmax - Zmin )
where I is the fog intensity at a point, density is just that, the fog density, and Zmin, Zmax defines the depth of the fog volume.
To blend the fog color with a polygon, the equation is always the same:
C = I * F + ( 1 - I ) * P
where C is the final color, I then fog intensity, F the fog color and P the polygon's color.
This equation can be rendered using alpha blended Gouraud shaded polygons. Simply replace I with alpha in the above equation and use the fog color as the color for the Gouraud shaded polygon. When I say Gouraud shaded polygon here, I mean that the alpha is interpolated linearly, not the color which is constant.
Fog in cells
Basically, each cell have an fog density and a fog color. If a cell is to have no fog at all, it's fog density is set to 0. To render the fog volume, we need to compute the intensity I at each vertices, and for this, we need to find Zmin and Zmax.
When we render through a portal, we have to compute the plane equation in view space for that portal before entering the fogged cell. This plane equation coincide with the beginning of the fog volume (the cell's hull is the fog volume).
Given the plane equation of the portal ( in view space):
Ax + By + Cz + D = 0
We easily obtain:
Zmin = Z = - ( Ax + By + D ) / C
Similarly, for any polygon behind the portal, we take it's plane equation in view space to obtain:
Zmax = Z = - ( Ax + By + D ) / C
So for each polygon rendered through a portal, we need to compute alpha = density * ( Zmax - Zmin ) for each vertices. A fog polygon is then generated using the alpha values and rendered on top of the original polygon. If a hardware device directly support linear fog, we don't even need to render the fog polygons in a second pass. If the hardware doesn't support linear fog, then we have to use alpha blending in a second pass.
The reason I use linear fog is that you can use a Gouraud shaded polygon to render the fog polygons. Another reason is that DirectX and probably most hardware currently only support linear fog.
Consider a cell with fog which is on top of another cell with fog. Both cells use fog with the same color and same density. Both fog volume share a plane which coincide with the portal between them. Since the ( Zmax - Zmin ) factors will most probably be different, the fog intensities will also be different. Using linear fog, I easily demonstrate that the linearity is broke if we render each fog volume separately:
T = Ia * F + ( 1 - Ia ) * P (1) C = Ib * F + ( 1 - Ib ) * T (2)
Substitute (1) in (2) and you get:
C = Ib * F + ( 1 - Ib ) * ( Ia * F + ( 1 - Ia ) * P )
Rearranging, you get:
C = (Ia + Ib - Ia * Ib ) * F + ( 1 - ( Ia + Ib - Ia * Ib ) ) * P
If we use I = ( Ia + Ib - Ia * Ib ), we get:
C = I * F + ( 1 - I ) * P
Now if I is linear, everything is ok. If it is not, the solution is not exact.
If we write Ia and Ib as:
Ia = density * ( Z3 - Z2 ) Ib = density * ( Z2 - Z1 )
We then can write I as:
I = density * ( Z3 - Z2 ) + density * ( Z2 - Z1 ) - density * ( Z3 - Z2 ) * density * ( Z2 - Z1 )
Rearranging, we finally get:
I = density * ( Z3 - Z1 ) - density * density * ( Z3 - Z2 ) * ( Z2 - Z1 )
This is clearly non linear. The exact solution for linear fog would have been:
I = density * ( Z3 - Z1 )
Solutions to the problem
The first solution is simple: simply ignore it. It may look good, it may look not so good, only testing will tell. More to come when I implement the fog volumes.
The second solution is to regroup fog volumes together. This will only be valid if the fog color and fog density is the same in all fog volume joined together. The concept of "rooms", consisting of multiple cells, could be used for that. This solution doesn't work if different fog colors are combined.
Right now, I think that the first solution should be good enough. It is not an exact solution, but we are talking about a 3D action game here, and speed is more important. Joining fog volumes is not exactly a fast thing to do.
Comparison between solution one and the exact solution in a typical game scene:
density = 1 / 100 Z3 = 90 Z2 = 60 Z1 = 40
Solution1: I = 0.44
Right there, you can see an error of 12%. I expect this to be even worse for more cells. It means that you should rather use big fogged cells then a lot of small ones. Only testing will tell if solution 1 is ok.