GL_TEXTURE_3D and mipmapping

Sage
Posts: 1,199
Joined: 2004.10
Post: #1
I've implemented the alt-grad mapping for terrain described in the chapters on "RealWorldz" at the end of the Orange Book. Alt-Grad mapping is super simple, where you have a 3d surface texture for your terrain, and the z-value for the texture coordinate lookup is calculated by looking into a greyscale texture, indexing x by the surface gradient ( 1 - normal.z ) and the y value is the altitude.

It works great -- in principle -- except that mipmapping of my 3d texture causes the effect to be lost completely, since the depth collapses from ( in my case 4 layers to 1 ) as the mipmapping increases. When mipmapping is disabled for the texture, it looks correct, but looks like hell because of the aliasing artifacts that happen when you disable mipmapping.

Here's a couple screenshots to illustrate:

First, with mipmapping disabled -- you can see various different surface textures as one would expect:
[Image: Screenshots-2006-08-12-14.png]

Second, with mipmapping enabled. All the surface textures collapse into one, with mottled tone.
[Image: Screenshots-2006-08-12-15.png]

As you'd expect, when you approach closely, the mipmapping decreases and it looks OK, but only when you're close enough, which basically ruins the whole effect.

My question is whether there's a way to tell gl to *not* mipmap the depth of the 3d texture. E.g., mipmap S & T, but not R.

Is there such a way?

If there isn't, I can drop 3d textures for four 3d textures and have GLSL calculate per-texture weightings, but I like the cleanliness of the 3d texture approach, it's really slick and makes it easy to support as many or as few depths as you want ( so long as it's a power of two, obviously ).

Thanks,

P.S. I also implemented mushrooming ( from the same chapter in the orange book ), where vanilla heightmapped terrain can have overhangs and other protruberances. I also implemented ambient occlusion in my lightmap baking... I love it!
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #2
Don't you specify the filter function per axis?

edit: Never mind. I guess that's the repeat function.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #3
Skorche Wrote:Don't you specify the filter function per axis?

edit: Never mind. I guess that's the repeat function.

I'm actually hoping that I can disable mipmapping for just the R axis. I've googled a bit, but can't figure out a way. I don't know, either, if manually generating my own mipmaps will let me prevent the collapse of the R axis.
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #4
No, TEXTURE_3D must reduce mipmap dimensions in S,T,R.

What you want is an extension like MESAX_texture_stack. But no current hardware supports this.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #5
Aaarg! That's exactly what I want.

Looks like I'll be using GLSL and separate textures. Thanks for the tip, arekkusu.
Quote this message in a reply
Sage
Posts: 1,403
Joined: 2005.07
Post: #6
That looks amazing! What is this mushrooming?

Sir, e^iπ + 1 = 0, hence God exists; reply!
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #7
unknown Wrote:That looks amazing! What is this mushrooming?

I would guess it's the ability of the terrain to go more than vertical. (like a mushroom) Wink

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Sage
Posts: 1,403
Joined: 2005.07
Post: #8
uh yeah.. me too, I was kind of hoping for a more detailed description.

Sir, e^iπ + 1 = 0, hence God exists; reply!
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #9
My implementation of mushrooming is far from perfect, you have to be careful not to mushroom too much or the terrain can go a little degenerate. I'll post the code, it ought to work on any heightmapped terrain.

Basically, it just pushes the geometry out along its normal based on a function of height. Simple enough...
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #10
OK, mushrooming code. It's pretty well commented, so it ought to make sense. One thing, though, is that the second method -- Terrain::mushroomRelax -- works but definitely could be better. All it does right now is try to make certain that windings don't break too badly.

Code:
struct TerrainHeightComparator
{
    vec3 *vertices;
    
    TerrainHeightComparator( vec3 *Vertices ) : vertices(Vertices){}

    inline bool operator()( int indexA, int indexB )
    {
        return vertices[ indexB ].z > vertices[ indexA ].z;
    }
};


void Terrain::mushroom( void )
{
    if ( _mushrooming < EPSILON ) return;
    
    const int passes = 20;
    float mushroomingChunk = _mushrooming / float( passes );
    float worldExtent = _size * 0.5f;
    float worldExtentThickness = 1000.0f;
    float relaxationAmount = 1.0f / float( passes );

    /*
        Process
        Note: mushrooming is applied as a function of height.
        For each vertex, extend its position along its normal by the
        vertex's z value / maxHeight times the mushrooming factor
        for that altitude.
        
        Notes:
        1) Instead of indexing linearly, we must created an index
        array sorted by height, so we can apply distortion *up* the
        terrain.
        
        2) We're doing this in multiple passes, performinga  "relaxation"
        of the geometry after each mushrooming iteration to try to
        prevent collapsing triangles.
        
        3) We also linearly decrease the mushrooming factor as we approach
        the ends of the geometry, since the mushrooming algorithm creates
        very noticable weirdness at the edges ( since there's nothing to
        connect to ).
    */

    /*
        Create a copy of the original vertices, we need this
        for the correction of degenerate triangles
    */
    std::vector< vec3 > originalVertices;
    originalVertices.resize( _numVertices );
    for ( int i = 0; i < _numVertices; i++ ) originalVertices[i] = _vertices[i];

    /*
        Create storage for indices sorted by height
    */
    std::vector< int > indicesByHeight;
    indicesByHeight.resize( _numVertices );
    
    float mushroomingZ = _maxHeight * _mushroomingAltitude;

    for ( int i = 0; i < passes; i++ )
    {
        /*
            Copy over indices and sort them by height
        */
        for ( int i = 0; i < _numVertices; i++ ) indicesByHeight[i] = i;        
        std::sort( indicesByHeight.begin(), indicesByHeight.end(), TerrainHeightComparator( _vertices ));
        
        /*
            Now, apply mushrooming per-vertex
        */
        std::vector< int >::iterator it( indicesByHeight.begin() ), end( indicesByHeight.end() );
        for ( ; it != end; ++it )
        {
            vec3 vertex = _vertices[ *it ],
                 normal = _normals[ *it ];
                         
            /*
                Calculate x & y scaling factors which are scaled out by edge threshold
            */
            
            float factor = 0;
            
            if ( vertex.z > mushroomingZ )
            {
                factor = 1.0f - ( vertex.z - mushroomingZ ) / ( _maxHeight - mushroomingZ );
            }
            else
            {
                factor = 1.0f - ( mushroomingZ - vertex.z ) / mushroomingZ;
            }
            
            factor = clamp( powf( factor, _mushroomingHeightBias ), 0.0f, 1.0f );
                        
            float factorX = factor * mushroomingChunk,
                  factorY = factor * mushroomingChunk;
                  
            if ( vertex.x < -worldExtent + worldExtentThickness )
            {
                factorX *= 1.0f - clamp( ( vertex.x - (worldExtent + worldExtentThickness)) / worldExtentThickness, 0.0f, 1.0f );
            }
            else if ( vertex.x > worldExtent - worldExtentThickness )
            {
                factorX *= 1.0f - clamp( ( vertex.x - (worldExtent - worldExtentThickness)) / worldExtentThickness, 0.0f, 1.0f );
            }

            if ( vertex.y < -worldExtent + worldExtentThickness )
            {
                factorY *= 1.0f - clamp( ( vertex.y - (worldExtent + worldExtentThickness)) / worldExtentThickness, 0.0f, 1.0f );
            }
            else if ( vertex.y > worldExtent - worldExtentThickness )
            {
                factorY *= 1.0f - clamp( ( vertex.y - (worldExtent - worldExtentThickness)) / worldExtentThickness, 0.0f, 1.0f );
            }

            /*
                Apply the mushrooming factor, and then perform a clamping sanity-check
            */
            vertex += normal * vec3( factorX, factorY, _mushroomingCapping );
            vertex.x = clamp( vertex.x, -worldExtent, worldExtent );
            vertex.y = clamp( vertex.y, -worldExtent, worldExtent );
            vertex.z = std::max( vertex.z, 0.0f );

            /*
                Finally, assign the vertex
            */
            _vertices[ *it ] = vertex;
        }
            
        /*
            After each mushrooming pass, update the normals, since the
            mushrooming will have resulted in new geometry.
        */

        calculateNormals();
    }
    
    /*
        Perform relaxation
    */

    for ( int i = 0; i < passes; i++ )
    {
        mushroomRelax( relaxationAmount, originalVertices );    
    }

    /*
        And one more normal calculation pass, since the
        relaxation will have changed geometry.
    */

    calculateNormals();
}

void Terrain::mushroomRelax( float correctiveAmount, const std::vector< vec3 > &originalVertices )
{
    /*
        Relaxation is performed per-triangle.
        A triangle is considered to be OK if the normal of the new triangle
        is still pointing roughly the same direction as the original, e.g.,
        the dot-product is > 0, and the magnitudes of the edges are greater than
        or equal to the original, where a triangle is as such, with AC the hypoteneuse.
        
        A
        | \
        |  \
        B-- C
        
        If the normal is still in the roughly same direction, just verify
        that the lengths are >= to the originals, else perform correction
        on the erroneous segment.
        
        If the normal is reversed, perform correction on each segment.
        
        Correction is defined as moving the offending vertices by
        correctiveAmount to    where they were before mushrooming was applied,
        or in the case of shortened segments, in the direction necessary
        to fix the collapse.
    */

    for ( int i = 0; i < _numIndices; i += 3 )
    {
        int indexA = _indices[ i + 0 ],
            indexB = _indices[ i + 1 ],
            indexC = _indices[ i + 2 ];
            
        vec3 originalA( originalVertices[ indexA ] ),
             originalB( originalVertices[ indexB ] ),
             originalC( originalVertices[ indexC ] ),
             mushroomA( _vertices[ indexA ] ),
             mushroomB( _vertices[ indexB ] ),
             mushroomC( _vertices[ indexC ] );
            
        vec3 originalNormal( vec3::normal( originalA, originalB, originalC )),
             mushroomNormal( vec3::normal( mushroomA, mushroomB, mushroomC ));
                            
        /*
            Verify normal
        */
        
        if ( dot( originalNormal, mushroomNormal ) < 0.0f )
        {
            /*
                Winding broke, move vertices halfway to original locations.
            */

            _vertices[ indexA ] = lrp( correctiveAmount, mushroomA, originalA );
            _vertices[ indexB ] = lrp( correctiveAmount, mushroomB, originalB );
            _vertices[ indexC ] = lrp( correctiveAmount, mushroomC, originalC );            
        }
        else
        {
            /*
                Still facing same direction, so check distances
            */
            
            float originalAB( originalA.distance( originalB )),
                  originalBC( originalB.distance( originalC )),
                  originalCA( originalC.distance( originalA )),
                  mushroomAB( mushroomA.distance( mushroomB )),
                  mushroomBC( mushroomB.distance( mushroomC )),
                  mushroomCA( mushroomC.distance( mushroomA ));
        
            if ( originalAB > mushroomAB )
            {
                vec3 dir = mushroomA - mushroomB;
                dir.normalize();
                vec3 newA = mushroomB + dir * originalAB;
                
                dir = mushroomB - mushroomA;
                dir.normalize();
                vec3 newB = mushroomA + dir * originalAB;

                _vertices[ indexA ] = lrp( correctiveAmount, mushroomA, newA );
                _vertices[ indexB ] = lrp( correctiveAmount, mushroomB, newB );

                /*
                    Adjust the distances to accommodate the change we just made
                */
                mushroomBC = mushroomB.distance( mushroomC );
                mushroomCA = mushroomC.distance( mushroomA );
            }

            if ( originalBC > mushroomBC )
            {
                vec3 dir = mushroomB - mushroomC;
                dir.normalize();
                vec3 newB = mushroomC + dir * originalBC;
                
                dir = mushroomC - mushroomB;
                dir.normalize();
                vec3 newC = mushroomB + dir * originalBC;

                _vertices[ indexB ] = lrp( correctiveAmount, mushroomB, newB );
                _vertices[ indexC ] = lrp( correctiveAmount, mushroomC, newC );

                /*
                    Adjust the distances to accommodate the change we just made
                */
                mushroomCA = mushroomC.distance( mushroomA );
            }

            if ( originalCA > mushroomCA )
            {
                vec3 dir = mushroomC - mushroomA;
                dir.normalize();
                vec3 newC = mushroomA + dir * originalCA;
                
                dir = mushroomA - mushroomC;
                dir.normalize();
                vec3 newA = mushroomC + dir * originalCA;

                _vertices[ indexC ] = lrp( correctiveAmount, mushroomC, newC );
                _vertices[ indexA ] = lrp( correctiveAmount, mushroomA, newA );
            }
        }        
    }    
}

As I mentioned earlier, my mushroomRelax method isn't any good, and probably causes more problems than it solves.
Quote this message in a reply
Sage
Posts: 1,403
Joined: 2005.07
Post: #11
Wow, that is an impressive technique. Thanks for posting the code its interesting too see how thats done Grin

Sir, e^iπ + 1 = 0, hence God exists; reply!
Quote this message in a reply
Jones
Unregistered
 
Post: #12
TommorowPlusX, every time I see screens of your work, I'm amazed. Blink Really, I've no idea how to achieve such an impressive level of... everything! Smile
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #13
I appreciate the kind words, but, well let's see if I ever manage to produce something. Rasp
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #14
Somebody on the mac-opengl mailing list suggested that I manually generate my mipmap data and submit it myself. I just gave this a shot ( with some hacky code ) and it works!

This is good, because yesterday I wrote a GLSL implementation, which works, but brings the performance down ( when rendering just terrain and skydome ) from ~70fps to 30. The fixed function implementation is a *lot* faster.
Quote this message in a reply
Jones
Unregistered
 
Post: #15
TomorrowPlusX Wrote:Somebody on the mac-opengl mailing list suggested that I manually generate my mipmap data and submit it myself. I just gave this a shot ( with some hacky code ) and it works!

This is good, because yesterday I wrote a GLSL implementation, which works, but brings the performance down ( when rendering just terrain and skydome ) from ~70fps to 30. The fixed function implementation is a *lot* faster.

Do you mean you wrote some code that runs through pixel data arrays and generates mipmaps as it goes through it?

After hearing you mention 3D textures (didn't know about them) I went and read about them on the GameDev wiki. I can see their use for mixing two different textures, like in the mountain example. (Smooth transition from grass to rock...) And I can see it's a great feature, but I'm not sure how you would pass data to it...

Do you just pack the pixel data into one array and pass it like that?

Thanks!
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Mipmapping Nick 4 3,176 Aug 30, 2004 01:40 AM
Last Post: sealfin