View Full Version : GL_TEXTURE_3D and mipmapping
TomorrowPlusX
2006.08.14, 10:10 AM
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:
http://zakariya.net/shamyl/Screenshots/Screenshots-2006-08-12-14.png
Second, with mipmapping enabled. All the surface textures collapse into one, with mottled tone.
http://zakariya.net/shamyl/Screenshots/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!
Skorche
2006.08.14, 03:10 PM
Don't you specify the filter function per axis?
edit: Never mind. I guess that's the repeat function.
TomorrowPlusX
2006.08.14, 03:41 PM
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.
arekkusu
2006.08.14, 03:41 PM
No, TEXTURE_3D must reduce mipmap dimensions in S,T,R.
What you want is an extension like MESAX_texture_stack (http://www.opengl.org/registry/specs/MESAX/texture_stack.txt). But no current hardware supports this.
TomorrowPlusX
2006.08.14, 06:08 PM
Aaarg! That's exactly what I want.
Looks like I'll be using GLSL and separate textures. Thanks for the tip, arekkusu.
unknown
2006.08.14, 07:10 PM
That looks amazing! What is this mushrooming?
Skorche
2006.08.15, 02:57 AM
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) ;)
unknown
2006.08.15, 07:20 AM
uh yeah.. me too, I was kind of hoping for a more detailed description.
TomorrowPlusX
2006.08.15, 10:26 AM
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...
TomorrowPlusX
2006.08.15, 11:01 AM
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.
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.
unknown
2006.08.15, 11:16 AM
Wow, that is an impressive technique. Thanks for posting the code its interesting too see how thats done :D
Jones
2006.08.15, 02:14 PM
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! :)
TomorrowPlusX
2006.08.15, 02:14 PM
I appreciate the kind words, but, well let's see if I ever manage to produce something. :p
TomorrowPlusX
2006.08.16, 10:26 AM
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.
Jones
2006.08.16, 12:54 PM
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!
TomorrowPlusX
2006.08.16, 01:03 PM
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.
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.
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]
Jones
2006.08.16, 04:59 PM
Heh... something simple for once. ;)
Thanks for the answer! :)
TomorrowPlusX
2006.08.17, 11:59 AM
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.
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
http://zakariya.net/shamyl/etc/mapmap_0.png
Mipmap level 4
http://zakariya.net/shamyl/etc/mipmap_4.png
Mipmap level 8
http://zakariya.net/shamyl/etc/mipmap_8.png
arekkusu
2006.08.17, 01:32 PM
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."
TomorrowPlusX
2006.08.17, 02:15 PM
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.
To echo another thread, the relevant parts of the specification
And that's why we love you!
Thanks, chief.
arekkusu
2006.08.17, 05:06 PM
:P
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.
TomorrowPlusX
2006.08.17, 05:24 PM
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.
http://zakariya.net/shamyl/Screenshots/Screenshots-2006-08-17-18.png
http://zakariya.net/shamyl/Screenshots/Screenshots-2006-08-17-23.png
http://zakariya.net/shamyl/Screenshots/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.
Fenris
2006.08.17, 06:16 PM
It appears that my socks are rocked as well. They're all curled up.
arekkusu
2006.08.17, 09:31 PM
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.
Jones
2006.08.21, 08:37 PM
*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.
TomorrowPlusX
2006.08.22, 10:54 AM
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 :p
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.
Jones
2006.08.22, 01:07 PM
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 :p
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?
TomorrowPlusX
2006.08.22, 02:04 PM
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:
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();
}
Jones
2006.08.22, 05:54 PM
Hmm, thanks for that. :)
I didn't know OpenGL had any built in Antisotropic filtering stuff.
arekkusu
2006.11.10, 03:07 AM
I think it is a good bet that future hardware will support the texture-stack approach.
*cough cough cough* (http://developer.download.nvidia.com/opengl/specs/GL_EXT_texture_array.txt)
TomorrowPlusX
2006.11.10, 03:26 PM
*cough cough cough* (http://developer.download.nvidia.com/opengl/specs/GL_EXT_texture_array.txt)
Huh.
What're the odds that will trickle down into, say, my spanking new x1600 in my macbook pro?
vBulletin® v3.6.8, Copyright ©2000-2008, Jelsoft Enterprises Ltd.