problem with heightmapping

Started by
4 comments, last by skytiger 11 years, 10 months ago
I am trying to do some heightmapping with XNA. Basically I'm making a grid of verticies and then linking them together with indicies to form triangles. I then read the red values from all the pixels in a JPG or PNG, divide by 255, multiply by a scale factor and then set each of the 'y' value in the verticies in the grid to that value...giving a rudimentary heightmap. My problem is that the geometry does not look quite right in the render. Also if there is a better way to generate terrain I would like to hear it...because I can't go beyond a 512x512 heightmap without exceeding XNA's max triangle count. I know terrain generation with heightmaps is an ancient problem...and I'm hoping there is a better way to do what I am trying to do. Any ideas on how to make this work better (at least from a geometry standpoint).


The code I use to generate the triangle grid and read the heightmap image:

private float[] getVertexYFromImage(Texture2D displacementmap, int scalefactor)
{
Color[] pixels = new Color[displacementmap.Width * displacementmap.Height];
displacementmap.GetData<Color>(pixels);
float[] vertexy = new float[pixels.Length];
for (int cursor = 0; cursor < pixels.Length; cursor++)
{
//use red channel to get vert height (y)
vertexy[cursor] = ((float)pixels[cursor].R / 255.0f) * scalefactor;
}
return vertexy;
}
private void getTerrainVertexIndexData(int size, int spread, out int[] indexData, out VertexPositionNormalTexture[] vertexData)
{
vertexData = new VertexPositionNormalTexture[size*size];
indexData = new int[3 * (2 * (size - 1)) * (size - 1)];
//get height data for verticies
float[] vertexYData = getVertexYFromImage(displaceT2D, 32);
//build grid of verts
int xcounter = 0;
int zcounter = 0;
int x_vert = 0;
float y_vert = 0;
int z_vert = 0;
float u = 0.0f;
float v = 0.0f;
for (int index = 0; index < vertexData.Length; index++)
{
y_vert = vertexYData[index];
vertexData[index].Position = new Vector3(x_vert, y_vert, z_vert);
vertexData[index].Normal = new Vector3(0.0f, 0.0f, 0.0f);
vertexData[index].TextureCoordinate = new Vector2(u, v);
if (xcounter < (size - 1))
{
x_vert = x_vert + spread;
xcounter++;
}
else
{
x_vert = 0;
xcounter = 0;
z_vert = z_vert + spread;
zcounter++;
}
u = (float)xcounter / (size - 1);
v = (float)zcounter / (size - 1);
}
//build indixies to form triangles from verts
int icounter = 0;
int cursorx = 0;
int skipcounter = size-1;
for (int currentstrip = 0; currentstrip < (size - 1); currentstrip++)
{
for (int count = 0; count < (size - 1); count++)
{
indexData[icounter] = cursorx;
indexData[icounter + 1] = cursorx + 1;
indexData[icounter + 2] = cursorx + 1 + (size);
icounter = icounter + 3;
indexData[icounter] = cursorx;
indexData[icounter + 1] = cursorx + 1 + size;
indexData[icounter + 2] = cursorx + size;
icounter = icounter + 3;
cursorx++;
}
if (cursorx == skipcounter)
{
cursorx++;
skipcounter = skipcounter + size;
}
}
//calculate normals for smooth shading
for(int counter=0; counter<indexData.Length / 3; counter++)
{
Vector3 firstvec = vertexData[indexData[counter * 3 + 1]].Position - vertexData[indexData[counter * 3]].Position;
Vector3 secondvec = vertexData[indexData[counter * 3]].Position - vertexData[indexData[counter * 3 + 2]].Position;
Vector3 normal = Vector3.Cross(firstvec, secondvec);
normal.Normalize();
vertexData[indexData[counter * 3]].Normal += normal;
vertexData[indexData[counter * 3 + 1]].Normal += normal;
vertexData[indexData[counter * 3 + 2]].Normal += normal;
}
for (int counter = 0; counter < vertexData.Length; counter++)
{
vertexData[counter].Normal.Normalize();
}
}


Thanks,
John
Advertisement

JPG or PNG

For accurate data you need a lossless compression format, so avoid JPG and stick to lossless compressed PNG to avoid artifacts.


because I can't go beyond a 512x512 heightmap without exceeding XNA's max triangle count

Instead of creating your whole map at once, subdivide it in patches (i.e. 64x64) and create them on-the-fly once you get close to them.
you could also generate your heightmap/noise in an application and store the heights across all the channels giving you much greater range of depth:

http://www.gamerendering.com/2008/09/25/packing-a-float-value-in-rgba/

I think that is derived from a post on this forum anyway, should help smooth out the results!
I figured out how to magnfiy the problem. I just increased my red channel scale factor and this is what happened:

terrainproblem.jpg

I found a vertex smoothing algorithm. I'll let you know how it works out...

http://www.nfostergames.com/Lessons/TerrainSmoothing.htm
fixed...

terrainproblem_fixed.jpg

and some of the code that I used (4 passes of it seems to be about right), in the rare chance anyone else runs into the same set of problems...


private float[] getSmoothedVertexData(float[] yvalues, int size)
{
for (int count = 0; count < yvalues.Length; count++)
{
float adjSum = 0.0f;
float adjAvg = 0.0f;
if (count == 0) //left bottom corner
{
adjSum = yvalues[1] + yvalues[size] + yvalues[size+1];
adjAvg = adjSum / 3.0f;
yvalues[count] = (yvalues[count] + adjAvg) / 2.0f;
}
else if(count == (size - 1)) //right bottom corner
{
adjSum = yvalues[size - 2] + yvalues[(size -2)+size] + yvalues[(size -1)+size];
adjAvg = adjSum / 3.0f;
yvalues[count] = (yvalues[count] + adjAvg) / 2.0f;
}
else if(count == (yvalues.Length - size)) //left top corner
{
adjSum = yvalues[(yvalues.Length - size) + 1] + yvalues[(yvalues.Length - size) - size] + yvalues[(yvalues.Length - size) - size + 1];
adjAvg = adjSum / 3.0f;
yvalues[count] = (yvalues[count] + adjAvg) / 2.0f;
}
else if(count == (yvalues.Length - 1)) //right top corner
{
adjSum = yvalues[(yvalues.Length - 2)] + yvalues[(yvalues.Length - 1) - size] + yvalues[(yvalues.Length - 1) - size - 1];
adjAvg = adjSum / 3.0f;
yvalues[count] = (yvalues[count] + adjAvg) / 2.0f;
}
else if (count > 0 && count < size) //bottom strip
{
adjSum = yvalues[count - 1] + yvalues[count + 1] + yvalues[count + size] + yvalues[count + size + 1] + yvalues[count + size - 1];
adjAvg = adjSum / 5.0f;
yvalues[count] = adjAvg;
}
else if (count > (yvalues.Length - size) && (count < (yvalues.Length - 1))) //top strip
{
adjSum = yvalues[count - 1] + yvalues[count + 1] + yvalues[count - size] + yvalues[count - size - 1] + yvalues[count - size + 1];
adjAvg = adjSum / 5.0f;
yvalues[count] = adjAvg;
}
else if ((count % size) == 0) //left strip
{
adjSum = yvalues[count + 1] + yvalues[count - size] + yvalues[count + size] + yvalues[count + size + 1] + yvalues[count - size + 1];
adjAvg = adjSum / 5.0f;
yvalues[count] = adjAvg;
}
else if ((count + 1) % size == 0) //right strip
{
adjSum = yvalues[count - 1] + yvalues[count - size] + yvalues[count + size] + yvalues[count - size - 1] + yvalues[count + size - 1];
adjAvg = adjSum / 5.0f;
yvalues[count] = adjAvg;
}
else
{
int[] adjacentVerts = getAdjacentVertPositions(count, size);
for (int index = 0; index < 8; index++)
{
adjSum = adjSum + yvalues[adjacentVerts[index]];
}
adjAvg = adjSum / 8.0f;
yvalues[count] = (yvalues[count] + adjAvg) / 2.0f;
}
}
return yvalues;
}
private int[] getAdjacentVertPositions(int arrayPos, int size)
{
int[] adjacentVertPositions = new int[8];
adjacentVertPositions[0] = arrayPos - 9;
adjacentVertPositions[1] = arrayPos - 8;
adjacentVertPositions[2] = arrayPos - 7;
adjacentVertPositions[3] = arrayPos + 1;
adjacentVertPositions[4] = arrayPos + 9;
adjacentVertPositions[5] = arrayPos + 8;
adjacentVertPositions[6] = arrayPos + 7;
adjacentVertPositions[7] = arrayPos - 1;
return adjacentVertPositions;
}
http://skytiger.wordpress.com/2010/11/28/xna-large-terrain/

This topic is closed to new replies.

Advertisement