A little GLSL help. Math!

Sage
Posts: 1,199
Joined: 2004.10
Post: #1
I've recently implemented fog planes. They look pretty great, to toot my own horn.

Here's a shot looking down on the fog plane:
[Image: terraintest-2008-10-27-16.jpg]


And here's a shot looking up, from beneath the fog plane:
[Image: terraintest-2008-10-27-19.jpg]

Now, they're implemented in the vertex shader by intersecting a ray from the camera to each vertex through the fog plane. I handle four situations:

- camera and vertex are above the fog plane
- camera is above, vertex is below
- camera is below, vertex is above
- camera and vertex are below

With these four situations handled, it looks surprisingly good. My only complaint is that I'm handling fog as if it were homogenous density, like water. E.g., the fog is just as dense at the fog plane as it is, say, 100 meters below. This strikes me as unrealistic, since low-lying fog would likely be less dense as it approaches the plane.

I'm curious about the math to fade the fog density out as it approaches the fog plane. My trouble is I'm not certain what the best approach would be. So far the best idea I've had is to sample at some fixed number of distances along the ray and sum up the density. But this strikes me as being unnecessarily complex. There's got to be some smarter way of doing this, but my math is weak.

Here's the code. It's pretty simple.

fog.vs
Code:
#define FOG_PLANE 1

uniform float FogDistance;
uniform float FogPlaneVisibleDistance;

#ifdef FOG_PLANE
    uniform vec4 FogPlane;
#endif

///////////////////////////////////////////////////////////////////////

varying float Fog_DistanceContribution;
varying float Fog_PlaneContribution;

///////////////////////////////////////////////////////////////////////
// Plane maths

#ifdef FOG_PLANE
/*
        classify a vertex as being in front, on, or behind a plane
        return 1 if in front
               0 if on the plane
               -1 if behind the plane
*/

    float halfspace( in vec4 plane, in vec3 v )
    {
        float dt = plane.x*v.x + plane.y*v.y + plane.z*v.z + plane.w;
        return clamp( dt, -1.0, 1.0 );
    }

/*
        Intersect a ray with a plane
        -plane The plane
        -p The origin of the ray
        -dir The normalized direction of the ray
        -intersection The intersection will be written here
        -distance The distance from `intersection to `p will be written here
*/
    void rayIntersection( in vec4 plane, in vec3 p, in vec3 dir, out vec3 intersection, out float distance )
    {
        float dNV = dot( plane.xyz, dir );
    
        vec3 q = plane.xyz * plane.w;
        float t = dot( plane.xyz, ( q - p ) ) / dNV;
        intersection = p + ( dir * t );
        distance = length( intersection - p );
    }
#endif

///////////////////////////////////////////////////////////////////////

void fog_setup( in vec3 ecPosition )
{
    //
    // Calculate distance fog contribution. Note, we store it as reciprocal,
    // where 1 means no fog, 0 means full fog.
    //

    float fragDistance = length( ecPosition );
    Fog_DistanceContribution = min( fragDistance / FogDistance, 1.0 );
    Fog_DistanceContribution = 1.0 - ( Fog_DistanceContribution * Fog_DistanceContribution );

    #ifdef FOG_PLANE
        //
        // Calculate fog plane contribution
        //

        vec3 cameraPosition = vec3( 0,0,0 );
        vec3 rayDirection = normalize( -ecPosition );

        float cameraClassification = halfspace( FogPlane, cameraPosition );
        float vertexClassification = halfspace( FogPlane, ecPosition );

        vec3 intersection;
        float distance;
        rayIntersection( FogPlane, cameraPosition, rayDirection, intersection, distance );          


        //
        // We need to determine which of the four ray transits this vertex represents:
        // 1) camera and vertex are above the fog plane
        // 2) camera is above, the vertex is below
        // 3) camera is below and vertex is above
        // 4) camera and vertex are below
        //

        Fog_PlaneContribution = 0.0;
        if ( cameraClassification > 0.0 )
        {
            if ( vertexClassification < 0.0 )
            {      
                Fog_PlaneContribution = min(( fragDistance - distance ) / FogPlaneVisibleDistance, 1.0 );
            }
        }
        else
        {
            if ( vertexClassification > 0.0 )
            {
                Fog_PlaneContribution = min( distance / FogPlaneVisibleDistance, 1.0 );
            }
            else
            {
                Fog_PlaneContribution = min( fragDistance / FogPlaneVisibleDistance, 1.0 );
            }
        }

        Fog_PlaneContribution = 1.0 - sqrt(Fog_PlaneContribution);
    #endif
}

And the fragment shader fog.fs
Code:
#define FOG_PLANE 1

uniform vec4 FogColor;

#ifdef FOG_PLANE
uniform vec4 FogPlaneColor;
#endif

///////////////////////////////////////////////////////////////////////

varying float Fog_DistanceContribution;

#ifdef FOG_PLANE
varying float Fog_PlaneContribution;
#endif

///////////////////////////////////////////////////////////////////////

/*
    Apply both distance and planar fog contribution for an ambient render pass
*/
vec3 fog_apply_ambient( in vec3 color )
{
    #ifdef FOG_PLANE
        return mix( FogPlaneColor.rgb, mix( gl_Fog.color.rgb, color, Fog_DistanceContribution ), Fog_PlaneContribution );
    #else
        return mix( FogColor.rgb, color, Fog_DistanceContribution );
    #endif
}

/*
    Apply JUST planar fog contribution for an ambient render pass
*/
vec3 fog_plane_apply_ambient( in vec3 color )
{
    #ifdef FOG_PLANE
        return mix( FogPlaneColor.rgb, color, Fog_PlaneContribution );
    #else
        return color;
    #endif
}

/*
    Apply both distance and planar fog contribution for a lit render pass
*/
vec3 fog_apply_lit( in vec3 color )
{
    //
    // For lit rendering, we don't apply fog color, we just fade to black,
    // reducing the fragment's contribution to the scene
    //

    #ifdef FOG_PLANE
        return Fog_PlaneContribution * Fog_DistanceContribution * color;
    #else
        return Fog_DistanceContribution * color;
    #endif
}

/*
    Apply JUST planar fog contribution for a lit render pass
*/
vec3 fog_plane_apply_lit( in vec3 color )
{
    //
    // For lit rendering, we don't apply fog color, we just fade to black,
    // reducing the fragment's contribution to the scene
    //

    #ifdef FOG_PLANE
        return Fog_PlaneContribution * color;
    #else
        return color;
    #endif
}

Any ideas?
Quote this message in a reply
Member
Posts: 45
Joined: 2006.07
Post: #2
The fancy math name for the thing you're computing is a line integral (http://en.wikipedia.org/wiki/Line_integral). Uniform density fog gives a very straightforward way of computing this line integral: just look at the length of the ray segment going thru the fog and multiply it by a constant.

The trick of speeding up non-uniform fog without resorting to the type of numerical sampling you're proposing is to pick a representation of the fog that yields a friendly closed-form formula for an integral. Ken Perlin has a good article about this: http://mrl.nyu.edu/~perlin/experiments/gabor/. The part about making a lookup table for precomputing the indefinite integrals seems pretty relevant. You can tune out about exactly where he starts talking about Gabor functions - I'm not sure that he ever got that part working.

Dunno if this will help much, but it might be the type of thing you're looking for...

EDIT: found this paper by googling "gabor perlin fog" - http://www.cs.gmu.edu/~jchen/cs662/fog.pdf
Quote this message in a reply
Member
Posts: 320
Joined: 2003.06
Post: #3
A little OT but in those screenshots there appears to be some noise added to the output. Is this intentional? It looks fantastic, like a photo taken on a moderately high ISO setting. Really adds to the realism.

Chopper, iSight Screensavers, DuckDuckDuck: http://majicjungle.com
Quote this message in a reply
DoG
Moderator
Posts: 869
Joined: 2003.01
Post: #4
If you just want the density to vary with depth, decompose the ray into normal and planar components with respect to the fog plane, and say, multiply the length of the ray with it's depth, or some variaton of that. That should give you a cheap way to 'fade out' the fog as it approaches the plane.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #5
mattz Wrote:The fancy math name for the thing you're computing is a line integral (http://en.wikipedia.org/wiki/Line_integral). Uniform density fog gives a very straightforward way of computing this line integral: just look at the length of the ray segment going thru the fog and multiply it by a constant.

The trick of speeding up non-uniform fog without resorting to the type of numerical sampling you're proposing is to pick a representation of the fog that yields a friendly closed-form formula for an integral. Ken Perlin has a good article about this: http://mrl.nyu.edu/~perlin/experiments/gabor/. The part about making a lookup table for precomputing the indefinite integrals seems pretty relevant. You can tune out about exactly where he starts talking about Gabor functions - I'm not sure that he ever got that part working.

Dunno if this will help much, but it might be the type of thing you're looking for...

EDIT: found this paper by googling "gabor perlin fog" - http://www.cs.gmu.edu/~jchen/cs662/fog.pdf

That looks pretty much exactly on the nose. Thanks! I'll look into it.

That being said, I'm beginning to but up against the instruction limit for my x1600. Between this, fancy shaders and cascading shadow maps...
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #6
reubert Wrote:A little OT but in those screenshots there appears to be some noise added to the output. Is this intentional? It looks fantastic, like a photo taken on a moderately high ISO setting. Really adds to the realism.

Thanks,

I wrote a "filter stack" API that lets me write GLSL filters for postprocessing. The screenshots show a "gloom" filter and a noise filter. I like the output too... very moody.
Quote this message in a reply
Post Reply