Sign in to follow this  

Optimizing 8 Texture Alpha Blend

This topic is 3029 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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], i);
        glTexCoordPointer(2, GL_FLOAT, 0, &TextureCoordBuffer[i][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!

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
>>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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
a) What you're doing is multitexturing, not alpha blending
b) You don't need 8 tex. coord interpolators
c) You don't need 8 different textures

You said you're recreating WIII's terrain, but if I recall correctly, it used just one texture.

Using many interpolators may become a bottleneck even in high-end gpus (read GPU Programming Guide GeForce 8 and 9 Series, page 26) And texture reads is also another potential bottleneck, given that it consumes a lot of bandwidth. Only high end gpus have lots of available bandwidth.
If they're all 2048x2048x32 bits, that would be 2048x2048x4x8 = 128 MB per frame. At 60fps, that's 7.680 MB/s!

What you need to do instead, is to put this very same shader to render in a simple texture so that you do the blending offline
Then use one single texture and problem solved.
Then you could use another one for bump mapping.

Other stuff:
1) What GPU are you using?
2) Try enabling mipmapping
3) Increase the quality by doing more math on the shaders instead of textures.
4) What are the texture resolutions?

You should use about 3 to 4 textures to pull excellent quality (one or two about 2048x2048, two around 256x256)

Cheers
Dark Sylinc

Share this post


Link to post
Share on other sites
Each texture is only 512x512 resulting in 8mb a frame and 2mb of video memory used (dxt compression). Warcraft 3 does use 8 seperate terrain textures. I have been reverse engineering the Warcraft3 formats to be used in Project Revolution (http://www.moddb.com/mods/project-revolution) for some time now.

Matias Goldberg: Your are suggesting that I render this to a framebuffer and then wrap that texture to the terrain?

Share this post


Link to post
Share on other sites
Quote:

Each texture is only 512x512 resulting in 8mb a frame and 2mb of video memory used (dxt compression)

Well... that's kinda.... low. Are you sure it's the shader the problem?
What's your GPU. What framerate are you getting?
Use a GPU profiler.

Quote:

Warcraft 3 does use 8 seperate terrain textures

Yes, but I highly doubt they were all 8 being blended in real time.

Quote:

Matias Goldberg: Your are suggesting that I render this to a framebuffer and then wrap that texture to the terrain?

Indeed.
Rendering to the framebuffer should only be made once.

Quote:
rbarris
WarCraft III doesn't know what a shader is. It uses multiple passes

True. I used to play WIII in a PII 350Mhz with a Sis620. Although kinda slow, it was still playable on the small maps.
That card didn't support single-pass multitexturing so that leaves multi-pass. There's no way that card could handle the terrain at any playable speed with 8 passes. It was very crappy.

Cheers
Dark Sylinc

Share this post


Link to post
Share on other sites
Quote:
Original post by rbarris
WarCraft III doesn't know what a shader is. It uses multiple passes.

well that was my second suggestion :)

I think The OP is of the belief creating an ubershader is gonna be quicker when its not

Share this post


Link to post
Share on other sites
So how low are your FPS?

I have some terrain rendering that uses 16 textures per pass and with a GF8800 640MB card I would get 60fps IIRC on a 512x512 or 1024x1024 terrain map that was brute force rendered... I can't remember as its been so long since I ran that code to see my FPS.

In any case it was smooth and looked real good.

You don't need 8 sets of texture coordinates IIRC. Why not calculate them in the VS? Seems wasteful to pass 7 extra sets for nothing...

Update: IIRC that included a skydome, water rendering with refraction and reflection passes also using GLSL, and shadow mapping.

[Edited by - MARS_999 on August 21, 2009 8:38:16 PM]

Share this post


Link to post
Share on other sites
If you think that a terrain material is just a simple texture, the same simple shading method for all materials, then you can do multitexturing in the shader. In this case, write a shader for 1 texture, for 2 textures, for 3 textures etc. as zedz suggested in the above post.

However, if you want to use a bit more advanced techniques like parallax mapping etc and different shading techniques for different terrain materials, one pass shader can be very limited. In this case, you can use the framebuffer to composite different materials. You can do it using alpha-blending and multipass, this is used in many games.

Share this post


Link to post
Share on other sites

This topic is 3029 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this