Lighting Problems

In my current project I am rendering terrain from a height map but when I light the terrain I am getting a striped look. Check out the screenshot to see what I mean. I have even enabled blending, polygon smoothing and and using a GL_SMOOTH shade model but nothing seems to effect the stripes running down the terrain. Here are my surface normal calculations:

private void computeSurfaceNormals(float[][] surfaceNormals)
{
	int surfaceNormalsIndex = 0;
	
	for(int y = -1; y <= heightMapLength - 1; y++)
	{
		float height1, height2, height3;
		for(int x = -1; x <= heightMapWidth - 1; x++)
		{
			if(x == -1 || x == heightMapWidth - 1 || y == -1 || y == heightMapLength - 1)
			{
				surfaceNormals[surfaceNormalsIndex++] = new float[] {0.0f, 1.0f, 0.0f};
				surfaceNormals[surfaceNormalsIndex++] = new float[] {0.0f, 1.0f, 0.0f};
			}
			else
			{
				height1 = heightMapData[(y*heightMapWidth)+x];
				height2 = heightMapData[((y+1)*heightMapWidth)+x];
				height3 = heightMapData[(y*heightMapWidth)+x+1];
				
				float[] vec1 = {0.0f, height2 - height1, 1.0f};
				float[] vec2 = {1.0f, height3 - height1, 0.0f};
				
				surfaceNormals[surfaceNormalsIndex++] = GameUtil.crossProduct(vec1, vec2);
				
				height1 = height3;
				height3 = heightMapData[((y+1)*heightMapWidth)+x+1];
				
				vec1[1] = height2 - height1;
				vec2[1] = height3 - height1;
				
				surfaceNormals[surfaceNormalsIndex++] = GameUtil.crossProduct(vec1, vec2);
			}	//  else
		}	//  for
	}	  // for
}	//  computeSurfaceNormals

I then normalize the surface normals and calculate the vertex normals with:

private void computeVertexNormals(float[][] surfaceNormals)
{
	int triRowOffset = ((heightMapWidth+1)*2);
	int triIndex     = 0;
	int vertexIndex  = 0;
	float[] currentNormal = new float[3];
	float[][] tempVec = new float[6][3];
	for(int z = 0; z < heightMapLength; z++)
	{
		for(int x = 0; x < heightMapWidth; x++)
		{
			tempVec[0] = surfaceNormals[triIndex];
			tempVec[1] = surfaceNormals[triIndex + 1];
			tempVec[2] = surfaceNormals[triIndex + 2];
			tempVec[3] = surfaceNormals[triIndex + triRowOffset];
			tempVec[4] = surfaceNormals[triIndex + triRowOffset + 1];
			tempVec[5] = surfaceNormals[triIndex + triRowOffset + 2];
			
			currentNormal[0] = (tempVec[0][0] + tempVec[1][0] + tempVec[2][0] + tempVec[3][0] + tempVec[4][0] + tempVec[5][0]) / 6.0f;
			currentNormal[1] = (tempVec[0][1] + tempVec[1][1] + tempVec[2][1] + tempVec[3][1] + tempVec[4][1] + tempVec[5][1]) / 6.0f;
			currentNormal[2] = (tempVec[0][2] + tempVec[1][2] + tempVec[2][2] + tempVec[3][2] + tempVec[4][2] + tempVec[5][2]) / 6.0f;
			
			vertexNormals[vertexIndex][0] = currentNormal[0];
			vertexNormals[vertexIndex][1] = currentNormal[1];
			vertexNormals[vertexIndex][2] = currentNormal[2];
			
			vertexIndex++;
			triIndex += 2;
		}	//  for
		triIndex += 2;
	}	//  for
}	//  computeVertexNormals

Anyone know what I am doing wrong here? Thanks for any help in advance.

The only really wrong thing i can see is that your “normalize” code isn’t normalizing your normal at all.

Normalizing s done this way:


float len = sqrt(x*x+y*y+z*z);
x /= len;
y /= len;
z /= len;

Further I’d recommend you to make some class to store 3 float fields - x y z - instead of fiddling around with float[3]s.

Hey,

I normalize the surface normals before I use them to calculate the vertex normals. I didn’t post that code because I know it is working properly as I have tested it against other functions that are known to work. Any reason you can think of as to why I am getting this striping artifact?

currentNormal (float[3]) can’t reasonably be a valid normal after those calculations.

re-normalize the result.

Riven,

Actually I have tried that as well, I will say I have tried many different methods: normalizing surface normals and vertex normals, normalizing just surface normals, normalizing just vertex normals, normalizing just vertex normals without averaging the normals of the faces and instead just adding them all up, ect, ect. All of this only contributes to more or less realistic looking lighting on the terrain. None of these has effected the striped “bug” if you will in my code. For an in depth article on different ways\thoughts on lighting terrain see this article which highlights many algos on lighting and normal computations and is pretty well written. You and I are thinking along the same lines though because that is what led me to try a bunch of different normal algos but as I stated the different approaches don’t effect the stripes in the terrain, only how realistic the lighting and shading look, which leads me to believe the problem lies elsewhere.

Ok, this time I also read the first part of your source-code, and found an error there too, which directly links to the output of your screenshot:

You are calculating normals using only 2 samples per normal, instead of the required three (to find the normal of a face).

You actually see that in your screenshot, as you only get smooth shades on 1 axis - you call it strides.

This is (pseudo) code calculate a normal for a 3d face:


      Vec3 a = new Vec3(......);
      Vec3 b = new Vec3(......);
      Vec3 c = new Vec3(......);

      Vec3 q = new Vec3(b.x - a.x, b.y - a.y, b.z - a.z);
      Vec3 p = new Vec3(c.x - a.x, c.y - a.y, c.z - a.z);

      Vec3 n = crossProduct(q, p);
      n.normalize();

Dear isolier,
If I understand your code correctly, i saw something fishy in this part.

       Firstly i assume from your code that, your render engine render each triangle seperately (that mean you doesn't use triangle strip). Secondly i assume your normal calculation is aready correct. Thirdly i assume this part of your code i just quote is for average normal.

       If my assumption are correct, you doesn't average an normal from exactly same vertex but you just average normal on vertex that have same position on triangle. That mean in your program, the same vertex (vertex that have same 3d position) on each triangle doesn't have same normal.

        So my suggestion is, you should calculate average normal on exactly same vertex that will give you correct result. But if you can't you should have only one normal per vertex. (I mean if you have 100x100 grid terran you should store only 10000 normal vector.)  Then use index to render it all. This second way may give you wrong result if you can't calculate (include but not limit to average) vertex correctly but it should solve your "strip look" problem. (I assume you set index array correctly  ;) )