Multitextured Terrain

Started by
14 comments, last by Boruki 18 years, 7 months ago
Hello, I am trying to get blended multitextured terrain in my current project but I am at a loss as to how to get their. I have googled and searched these forums but can't seem to come up with anything that might help me out. I am currently only able to texture the terrain one particular texture but I have five textures: water, dirt, grass, sand, rock and snow. I am not sure how to switch between textures depending on the height of the terrain. Should I use each texture in its own texture unit? Or would that combine all of the textures into the final output and basically be all black? Or do I just use one texture unit for all the textures? Anyhow, any help you can provide whether it be links to a site where this is covered or anything will be very much appreciated. So, thank you in advance.
Advertisement
My original attempt at multitexturing terrain was to use 3D Textures. Although as you can guess it will have its limitations. This is good if you just want different heights to be different textures. Shouldn't pose too many problems - basically the UV components will map to the X & Z axis of your terrain, and the W component will map to height (Y) of your terrain.

In my terrain demo I use texture splatting. Each different terrain texture has its own alpha map. The texture is tiled across the terrain, where as the alpha map covers the entire area of the height map. To be able to render out a fair few layers you need to do a fair amount of optimisation, if you choose this method of rendering you'll find out soon enough. Its all good fun though. I'll post some code up if you get stuck anywhere, but seriously, try googling "texture splatting" and see what you come up with.
Adventures of a Pro & Hobby Games Programmer - http://neilo-gd.blogspot.com/Twitter - http://twitter.com/neilogd
Hope this helps:
http://www.cbloom.com/3d/techdocs/splatting.txt
http://www.gamedev.net/reference/articles/article2246.asp
I did it with a Groundblending shader.
For every vertex of the terrain pass five textures and an alpha value for each texture to the shader.
Make sure the sum of all five alpha values equals 1 and then just blend the textures together.
If you are interested, I can post some code some time later, since I'm at work now
Quote:Original post by Hydrael
I did it with a Groundblending shader.
For every vertex of the terrain pass five textures and an alpha value for each texture to the shader.
Make sure the sum of all five alpha values equals 1 and then just blend the textures together.
If you are interested, I can post some code some time later, since I'm at work now


I am very interested actually. Also, did you use vertex arrays for your renderer or display lists, as my current implementation uses display lists because when I was using vertex arrays it was much, much slower. Also, my thoughts on how to accomplish this where very similar to yours, except I was trying to figure out a way to do it without shaders. That is, just have an alpha value for each texture depending on the height of the current vertex. Then just use multitextures to render all the textures at once iterating over the terrain. I know nothing of shaders to be honest, if there isn't a way to do it without shaders, how long does it take to get up and running with a working knowledge of them in your opinion?
Quote:Original post by Isolier
I am very interested actually. Also, did you use vertex arrays for your renderer or display lists, as my current implementation uses display lists because when I was using vertex arrays it was much, much slower. Also, my thoughts on how to accomplish this where very similar to yours, except I was trying to figure out a way to do it without shaders. That is, just have an alpha value for each texture depending on the height of the current vertex. Then just use multitextures to render all the textures at once iterating over the terrain. I know nothing of shaders to be honest, if there isn't a way to do it without shaders, how long does it take to get up and running with a working knowledge of them in your opinion?


Sorry for answering that late, but I was kind of under stress yesterday - I will post the code about 6pm today (GMT).
I am using indexed vertex arrays for rendering my terrain, but that basically shouldn't matter.
As for your question regarding an implementation without shaders:
I wouldn't know how to do it without them, because that groundblending shader I used as a "I now will learn GLSL by using it" project - so I actually never tried it without them. But I guess without shaders you will have to do your rendering in several passes (not 100% sure on that though).
But to calm you: shaders aren't that complicated as it might seem (at least the basics). It took me about 2-3 weeks from scratch until that shader was done.
A great source for GLSL tutorials lies here
When I get home, I will post all of the relevant code. If you are lucky, it could work by just copy/pasting it...if not, just keep asking then - I'll help as good as I can ;)

Greets

Chris
Hello,

I have figured out how to get different levels of transparency using texture combining. However, I was wondering how I could go about rendering the transitions between textures. Should I collect all of the vertices of a certain height in an array and set the level of blending I want for that height then render those triangles with glDrawElements()?

[Edited by - Isolier on September 18, 2005 3:40:30 AM]
Quote:Original post by Isolier
Hello,

I have figured out how to get different levels of transparency using texture combining. However, I was wondering how I could go about rendering the transitions between textures. Should I collect all of the vertices of a certain height in an array and set the level of blending I want for that height then render those triangles with glDrawElements()?


I love it when people reply to their thread with "yeah thanks, figured it out.. byebye". It would make these forums a lot more useful if people actually posted their solutions for others to learn from.
Is it not possible to use both a display list and a vertex array?
AfroFire | Brin"The only thing that interferes with my learning is my education."-Albert Einstein
Quote:Original post by Boruki
I love it when people reply to their thread with "yeah thanks, figured it out.. byebye". It would make these forums a lot more useful if people actually posted their solutions for others to learn from.


I think you could have made that suggestion with a little less sarcasm. It would also make these forums more useful if more people would talk to each other with the same respect that one would afford someone in the real world, without the security blanket of an internet forum.

So, here is what I have so far: I am using texture combining and interpolation, however, I am basically just doing what I see best in terms of the method of transitioning between different textures, which, to be honest probably isn't all that great but I am going off my own limited knowlegde as I can't really find much in the way of tutorials or anything that might set me straight. So I have two texture units enabled, texture unit 0 and texture unit 1. Texture unit 0 is sort of the base texture I guess you could call it and texture unit 1 is what gets blended with texture unit 0. What I do is create one large vertex array, then from this vertex array I scan through and create a new vertex array with all the triangles above a certain height. All of the others go into another vertex array that are below this height. Finally, I render the lower vertex array with the texture from texture unit 0 with texture unit 1 trancparency all the way up so that you can't see any of texture unit 1 texture. Then I change the trancparecy of texture unit 1 so that a little of unit 1 texture will be shown on top of unit 0 and render the upper vertex array. Now on to the actual code:

This is how I set up texture unit 0 and texture unit 1. Now I am not going to give a full blown explanation on the texture combiner and the interpolation combine methode because there are many resources out on the net that do a much better job at explaining these sorts of things than I can. However, basically what happens is OpenGL takes the color from the texture bound to texture unit 0 as the first argument(GL_PREVIOUS works for this because we are working with texture unit 1 but you can also specify GL_TEXTURE0), then takes the texture from the current texture bound to the current texture unit(which again is texture unit 1, notice we use GL_TEXTURE as the source which tells opengl to use the current texture), and finally we use GL_CONSTANT as the final source with an operand of GL_SRC_ALPHA instead of GL_SRC_COLOR because we want to use the alpha component of the current texture units texture environment, not texture image, read carefully, the current texture units texture environment's alpha channel to interpolate between how much of texture unit 1 gets displayed on top of texture unit 0. We attain this by setting the third source(SOURCE2) to GL_CONSTANT, then we can manipulate the texture environment with calls to glTexEnv().
gl.glClientActiveTexture(GL.GL_TEXTURE0);gl.glActiveTexture(GL.GL_TEXTURE0);gl.glEnable(GL.GL_TEXTURE_2D);gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, texCoordBuffer);gl.glBindTexture(GL.GL_TEXTURE_2D, textureObjects[DIRT_TEXTURE_ID]);gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);gl.glClientActiveTexture(GL.GL_TEXTURE1);gl.glActiveTexture(GL.GL_TEXTURE1);gl.glEnable(GL.GL_TEXTURE_2D);gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, texCoordBuffer);gl.glBindTexture(GL.GL_TEXTURE_2D, textureObjects[GRASS_TEXTURE_ID]);gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_COMBINE);gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB, GL.GL_INTERPOLATE);gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_SOURCE0_RGB, GL.GL_PREVIOUS);gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_OPERAND0_RGB, GL.GL_SRC_COLOR);gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_SOURCE1_RGB, GL.GL_TEXTURE);gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_OPERAND1_RGB, GL.GL_SRC_COLOR);gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_SOURCE2_RGB, GL.GL_CONSTANT);gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_OPERAND2_RGB, GL.GL_SRC_ALPHA);


This the code that gets the "upper"(grassTris) and "lower"(sandTris) vertex arrays after the main vertex array(vertexList) has been created.
for(int i = 0; i < vertexList.length-18; i+=18){	if(vertexList[i+1] < (float)(100.0f/256.0f)*(float)MAX_HEIGHT)	{		sandTris[sandIndex]   = vertexList;		sandTris[sandIndex+1] = vertexList[i+1];		sandTris[sandIndex+2] = vertexList[i+2];		sandTris[sandIndex+3] = vertexList[i+3];		sandTris[sandIndex+4] = vertexList[i+4];		sandTris[sandIndex+5] = vertexList[i+5];		sandTris[sandIndex+6] = vertexList[i+6];		sandTris[sandIndex+7] = vertexList[i+7];		sandTris[sandIndex+8] = vertexList[i+8];		sandTris[sandIndex+9]   = vertexList[i+9];		sandTris[sandIndex+10] = vertexList[i+10];		sandTris[sandIndex+11] = vertexList[i+11];		sandTris[sandIndex+12] = vertexList[i+12];		sandTris[sandIndex+13] = vertexList[i+13];		sandTris[sandIndex+14] = vertexList[i+14];		sandTris[sandIndex+15] = vertexList[i+15];		sandTris[sandIndex+16] = vertexList[i+16];		sandTris[sandIndex+17] = vertexList[i+17];		sandIndex += 18;	}	//  if	else	{		grassTris[grassIndex]   = vertexList;		grassTris[grassIndex+1] = vertexList[i+1];		grassTris[grassIndex+2] = vertexList[i+2];		grassTris[grassIndex+3] = vertexList[i+3];		grassTris[grassIndex+4] = vertexList[i+4];		grassTris[grassIndex+5] = vertexList[i+5];		grassTris[grassIndex+6] = vertexList[i+6];		grassTris[grassIndex+7] = vertexList[i+7];		grassTris[grassIndex+8] = vertexList[i+8];		grassTris[grassIndex+9]   = vertexList[i+9];		grassTris[grassIndex+10] = vertexList[i+10];		grassTris[grassIndex+11] = vertexList[i+11];		grassTris[grassIndex+12] = vertexList[i+12];		grassTris[grassIndex+13] = vertexList[i+13];		grassTris[grassIndex+14] = vertexList[i+14];		grassTris[grassIndex+15] = vertexList[i+15];		grassTris[grassIndex+16] = vertexList[i+16];		grassTris[grassIndex+17] = vertexList[i+17];		grassIndex += 18;				}	//  else}	//  for


Now I render all this with the following, however notice the use of glTexEnvfv to change the alpha value of texture unit 1 so that more or less of the texture is actually displayed. Also, you will notice I reference grassBuffer and sandBuffer instead of grassTris and sandTris, this is just a "Javaism" where you have to wrap an array into a "buffer" object so that things work nice, same with the gl.gl* calls, just more Java and JOGL quirks, ignore them.
gl.glClientActiveTexture(GL.GL_TEXTURE1);gl.glActiveTexture(GL.GL_TEXTURE1);gl.glTexEnvfv(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_COLOR, new float[]{0.0f, 0.0f, 0.0f, 1.0f});gl.glVertexPointer(3, GL.GL_FLOAT, 0, sandBuffer);gl.glDrawArrays(GL.GL_TRIANGLES, 0, sandIndex/3);gl.glTexEnvfv(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_COLOR, new float[]{0.0f, 0.0f, 0.0f, 0.5f});gl.glVertexPointer(3, GL.GL_FLOAT, 0, grassBuffer);gl.glDrawArrays(GL.GL_TRIANGLES, 0, grassIndex/3);


Now, the problem I am having right now is my texture coordinates are all jacked up, because I obviously can't use the same set of texture coords I was using when I just render the whole scene at once instead of splitting it up into two vertex arrays, but the code for that is:
float xStep      = WORLD_WIDTH  / heightMapWidth;float zStep      = WORLD_LENGTH / heightMapLength;texCoordList     = new float[(heightMapWidth-1)*(heightMapLength-1)*12];int listIndex    = 0;for(int z = 0; z < heightMapLength - 1; z++){	for(int x = 0; x < heightMapWidth - 1; x++)	{		texCoordList[listIndex]   = (float)x*xStep/textures[0].getWidth();		texCoordList[listIndex+1] =(float)z*zStep/textures[0].getHeight();				texCoordList[listIndex+2] = (float)x*xStep/textures[0].getWidth();		texCoordList[listIndex+3] = (float)(z+1)*zStep/textures[0].getHeight();		texCoordList[listIndex+4] = (float)(x+1)*xStep/textures[0].getWidth();		texCoordList[listIndex+5] = (float)z*zStep/textures[0].getHeight();				texCoordList[listIndex+6] = (float)(x+1)*xStep/textures[0].getWidth();		texCoordList[listIndex+7] = (float)z*zStep/textures[0].getHeight();				texCoordList[listIndex+8] = (float)x*xStep/textures[0].getWidth();		texCoordList[listIndex+9] = (float)(z+1)*zStep/textures[0].getHeight();				texCoordList[listIndex+10] = (float)(x+1)*xStep/textures[0].getWidth();		texCoordList[listIndex+11] = (float)(z+1)*zStep/textures[0].getHeight();				listIndex += 12;	}	//  for}	//  for


Now to finish this off, again my texcoords are all wrong and I don't even know if this is the right way to go about this whole thing, there are probably numerous things I am doing wrong and numerous things that are just flat out stupid, but this was the best I could come up with so far, it works ok but definitely not great because in order to get transitions between the high and low vertex arrays I will need one or two transitional arrays that would have the alpha value of texture unit 1 between 1.0f and 0.5f so that things don't look weird. So there you have it, I don't know if any of it made sense but hopefully it helped someone and hopefully someone will help me because I certainly need it :) Also, I am not going to proof read this so sorry for any gramatical or spelling errors.

This topic is closed to new replies.

Advertisement