Fog and Attenuation May 03, 2006
Now that we have vertex and pixel shaders, we get to write our own fogging or light attenuation functions. Below are a few methods. I'll present these as per-vertex calculations, though they could also be done per-pixel if needed. (For per-pixel you might want to use a texture lookup table.)
Adding Full-Scene Fog to a shader
First we need to compute the fog percentage using the distance between the viewer and the vertex. We always calculate the vertex position in view-space for the pixel shader, probably with a line like this: Position = mul(ModelViewProj, VertOS); We can use this position with the FogAmount function listed below, with a uniform variable for fog density. This function does the same calculation as the OpenGL GL_EXP fog mode. The fog amount is then sent into the pixel shader, perhaps in an unused alpha component. float FogAmount( float fogDensity, float3 PositionVS ) { float z = length( PositionVS ); // viewer position is at origin return saturate( pow(2.7182818, -(fogDensity * z))); }Once we have the [0,1] fog percentage, we need to use it to adjust the pixel color. In the pixel shader just use pixelColor = lerp( fogColor, pixelColor, fogAmount ); Something to note is that lighting is usually done in multiple passes with additive blending turned on, often with one pass for each light. (A couple lights can be grouped together into a single pass, but this doesn't affect fogging.) If you add fog with each light it will get brighter with each pass, so we what we can do is set the fog color to black after the first pass, so the color contribution from each light is properly adjusted down for fog, but the fog is only added in once. Attenuation Functions
You'll probably want to attenuate your lights so their contribution fades towards zero as distance between the vertex and the light increases. Of course you don't have to use any specific attenuation function. Here's one possibility:
float Attenuation( float lStrength, float3 lightPos, float3 vertPos ) { float d = distance( lightPos, vertPos ); return saturate( lStrength / (d * d) ); }Now, although the above function is my favorite visually, for performance reasons we often want to limit a light to a certain radius. Just dividing by distance squared won't ever get to zero completely, so objects may slightly pop into or out of light at the edge of the light radius. So another possibility is: float AttenuationRadius( float lRadius, float3 lightPos, float3 vertPos ) { float d = distance( lightPos, vertPos ); float attenuation = saturate( (1-saturate( (d * d)/(lRadius*lRadius) )) ); return attenuation * attenuation; }This will be zero if distance is >= light radius. |