GLSL Displacement Mapping

Started by
11 comments, last by sjaakiejj 12 years, 1 month ago
Hi,

I've been trying to perform Displacement Mapping on the GPU using GLSL. The catch is that I'm using a dynamically refined quad-tree to display the terrain that I'm trying to 'displace', and the quads misalign at their borders (They align in the xz plane, just not in the y direction), as you can see in the image I attached.

I'm currently using the following Vertex Shader code:



uniform sampler2D Texture;

void main()
{
gl_TexCoord[0] = gl_MultiTexCoord0;
vec4 position = vec4(gl_Vertex);

vec4 tex = texture2D(Texture, gl_TexCoord[0].st);
pos.y = tex.x * 10.0;
gl_Position = gl_ModelViewProjectionMatrix * position;
}


Each quad has its own sub-texture associated with it, which I clip up on the CPU. (rendering it using the fragment shader gives the right results). The problem, for as far as I can tell, is that the value in "tex" is interpolated somehow (which, given my inexperience with shaders, is probably my use of texture2D in this context). I have however ensured that the pixels on the edges of the textures are identical to their neighbours on the texture patch next to it.

Thanks in advance for your time and help.
Advertisement
First, set your textures to GL_NEAREST, and turn of mip-mapping if you have that on. You have to section the big image. Downsize it by half, section it, downsize, section etc. Because pixels on the edge need to be sampled/averaged with what they are touching. And at the seems, the pixels don't get to take into account their neighbors pixels. Your issue looks a little bit more of an issue than that though.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal


First, set your textures to GL_NEAREST, and turn of mip-mapping if you have that on. You have to section the big image. Downsize it by half, section it, downsize, section etc. Because pixels on the edge need to be sampled/averaged with what they are touching. And at the seems, the pixels don't get to take into account their neighbors pixels. Your issue looks a little bit more of an issue than that though.


Hi,

Thanks for your response. I went over my code to make sure that all the parameters are set correctly, and for as far as I can see they are.

Here is the bit that generates and sends a clipped texture to the GPU:

void applyTexture( unsigned char* data, int w, int h, int n)
{
if( data != NULL ){
/* Generate a texture name and bind the current texture */
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);

/* Replace the color of the object, shading is done in shaders */
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

/* Specify texture mapping parameters */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

/* Load the texture into GPU memory */
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE,
data);

/* Unbind the texture to prevent it from appearing on unexpected objects */
glBindTexture(GL_TEXTURE_2D, 0);
}
}



So from the QuadTree, when I generate 4 new quads, I also clip the texture up into 4 pieces, and send one piece to each of the children. They then just call this method on the grid VB object, and the VB object is rendered as follows:


void render(){
/*
* Draw the object
*/
if(mTexture != 0)
{
glEnable(GL_TEXTURE_2D);
glBindTexture( GL_TEXTURE_2D, mTexture );
}

/* Use the shader */
glUseProgram(shader_id);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
/*
* Bind the VBO
*/
glBindBuffer(GL_ARRAY_BUFFER, mVboObject);


glVertexPointer(3,GL_FLOAT,mVertexStride,0);
glTexCoordPointer(2, GL_FLOAT, mTextureStride, (GLvoid*)(sizeof(float)*6));

glPushMatrix();
glDrawArrays(GL_TRIANGLES, 0, mNumVertices);
glPopMatrix();

glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);

if(mTexture != 0){
glFlush();
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
}

glBindBuffer(GL_ARRAY_BUFFER, 0);
glUseProgram(0);
}



Also, my texture clipping code is as follows:

unsigned char * QuadNode::getTextureRegion(unsigned char* data, int width, int height, int startX, int startY)
{
int nLength = (width/2) * (height/2);
int halfWidth = width / 2;
int addWidth = width / 2;

unsigned char *newData = new unsigned char[nLength];
int currentIndex = 0;
int dataIndex = 0;

for(int i = startX + startY * width; dataIndex < nLength; i++)
{
if(currentIndex >= halfWidth)
{
currentIndex = 0;
i += addWidth;
}
newData[dataIndex] = data;
currentIndex++; dataIndex++;
}

return newData;

}

unsigned char* QuadNode::correctData(unsigned char* data, int width, int height)
{
int horizontal = height / 2;
int vertical = width / 2;
unsigned char* nData = data;
for(int i = 0; i < width; i++)
{
nData[horizontal * width + i] = data[horizontal * width + i];//(data[horizontal * width + i] +
//data[(horizontal+1) * width + i]) / 2;
nData[(horizontal-1) * width + i] = nData[horizontal * width + i];
}

for(int i = 0; i < height; i++)
{
nData[width*i + vertical - mHmN] = (data[width*i + vertical - mHmN]);
nData[width*i + (vertical - mHmN) + 1] = (data[(width*i + vertical - mHmN) + 1]);
nData[width*i + (vertical - mHmN) + 2] = 255;// (data[(width*i + vertical - mHmN) + 2]);
nData[width*i + (vertical - mHmN) + 3] = (data[(width*i + vertical - mHmN) + 3]);

nData[width*i + vertical] = nData[width*i + vertical - mHmN];
nData[width*i + vertical + 1] = nData[(width*i + vertical - mHmN) + 1];
nData[width*i + vertical + 2] = nData[(width*i + vertical - mHmN) + 2];
nData[width*i + vertical + 3] = nData[(width*i + vertical - mHmN) + 3];
}

return nData;
}


Where correctTexture is called before getTextureRegion. I have tested the approach by setting the values as equal to 255 on blue or red, and rendering the result using the fragment shader. The colours are correctly on the two aligned edges, hence why I'm presuming that interpolation is the culprit.
Get rid of the displacement and use a picture instead of a heightmap and just cut that into sections and put them onto grids of flat planes. Make sure your chop up code is correct by verifying that the chopped up texture portions all map to 1 plane with the correct combined image.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal


Get rid of the displacement and use a picture instead of a heightmap and just cut that into sections and put them onto grids of flat planes. Make sure your chop up code is correct by verifying that the chopped up texture portions all map to 1 plane with the correct combined image.


Thanks for the response. I'm a bit unclear as to what you mean. I can think of what you said in one of two ways:
1. Just have a completely flat terrain with a picture on top of it, no height data
2. Use a JPG or BMP image, and each time for each child chop it up into 4 bits and send it to the children, and use that as height-data

1 is not acceptable for my purposes, whereas 2 is exactly what I'm already doing. I have probably misunderstood however.
I'm saying make sure your chop up algorithm is working properly, but cutting up an image of a picture, instead of just cutting up random floating point height data. So say you have a picture of a duck over your whole terrain (could just be black and white as well to make this work easier), it would go through the same chop up algorithm, but you would be able to visually see that the image tiles are generated correctly and placed on the proper terrain chunk. If the end result looks like a duck spread across a bunch of smaller sub-images then you know that split texture function works. Your data in the image just looks way off at the seams.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal


I'm saying make sure your chop up algorithm is working properly, but cutting up an image of a picture, instead of just cutting up random floating point height data. So say you have a picture of a duck over your whole terrain (could just be black and white as well to make this work easier), it would go through the same chop up algorithm, but you would be able to visually see that the image tiles are generated correctly and placed on the proper terrain chunk. If the end result looks like a duck spread across a bunch of smaller sub-images then you know that split texture function works. Your data in the image just looks way off at the seams.


Right, apologies smile.png

I just did that, and the chopping up code seems to be fine, regardless of how close or far away I am from the quads. I've attached the image in case you pick up anything odd that I missed.

This is the reference image I used (as you can see, the squaring is an artefact on the actual image):
http://www.ducklings...mpbell_duck.jpg
So I'm not sure, but my next debug option would be to open your height map up, and delete all the data to black, except for 1 single 1x1 pixel row. This way you can just look at 1 row rather than i giant messed up bumpy terrain, and just look at the edges to see what might be happening.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal


So I'm not sure, but my next debug option would be to open your height map up, and delete all the data to black, except for 1 single 1x1 pixel row. This way you can just look at 1 row rather than i giant messed up bumpy terrain, and just look at the edges to see what might be happening.


So just for clarity's sake, what value does the GLSL function "texture2D" return? Is it an interpolated value over the adjacent pixels, or is it the pixel at that vertex position?

Thanks for the help so far :)
It samples based on glTexParameter, GL_NEAREST means texture2D returns the exact pixel, GL_LINEAR blends the pixel with its neighbors, and so on does more with other parameters.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

This topic is closed to new replies.

Advertisement