Optimizing 8 Texture Alpha Blend

Started by
17 comments, last by the_flame 14 years, 7 months ago
Hello, I have a terrain system that alpha blends 8 texture layers using a GLSL shader. I plan to add parralax mapping to the shader, but cannot until the current shader can give decent performance. The current shader uses the alpha values between all 8 textures to blend the terrain. It has been giving really poor performance Here is the fragment shader:

[start]
uniform sampler2D Texture0;
uniform sampler2D Texture1;
uniform sampler2D Texture2;
uniform sampler2D Texture3;
uniform sampler2D Texture4;
uniform sampler2D Texture5;
uniform sampler2D Texture6;
uniform sampler2D Texture7;

varying vec4 normal;

vec4 GenerateTerrainColor() {
	vec4 outcolor;
	vec4 c1, c2;
	
  	c1 = texture2D(Texture0, gl_TexCoord[0].st);
  	c2 = texture2D(Texture1, gl_TexCoord[1].st);
	outcolor = mix(c1, c2, c2.a);
	
	c1 = outcolor;
  	c2 = texture2D(Texture2, gl_TexCoord[2].st);
	outcolor = mix(c1, c2, c2.a);
	
	c1 = outcolor;
  	c2 = texture2D(Texture3, gl_TexCoord[3].st);
	outcolor = mix(c1, c2, c2.a);
	
	c1 = outcolor;
  	c2 = texture2D(Texture4, gl_TexCoord[4].st);
	outcolor = mix(c1, c2, c2.a);
	
	c1 = outcolor;
  	c2 = texture2D(Texture5, gl_TexCoord[5].st);
	outcolor = mix(c1, c2, c2.a);
	
	c1 = outcolor;
  	c2 = texture2D(Texture6, gl_TexCoord[6].st);
	outcolor = mix(c1, c2, c2.a);
	
	c1 = outcolor;
 	c2 = texture2D(Texture7, gl_TexCoord[7].st);
	outcolor = mix(c1, c2, c2.a);
	
	return outcolor;
}

void main() {
    vec3 n = normalize(normal.xyz);

    float nDotL = max(0.0, dot(n, gl_LightSource[0].position.xyz));
        
    vec4 ambient = gl_FrontLightProduct[0].ambient;
    vec4 diffuse = gl_FrontLightProduct[0].diffuse * nDotL;
    vec4 color = gl_FrontLightModelProduct.sceneColor + ambient + diffuse;  
	
	gl_FragColor = color * GenerateTerrainColor();
}

[end]
Here is the vertex shader

[start]

varying vec4 normal;

void main() {
	normal.xyz = normalize(gl_NormalMatrix * gl_Normal);
    normal.w = gl_Vertex.y;
	
	gl_TexCoord[0].st = gl_MultiTexCoord0.st;
	gl_TexCoord[1].st = gl_MultiTexCoord1.st;
	gl_TexCoord[2].st = gl_MultiTexCoord2.st;
	gl_TexCoord[3].st = gl_MultiTexCoord3.st;
	gl_TexCoord[4].st = gl_MultiTexCoord4.st;
	gl_TexCoord[5].st = gl_MultiTexCoord5.st;
	gl_TexCoord[6].st = gl_MultiTexCoord6.st;
	gl_TexCoord[7].st = gl_MultiTexCoord7.st;
		
	gl_Position = ftransform();
} 

[end]
And here is the C+ code associated with rendering the terrain:

void Terrain::renderTerrain() {
    glColor4ub(255, 255, 255, 255);

    useShaderProgram(terrainShader);
    setShaderConstant1i(terrainShader, "Texture0", 0);
    setShaderConstant1i(terrainShader, "Texture1", 1);
    setShaderConstant1i(terrainShader, "Texture2", 2);
    setShaderConstant1i(terrainShader, "Texture3", 3);
    setShaderConstant1i(terrainShader, "Texture4", 4);
    setShaderConstant1i(terrainShader, "Texture5", 5);
    setShaderConstant1i(terrainShader, "Texture6", 6);
    setShaderConstant1i(terrainShader, "Texture7", 7);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);

    for (s32 i = 0; i < 8; i++) {
        enableTextureLayer(TerrainTilesets, i);
        glTexCoordPointer(2, GL_FLOAT, 0, &TextureCoordBuffer[0]);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    }

    glNormalPointer(GL_FLOAT, 0, NormalBuffer);

    glVertexPointer(3, GL_FLOAT, 0, VertexBuffer);

    glDrawElements(GL_QUADS, IndexCount, GL_UNSIGNED_INT, IndexBuffer);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);

    disableAllTextureLayers();

    useShaderProgram(0);
}
Any ideas on how to optimize this shader, or re-implement it in a faster way? Thanks!
Advertisement
One way would be to limit the number of textures per fragment (or even better per triangle).

Usually no more than 4 terrain textures are blended at one spot, on places where more are blended together, the visible artifact would be minimal. These cases can be easily found, and if they are a serious issue, fixed by a repaint (or a special texture limit brush in the terrain editor).
Large artifacts to occur when leaving out a single tile. Here is a screenshot for an example: http://img232.imageshack.us/img232/7328/holes.jpg

Would it be faster to have OpenGL blend the alpha layers? If so, I am not sure how to do it...
Do you really need 8 unique UV sets? Profile this batch and figure out why it's slow. You might be killing yourself with the number of interpolators. If so, can you make do with 1 UV set but 8 scale+offsets that can just be fragment shader constants?

Also be careful of the texture size, try to get all 8 able to fit into your card's texture cache.
It does need 8 separate uv sets. Each texture is broken down into a series of sub tiles which are used for terrain transitions. Here is a sample texture: http://img15.imageshack.us/img15/499/ashworldbrokenrock.jpg
If you're blending 8 textures, try sending in a 8 tilable base textures that share UVs and maybe support scale+offset for variation, and then have 2 mask textures, 8 color channels that give you a weight between 0 and 1 of each of the base textures. Then just blend. Paint the transitions into the mask texture.

>>setShaderConstant1i(terrainShader, "Texture0", 0); <-- u only need to set this once, not every frame

use a normal map will look much better than what u have at the moment.

perhaps look into megatexture

a much faster method than what u have there is to have a different shader depending on the number of different textures in a terrain block, eg have a shader that deals with 1 texture + one that deals with 2 textures, 3 texture etc.

another method is to not use multitexture at all + just blend each layer over the other one
What you are doing is multitexturing rather than alpha-blending.
Alpha-Blending is done in the framebuffer.
Multitexturing like you are doing can be very limited, it can be difficult especially if you want to add different shading techniques for different terrain materials like parallax mapping etc.
I have looked into texture splatting, and mega textures. Neither of them provide the visual quality my superiors would like to have.

This method that I am trying to replicate was first introduced in Warcraft 3, then again in the Frozen Throne. However, War3/TFT did not bump map the terrain.

All in all I am stuck with having to blend 8 textures via their alpha channels.

Perhaps if I used 3D textures, or where to have OpenGL blend the layers? However I would need a set of texture coordinates for each layer anyways...
You only need 1-2 reads if your not greedy and think out a way that allows you to keep maximum variations with minimal texture reads.

This image consists of only one texture read for diffuse. Just because i blended common blends together in texture. I can add extra texture read if i need more transitions.

Just make separate vertex/index buffers and shaders for each amount of textures needed. Also stupid kida thing you have is not having all tilesets in one big atlas texture...

http://img22.imageshack.us/img22/2609/83759770.jpg

Also usually you would want all pairs of textures than can blend together to be actually painted not just blended. Because blend just looks stupid and unrealistic in Warcraft sometimes.

This topic is closed to new replies.

Advertisement