Skip to main content
GameDev.net gamedev.net
🔒 Locked

Multipass texture splatting

Started by Nit Jan 30, 2006 at 8:56 AM 6 replies 10.5k views
Original Post
Nit
Nit
I would like to add texture splatting to my heightmapped terrain. I would like my program to run on my old pc, which uses a crappy NVidia TNT2. So for my first attempt, I would like to accomplish this without shaders or the multitexture extension. This leaves me with a multipass implementation (if there is a single pass solution that doesn't rely on extensions, that would be great too). To simply this discussion, I would like to discuss how this would be done using a texturemapped quad. My textures available to these quads are all packed into 1 tga image (just a bunch of texture tiles). My initial idea is that this multipass texture splatting will use blending to combine the alpha channel of one texture tile with the base texture of another tile. After blending those two textured quads ontop of eachother, I could place that quad ontop of the original base textured quad. To be honest, I've never (successfully) combined the alpha channel of one texture and applied/blended it to another. I'm not sure what the blend blend function parameters would be, as I've been having trouble getting this to come out right. Is this a viable solution? Are there better practices? Thanks.
Fahrenheit451
Fahrenheit451
Procedural texturing works well for this. The approach described here is one I used to generate the terrain below, and is described in Trent Polak's Focus on 3D Terrain Programming.

At startup create an empty RGB image array, say 512x512, and generate the pixel (color) data for this from base images - grass, dirt, rock, etc. For the current pixel, determine the height at the corresponding vertex on the mesh (or interpolated height if your texture size doesn't match the mesh size and your between two vertices), and then pull in the corresponding pixel from the base images (more interpolation required is you are using small 64x64 base textures). The percentage of color you use from the base image pixels is determined by ranges and percentages. If your heightmap is in the range 0 to 255 (nice because that matches RGB color ranges nicely), then you set ranges for which base images are active and the percentage of color used. For example, water 0-30, sand 20-60, green grass 40-160, brown grass 130-220, rock 200-255. If your height is 50 for the current pixel, then your final pixel color will be about an equal percentage of sand and grass. I then make a second pass where I factor in other things like slope (angle at current location from horizontal), and blend in other colors from three different rock images.

The final image then has a texture created from it, and is stretched across the mesh. The image below does multi-texture in a detail map though. The thing I like about this approach is taking the pixel from the base images based on where you are on the mesh and the terrain texture. This means that if you base image is rich in detail and color, then that is nicely distributed across the whole terrain, thus adding to the effect.

I highly recommend the book above. This implementation has so many variations that can be made on it. Everything done up-front at startup means you only need to make one pass to display it.

hth
F451

soconne
soconne
Its really easy to perform multi pass texture splatting with OpenGL using texture combiners. For each splat texture, you have the actual texture image and an alpha texture, so you would setup rendering like this:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glActiveTextureARB(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, alphaTexture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE0);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);

glActiveTextureARB(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, baseTexture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE1);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE1);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS);
glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA);

// render your quad
Author Freeworld3Dhttp://www.freeworld3d.org
Nit
Nit
In regards to the usage of texture combiners, can I combine a two different parts of the same texture? To minimize the number of glBindTexture calls that I make, I have all of my terrain textures packed into 1 bigger texture. So my textures are tiled into 1 big texture as:

+---------+---------+---------+|         |         |         ||  tex1   |   ...   |   texN  ||         |         |         ||         |         |         |+---------+---------+---------+


Lets say that tex1 was a grass tile, and texN was a special tile that I wanted to use to specify an alpha channel. Could I use texture combiners to do this or does this only work with combining 1 full texture with another full texture?

EDIT
Question2: Can/how-do combiners work with vertex arrays/vbos?
soconne
soconne
Yes, you can use the same texture for everything. As long as you specify the correct texture coordinates for your quad.

As for VBO's and vertex arrays, this does not effect them at all.
Author Freeworld3Dhttp://www.freeworld3d.org
Nit
Nit
Excellent, I'll take a look at texture combiners. Although I was hoping to get a solution that didn't rely on extensions, I suspect that if a user's card does not support combiners, they probably could do without the extra terrain detail anyway. Their card is probably slow enough that they should be running on the bare minimum.

@Fahrenheit451
Your terrain looks great. I don't think that the method your provided supports my current needs (but it might down the road). I'll eventually purchase that book, and take a look at these methods you describe in more detail.

Thanks for your help.
Nit
Nit
Special thanks to oconnellseanm for your help. There were a few corrections in the code snipped that was posted, and I just wanted to include the final working example.

Errata:
-must call glEnable(GL_TEXTURE_2D) for each texture unit (e.g. after glActiveTextureARB(...) enable GL_TEXTURE_2D)
-For texture unit 1, I needed to use GL_COMBINE rather than GL_REPLACE

Below is my resulting code (i'm drawing in ortho mode here):

void testAlphaMaps(){	float baseCoords[8];	float alphaCoords[8];	CTextureMGR::GetSingleton().getTileTextureCoords(&baseCoords[0], 0, CTextureMGR::TILE_TEX);	CTextureMGR::GetSingleton().getTileTextureCoords(&alphaCoords[0], 0, CTextureMGR::ALPHA_TEX);	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);		glActiveTextureARB(GL_TEXTURE0);	glEnable(GL_TEXTURE_2D);	glBindTexture(GL_TEXTURE_2D, CTextureMGR::ALPHA_TEX);	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE0);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);	glActiveTextureARB(GL_TEXTURE1);	glEnable(GL_TEXTURE_2D);	glBindTexture(GL_TEXTURE_2D, CTextureMGR::TILE_TEX);	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);	glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE1);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE1);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA);	glBegin(GL_TRIANGLES);	{		//0		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, alphaCoords[0], alphaCoords[1]);		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, baseCoords[0], baseCoords[1]);		glVertex2i(100,100);		//2		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, alphaCoords[4], alphaCoords[5]);		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, baseCoords[4], baseCoords[5]);		glVertex2i(200,200);		//3		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, alphaCoords[6], alphaCoords[7]);		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, baseCoords[6], baseCoords[7]);		glVertex2i(200,100);		//0		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, alphaCoords[0], alphaCoords[1]);		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, baseCoords[0], baseCoords[1]);		glVertex2i(100,100);		//1		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, alphaCoords[2], alphaCoords[3]);		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, baseCoords[2], baseCoords[3]);		glVertex2i(100,200);		//2		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, alphaCoords[4], alphaCoords[5]);		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, baseCoords[4], baseCoords[5]);		glVertex2i(200,200);	}	glEnd();	glActiveTextureARB(GL_TEXTURE1_ARB);	glBindTexture(GL_TEXTURE_2D, NULL);	glDisable(GL_TEXTURE_2D);	glActiveTextureARB(GL_TEXTURE0_ARB);	glBindTexture(GL_TEXTURE_2D, NULL);	glDisable(GL_TEXTURE_2D);}
Nit
Nit
Sorry to bump, but I just wanted to share more code for anyone else interested in using combiners/multitexturing using vertex arrays.

//draw a quad composed of 2 triangles with multitextured vertex arrays//NOTE: this is drawn in orthovoid testAlphaMapsVA(){	float baseCoords[8];	float alphaCoords[8];	float tileCoords[8];	CTextureMGR::GetSingleton().getTileTextureCoords(&baseCoords[0], 1, CTextureMGR::TILE_TEX);	CTextureMGR::GetSingleton().getTileTextureCoords(&alphaCoords[0], 0, CTextureMGR::ALPHA_TEX);		glColor4f(1.0f, 1.0f, 1.0f, 1.0f);	GLfloat* texturesAlpha = new GLfloat[12];	GLfloat* texturesBase = new GLfloat[12];	GLfloat* verticies = new GLfloat[12];	int idx =0;	texturesAlpha[idx++] = alphaCoords[0];	texturesAlpha[idx++] = alphaCoords[1];	texturesAlpha[idx++] = alphaCoords[4];	texturesAlpha[idx++] = alphaCoords[5];	texturesAlpha[idx++] = alphaCoords[6];	texturesAlpha[idx++] = alphaCoords[7];	texturesAlpha[idx++] = alphaCoords[0];	texturesAlpha[idx++] = alphaCoords[1];	texturesAlpha[idx++] = alphaCoords[2];	texturesAlpha[idx++] = alphaCoords[3];	texturesAlpha[idx++] = alphaCoords[4];	texturesAlpha[idx++] = alphaCoords[5];	idx = 0;	texturesBase[idx++] = baseCoords[0];	texturesBase[idx++] = baseCoords[1];	texturesBase[idx++] = baseCoords[4];	texturesBase[idx++] = baseCoords[5];	texturesBase[idx++] = baseCoords[6];	texturesBase[idx++] = baseCoords[7];	texturesBase[idx++] = baseCoords[0];	texturesBase[idx++] = baseCoords[1];	texturesBase[idx++] = baseCoords[2];	texturesBase[idx++] = baseCoords[3];	texturesBase[idx++] = baseCoords[4];	texturesBase[idx++] = baseCoords[5];	idx = 0;	verticies[idx++] = 100;	verticies[idx++] = 100;	verticies[idx++] = 200;	verticies[idx++] = 200;	verticies[idx++] = 200;	verticies[idx++] = 100;	verticies[idx++] = 100;	verticies[idx++] = 100;	verticies[idx++] = 100;	verticies[idx++] = 200;	verticies[idx++] = 200;	verticies[idx++] = 200;	glEnable(GL_BLEND);        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);	glActiveTextureARB(GL_TEXTURE0);	glEnable(GL_TEXTURE_2D);	glBindTexture(GL_TEXTURE_2D, CTextureMGR::ALPHA_TEX); 	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE0);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);	glClientActiveTextureARB(GL_TEXTURE0_ARB);	glTexCoordPointer(2,GL_FLOAT,0,texturesAlpha);	glEnableClientState(GL_TEXTURE_COORD_ARRAY);	glActiveTextureARB(GL_TEXTURE1);	glEnable(GL_TEXTURE_2D);	glBindTexture(GL_TEXTURE_2D, CTextureMGR::TILE_TEX);	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);	glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE1);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE1);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);	glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS);	glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA);	glClientActiveTextureARB(GL_TEXTURE1_ARB);	glTexCoordPointer(2,GL_FLOAT,0,texturesBase);	glEnableClientState(GL_TEXTURE_COORD_ARRAY);	glEnableClientState(GL_VERTEX_ARRAY);	glVertexPointer(2, GL_FLOAT, 0, verticies);	//draw 2 triangles, 3 verticies per triangle	glDrawArrays(GL_TRIANGLES, 0, 2*3);	glDisableClientState(GL_VERTEX_ARRAY);	glActiveTextureARB(GL_TEXTURE1_ARB);	glBindTexture(GL_TEXTURE_2D, NULL);	glDisable(GL_TEXTURE_2D);	glClientActiveTextureARB(GL_TEXTURE1_ARB);	glDisableClientState(GL_TEXTURE_COORD_ARRAY);	glActiveTextureARB(GL_TEXTURE0_ARB);	glBindTexture(GL_TEXTURE_2D, NULL);	glDisable(GL_TEXTURE_2D);	glClientActiveTextureARB(GL_TEXTURE0_ARB);	glDisableClientState(GL_TEXTURE_COORD_ARRAY);	cleanArray(texturesAlpha);	cleanArray(texturesBase);	cleanArray(verticies);}


[Edited by - Nit on February 2, 2006 6:16:21 PM]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.