GL_TEXTURE_3D and mipmapping

Sage
Posts: 1,199
Joined: 2004.10
Post: #16
Jones Wrote:Do you mean you wrote some code that runs through pixel data arrays and generates mipmaps as it goes through it?

Yes, my code's manually generating mipmap data. Poorly, at the moment, but I'm going to refactor and clean it up, now that I know the principle is valid.

Quote: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...

Or four textures, or eight. As many as you've got memory for. But I'm using a separate texture, an altitude/gradient map, to look up the R coordinate.


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

Pretty much! Just one long byte array ( width * height * depth * bpp ) in the end.

Thanks![/quote]
Quote this message in a reply
Jones
Unregistered
 
Post: #17
Heh... something simple for once. Wink

Thanks for the answer! Smile
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #18
So I don't want to be that guy crossposting, but I just posted this to the mac-opengl list, and I'm hoping somebody here maybe will see it and lend me a hand...

What I'm seeing is this -- I can create the mipmapped 3d texture where the R dimension remains constant, and I can view this texture ( and its different mipmap levels ) in OpenGL Profiler. I can bind and use this texture without GL reporting any error state, but when rendered I see only white. If I create the texture without mipmaps, when rendered I see the texture correctly, which tells me that my rendering code is OK, so the error is in the texture creation and initialization when I use mipmaps.

My guess, off the top of my head is that either:

1) You simply cannot manually mipmap a 3D texture without halving the R dimension at each level. GL won't complain if you do, but it won't render it. or,
2) The principle is valid, but my implementation is poor.

Obviously, I'm hoping it's #2, since my code using 3D textures can run completely inside the fixed-function pipeline ( and as such is much faster on my 5200 ) and scales nicely ( e.g., you can have 1,2,4,8,etc levels in your 3D texture without having to specialize rendering or GLSL code ).

I'm posting my code for loading and mipmapping 3D textures below. I'm hoping somebody can point out if I'm doing anything boneheaded, if #2 is the case. My code below takes in a vector of std::string filenames, loads an image for each, verifies they're all the same dimensions/components/internal-format and then enters a loop. The loop packs the images pixel data into one contiguous byte array, submits it to GL, and then halves each image. When the minor image dimension hits 1, or if mipmaps are not requested for the texture, the loop terminates. Finally, it sets the GL_TEXTURE_MAX_LOD to the final submitted mipmap level. There's also some obvious error checking and boilerplate. Finally, the method image_data::halve() is a simple method which creates a copy of the image, halved in dimensions with the source pixels accumulated into the target. I know from looking at GL profiler's view of the 3D texture that the halved image data is correct.

Code:
bool TextureManager::TextureEntry::load3D( StringLib::StringVec filenames,
                                           bool mipmaps,
                                           bool canChangeQuality,
                                           bool highQuality )
{
    using namespace PANSICore;
    using namespace ImageLoading;

    close();

    if ( !IsPow2( filenames.size() ))
    {
        Logger::log( LogEntry::Critical, "TextureManager::TextureEntry::load3D",
                     "Number of filenames (%u) must be a power of two", filenames.size() );
                    
        return false;
    }


    _filename = StringLib::join( filenames, ":" );
    _mipmaps = mipmaps;
    _canChangeQuality = canChangeQuality;
    _highQuality = canChangeQuality ? highQuality : true;
    _target = GL_TEXTURE_3D;

    _depth = filenames.size();

    /*
        Load images
    */

    bool fail = false;
    std::vector< image_data * > images;
    
    for ( int i = 0; i < _depth; i++ )
    {
        ImageLoading::image_data *image = ImageLoading::load( filenames[i], _highQuality ? 1 : 2 );

        if ( image )
        {
            images.push_back( image );

            /*
                First loaded image will be our reference
                for dimensions and format/components
            */
            if ( i == 0 )
            {
                _components = images[0]->components;
                _format = images[0]->format;
                _width = images[0]->width;
                _height = images[0]->height;
            }
            else
            {
                if ( images[i]->width != _width ||
                     images[i]->height != _height )
                {
                    Logger::log( LogEntry::Critical, "TextureManager::TextureEntry::load3D",
                                 "Texture3D layer %d has differing dimensions from previous", i );
                    fail = true;
                    break;
                }

                if ( images[i]->components != _components )
                {
                    Logger::log( LogEntry::Critical, "TextureManager::TextureEntry::load3D",
                                 "Texture3D layer %d has differing components from previous", i );
                    fail = true;
                    break;
                }

                if ( images[i]->format != _format )
                {
                    Logger::log( LogEntry::Critical, "TextureManager::TextureEntry::load3D",
                                 "Texture3D layer %d has differing internal format from previous", i );
                    fail = true;
                    break;
                }            
            }        
        }
        else
        {
            Logger::log( LogEntry::Critical, "TextureManager::TextureEntry::load3D",
                         "Unable to load 3D texture layer %d ( file: %s )",
                         i, filenames[i].c_str() );

            fail = true;
            break;
        }
    }
    
    /*
        If we failed, free what we managed to load and exit.
    */
    if ( fail )
    {
        for ( int i = 0; i < (int) images.size(); i++ ) delete images[i];
        close();
        
        return false;        
    }


    /*
        Now, create texture and start uploading. We'll halve each
        image after each pass, when the minor dimension drops to 1,
        we're ready to exit.
    */

    /*
        Create texture
    */

    glGenTextures( 1, &_textureID );
    glBindTexture( _target, _textureID );

    glTexParameteri( _target, GL_TEXTURE_MIN_FILTER, mipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR );
    glTexParameteri( _target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    
    bool done = false;
    int mipmapLevel = 0;
    
    while( !done )
    {
        glError();
        
        int levelWidth = images[0]->width,
            levelHeight = images[0]->height,
            bytesPerPixel = images[0]->bytesPerPixel(),
            pixelLayerLength = levelWidth * levelHeight * bytesPerPixel;
            
//        printf( "Mipmap %d levelWidth: %d levelHeight: %d bytesPerPixel: %d\n",
//                mipmapLevel, levelWidth, levelHeight, bytesPerPixel );
            
        /*
            If we've reached the final mipmap level we're done and can exit after this iteration
        */

        if ( levelWidth == 1 || levelHeight == 1 )
        {
//            printf( "Reached mipmap minimum, done=true\n" );
            done = true;
        }
            
        /*
            Generate a packed representation
        */

        GLbyte *packedPixels = new GLbyte[ pixelLayerLength * _depth ];        
        for ( int layer = 0; layer < _depth; layer++ )
        {
            int offset = layer * levelWidth * levelHeight * bytesPerPixel;
            memcpy( &packedPixels[ offset ], images[layer]->data, pixelLayerLength );
        }
        
        /*
            Now submit pixels to GL
        */

        glError();

        glPixelStorei( GL_UNPACK_ROW_LENGTH, levelWidth );
        glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
        
        glError();
        
//        printf( "Submitting mipmapLevel %d at ( %d x %d x %d )\n",
//                mipmapLevel, levelWidth, levelHeight, _depth );

        glTexImage3D( _target, mipmapLevel,
                      _components,
                      levelWidth, levelHeight, _depth,
                      0,
                      _format,
                      _components == GL_RGBA ? ARGB_IMAGE_TYPE : GL_UNSIGNED_BYTE,
                      packedPixels );

        glError();

        /*
            Clean up packed pixels
        */

        delete [] packedPixels;
                                
        /*
            If we're not generating mipmaps, we can quit after the first pass,
            otherwise, unless we're done, halve images and bump mipmap level.
        */
        
        if ( !mipmaps )
        {
            break;
        }
        else if ( !done )
        {
            mipmapLevel++;
            
            /*
                Now, halve each image
            */
            
            for ( int layer = 0; layer < _depth; layer++ )
            {
                image_data *image = images[layer];
                images[layer] = image->halve();
                
                delete image;
            }
        }        
    }

    /*
        Free images
    */

    for ( int layer = 0; layer < _depth; layer++ ) delete images[layer];

    /*
        Set the max mipmap level
    */
    glTexParameteri( _target, GL_TEXTURE_MAX_LOD, mipmapLevel );

    glBindTexture( _target, 0 );
    glDisable( _target );

    return true;
}

And here's some screenshots of the 3D texture in GL Profiler to show that, yes, it really is displaying there at least.

Mipmap level 0
[Image: mapmap_0.png]

Mipmap level 4
[Image: mipmap_4.png]

Mipmap level 8
[Image: mipmap_8.png]
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #19
Sorry, the answer is 1). Mipmap dims must halve in S/T/R at each level.
The fact that Profiler shows this is interesting, but probably a bug.

To echo another thread, the relevant parts of the specification are 3.8.10 and 3.8.8, which say:

"A mipmap is an ordered set of arrays representing the same image; each array has a resolution lower than the previous one. If the image array of level levelbase, excluding its border, has dimensions wb × hb × db, then there are floor(log2 (max(wb, hb, db))) + 1 image arrays in the mipmap. Numbering the levels such that level levelbase is the 0th level, the ith array has dimensions:
max(1, floor(wb/2i)) × max(1, floor(hb/2i)) × max(1, floor(db/2i))
until the last array is reached with dimension 1 × 1 × 1."
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #20
arekkusu Wrote:Sorry, the answer is 1). Mipmap dims must halve in S/T/R at each level.
The fact that Profiler shows this is interesting, but probably a bug.

Good to know. A posting on mac-opengl got my hopes up that manually creating mipmaps would get around it, but I guess it was a bum lead. I'll have to bark up a different tree, now. I am curious how the folks who wrote RealWorldz got around this, but since the implementation details are scarce, I'll never know.


Quote:To echo another thread, the relevant parts of the specification

And that's why we love you!

Thanks, chief.
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #21
Rasp

I think it is a good bet that future hardware will support the texture-stack approach. But in the meanwhile you'll have to blend 2D textures yourself.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #22
Well, there's a happy ending, after all. I usually have my surface textures at a very large scale to minimize the appearance of low-frequency repeating ( though I do a lot of low pass filter stuff in PS to avoid this in the first place ). I use a detail texture to show some detail up close. Easy enough.

Anyway, I thought I'd give a stab at accepting proper 3d texture minification and seeing if using a large scale for the surface texture might not minimize the impact of the collapse of the R dimension. And lo, my socks are rocked.

[Image: Screenshots-2006-08-17-18.png]



[Image: Screenshots-2006-08-17-23.png]



[Image: Screenshots-2006-08-17-20.png]

I'm still going to write a "high quality" GLSL version which will add specular highlights. But for now, I'm happy.
Quote this message in a reply
Oldtimer
Posts: 834
Joined: 2002.09
Post: #23
It appears that my socks are rocked as well. They're all curled up.
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #24
The problem with that approach is that your minification factor will change depending on your window size. So try it out at both 640x480 and on a 30" display.
Quote this message in a reply
Jones
Unregistered
 
Post: #25
*Drools over Screenshots.*

Sorry to here your mipmapping didn't work out the way you wanted, I was quite impressed with your idea and was hoping to try it myself.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #26
Actually, it's working fine. Arekkusu was right to suggest I try it at 640x480 but it looks OK. I don't have a 30" display, however Rasp

The thing is, since I have to scale up the texture hugely, I have two add-signed detail textures as well -- one at a very small scale, and one at a medium scale. This helps keep detail when viewing up close.
Quote this message in a reply
Jones
Unregistered
 
Post: #27
TomorrowPlusX Wrote:Actually, it's working fine. Arekkusu was right to suggest I try it at 640x480 but it looks OK. I don't have a 30" display, however Rasp

The thing is, since I have to scale up the texture hugely, I have two add-signed detail textures as well -- one at a very small scale, and one at a medium scale. This helps keep detail when viewing up close.

Ah, I think I understand. You just draw a large one when close, and a different sized one from a distance?
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #28
Ah, no actually. I use three texture units. The 3D texture is on unit 0, and the two detail textures are on units 1 and 2, respectively.

Here's the code:
Code:
void TerrainPrivate::DefaultRenderer::bind( render_pass pass, Light *forLight )
{
    glError();

    /*
        Surface texture (3d)
    */
    glActiveTexture( GL_TEXTURE0 );
    glEnable( GL_TEXTURE_3D );
    glBindTexture( GL_TEXTURE_3D, terrain->surfaceTexture().textureID() );
    glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
    
    glMatrixMode( GL_TEXTURE );
    glLoadIdentity();
    glScalef( 1.0f / terrain->surfaceTextureScale(), 1.0f / terrain->surfaceTextureScale(), 1.0f );
    glMatrixMode( GL_MODELVIEW );
    
    /*
        primary detail texture
    */
    glActiveTexture( GL_TEXTURE1 );
    glEnable( GL_TEXTURE_2D );
    glBindTexture( GL_TEXTURE_2D, terrain->primaryDetailTexture().textureID() );
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f );

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD_SIGNED);

    glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB,  GL_TEXTURE1 );
    glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
    
    glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
    glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );        

    glMatrixMode( GL_TEXTURE );
    glLoadIdentity();
    glScalef( 1.0f / terrain->primaryDetailTextureScale(), 1.0f / terrain->primaryDetailTextureScale(), 1.0f );
    glMatrixMode( GL_MODELVIEW );

    /*
        secondary detail texture
    */
    glActiveTexture( GL_TEXTURE2 );
    glEnable( GL_TEXTURE_2D );
    glBindTexture( GL_TEXTURE_2D, terrain->secondaryDetailTexture().textureID() );
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f );

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_ADD_SIGNED);

    glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB,  GL_TEXTURE2 );
    glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );
    
    glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB );
    glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );        

    glMatrixMode( GL_TEXTURE );
    glLoadIdentity();
    glScalef( 1.0f / terrain->secondaryDetailTextureScale(), 1.0f / terrain->secondaryDetailTextureScale(), 1.0f );
    glMatrixMode( GL_MODELVIEW );
    
    glError();
}
Quote this message in a reply
Jones
Unregistered
 
Post: #29
Hmm, thanks for that. Smile

I didn't know OpenGL had any built in Antisotropic filtering stuff.
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #30
arekkusu Wrote:I think it is a good bet that future hardware will support the texture-stack approach.

*cough cough cough*
Quote this message in a reply
Post Reply 

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