# Distinctive triangles and lighting clipping

## Recommended Posts

Hello!

I'm creating a very basic game world in 3D for a school project. Completely unrelated to the assignment, I decided that I wanted to create lighting for my world, to make it look more alive and less monotone and chunky. I've read a few tutorials and done what I thought would be sufficient, and it sort of works. It also sort of looks terrible. See picture:[attachment=19268:world.jpg]

I'm probably missing something really simple, because as far as I can tell it's every other triangle that is either too dark or too bright. What I'd like is a more gradual fade, and perhaps also smoothen the world out in the process. I'm guessing these two are somehow related. Any pointers on what I'd do to make this work?

My world generation code is really long, but the gist of it is:

I read heightdata from an RGB file, using red-channel as height, normalized to 0-20.

Then I generate vertexes, normals and  indices using CCW-winding & triangle-strip.

The full code is here, in all its uncommented glory. At the end is also the code for drawing, and the code for the 'sun'.

void GameMap::setupVertices(){

//textureNum is the identifier generated by glGenTextures

//Bind the texture again, and extract the needed data
glBindTexture(GL_TEXTURE_2D, textureNum);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
GLint i = height*width;
GLubyte * imageData = new GLubyte[i+1];
glGetTexImage(GL_TEXTURE_2D,0,GL_RED, GL_UNSIGNED_BYTE, &imageData[0]);

//Setup varibles: counter (used for counting vertices)
//VertexArray: pointer to address for storing the vertices. Size: 3 ints per point, width*height points total
//ColorArray: pointer to address for storing the color data. 3 bytes per point.
int counter = 0;
vertexArray = new GLint[height*width*3];
colorArray = new GLubyte[height*width*3];

srand(time(NULL));
//Loop through rows
for (int z = 0; z < height; z++){
//Loop along the line
for (int x=0; x < width; x++){
colorArray[counter] = 40;
vertexArray[counter++] = x;

colorArray[counter] =120;
vertexArray[counter++] = ((int) imageData[x+z*width] * 20) / 255;

colorArray[counter] =  40;
vertexArray[counter++] = z;

}
}
//"Return" total vertice amount
vertexCount = new GLsizei(counter);

}
void GameMap::setupNormals(){
normalArray = new GLfloat[height*width*3*2];
int counter = 0;
//Loop through rows
for (int y = 0; y < height-1; y++){
//Loop along the line
for (int x=0; x < width-1; x++){
//Triangle 1
if(y==126 && x == 126)
{
int b = 0;
}
int lowerLeft = x + width * y;
int lowerRight = (x + 1) + width * y;
int topLeft = x + width * (y + 1);
int topRight = (x + 1) + width * (y + 1);
GLfloat vectorU[3] = {
(GLfloat)vertexArray[topLeft*3]		-	vertexArray[lowerLeft*3],
(GLfloat)vertexArray[topLeft*3+1]	-	vertexArray[lowerLeft*3+1],
(GLfloat)vertexArray[topLeft*3+2]	-	vertexArray[lowerLeft*3+2]
};

GLfloat vectorR[3] = {
(GLfloat)vertexArray[lowerRight*3]		-	vertexArray[lowerLeft*3],
(GLfloat)vertexArray[lowerRight*3+1]	-	vertexArray[lowerLeft*3+1],
(GLfloat)vertexArray[lowerRight*3+2]	-	vertexArray[lowerLeft*3+2]
};
GLfloat normalVector[3] = {
(GLfloat)	vectorU[1]	*	vectorR[2]	-	vectorU[2]	*	vectorR[1],
(GLfloat)	vectorU[2]	*	vectorR[0]	-	vectorU[0]	*	vectorR[2],
(GLfloat)	vectorU[0]	*	vectorR[1]	-	vectorU[1]	*	vectorR[0]
};

normalArray[counter++] = normalVector[0];
normalArray[counter++] = normalVector[1];
normalArray[counter++] = normalVector[2];

//Triangle 2
vectorU[0] =(GLfloat) vertexArray[topLeft*3]-vertexArray[topRight*3];
vectorU[1] = (GLfloat)vertexArray[topLeft*3+1]-vertexArray[topRight*3+1];
vectorU[2] = (GLfloat)vertexArray[topLeft*3+2]-vertexArray[topRight*3+2];

vectorR[0] = (GLfloat)vertexArray[lowerRight*3]-vertexArray[topRight*3];
vectorR[1] = (GLfloat)vertexArray[lowerRight*3+1]-vertexArray[topRight*3+1];
vectorR[2] = (GLfloat)vertexArray[lowerRight*3+2]-vertexArray[topRight*3+2];
normalVector[0] = vectorU[1] *vectorR[2] - vectorU[2] * vectorR[1];
normalVector[0] = vectorU[2] *vectorR[0] - vectorU[0] * vectorR[2];
normalVector[0] = vectorU[0] *vectorR[1] - vectorU[1] * vectorR[0];
normalArray[counter++] = normalVector[0];
normalArray[counter++] = normalVector[1];
normalArray[counter++] = normalVector[2];
}
}
}

void GameMap::setupIndices(){
//Pointer to location for storing indices. Size: 2 triangles per square, 3 points per triangle, width*height triangles
indexArray = new GLuint[width*height*2*3];
int counter = 0;
//Loop through rows, don't go to top row (because those triangles are to the row below)
for (int y = 0; y < height-1; y++){
//Loop along the line, don't go to last point (those are connected to second last point)
if (y % 2 == 0){
for (int x=0; x < width-1; x++){
//
//	TL___TR
//	|  /  |
//	LL___LR
int lowerLeft = x + width * y;
int lowerRight = (x + 1) + width * y;
int topLeft = x + width * (y + 1);
int topRight = (x + 1) + width * (y + 1);

indexArray[counter++] = lowerLeft;
//indexArray[counter++] = lowerRight;
indexArray[counter++] = topLeft;

//indexArray[counter++] = topLeft;
//indexArray[counter++] = lowerRight;
//indexArray[counter++] = topRight;
}
}
else{
for (int x=width-1; x > 0; x--){
//
//	TL___TR
//	|  /  |
//	LL___LR
int lowerRight =	x + width * y;
int topRight =		x + width * (y + 1);

//indexArray[counter++] = lowerRight;
//indexArray[counter++] = topLeft;

//indexArray[counter++] = topLeft;
indexArray[counter++] = lowerRight;
indexArray[counter++] = topRight;
}
}
indexArray[counter++]=indexArray[counter-1];
}
//"Return" the amount of indices
indexCount = new GLsizei(counter);
}



This is the drawn with the following code:

void GameMap::draw(){

glEnable(GL_COLOR_MATERIAL);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_INT,0,vertexArray);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(3,GL_UNSIGNED_BYTE,0,colorArray);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT,0,normalArray);

if (indexCount != 0x00000000){

glDrawElements(GL_TRIANGLE_STRIP, *indexCount, GL_UNSIGNED_INT, indexArray);
}
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);

glDisable(GL_COLOR_MATERIAL);

}


And the light-source is the following:


//Called once per frame
void Sun::tick(){
ticker++;
if (ticker == 3600){
angle = (angle + 36) % 360;
float hypothenuse =		(float) 5 * textureWidth;
float x =				(float) hypothenuse * sin(angle);
float y =				(float) -(hypothenuse * sin(360-angle));
eye.X = x;
eye.Y = y;
GLfloat light_position[] = { -eye.X, eye.Y, eye.Z, 0.0f };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
ticker = 0;
}
}

void Sun::setupLightSource(){
GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };

glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);

glEnable(GL_LIGHT0);
}


##### Share on other sites

- You dont seem to normalize your calculated normals.

- You may want to calculate vertex normals from the triangle normals to not have those edges show up as much. That needs averaging all normals of adjacent triangles proportional to the angle at the vertices.

- You may want to add a little ambient light.

- You may want to reduce specular lighting as it is unnatural for terrain to look shiny and would need finer details to look well.

- You may want to use more modern OpenGL and write shaders, because otherwise you only get vertex lighting and per pixel lighting would look nicer.

- You may want to try generating smaller triangles.

##### Share on other sites

Excellent tips. Fixing the specular light and ambient light (apparently, black is the new white light..) already made huge improvements on the looks - it's still got that awful dark/bright pattern to it though.

Normalizing a vector means making sure the magnitude is roughly 1, by dividing by the magnitude if it's too far away?

So I'd essentially do the normal calculation twice, but the second time, the normal would be the average of triangle normals 0, 1, 128 for the first vertex?

What do you mean by more modern OpenGL? I've tried writing Shaders, without too much success, so I've decided to save on that for future updates, as I'm running short on time. :P

Will give the smaller triangles a try as well, probably after trying the vectors.

##### Share on other sites

You can normalize the triangle and vertex normals without any conditions. Just divide the normal with the normal length.

Yes, the first time you'll calculate the triangle normals and the second time you use that data to calculate the vertex normals.

However, I for one, prefer to calculate the vertex normals from the actual height data without calculating the triangle normals.

Cheers!

##### Share on other sites

Kauna: how would that work? I can draw it easily on paper, but attempting to exclude calculating the triangle normals doesn't seem to work. When attempting to rationale how I draw it, I seem to come back to subconciously knowing the triangle normals

##### Share on other sites

There are different methods.

To get you started If your heightmap is just an array of height values, you can easily calculate two vectors, which point from the vertex position to the position next to it.

So if you vertex position is P0, you could calculate the (world) positions of the adjacent vertices in X and Y-directions (let's call them P1 and P2).

Then you would calculate the deltas:

X = P1 - P0

Y = P2 - P0

and the normal would be cross product of these 2 vectors. Don't forget to normalize the normal.

Normals calculate this way may create anomalies at some terrain points so may you'll need to calculate several normal vectors for each vertex and then add them together (and normalize).

Practically the code is same as calculating the triangle normals, it just doesn't store the triangle normals. In that sense it might even be slower.

Some games such as Battlefied Bad Company 2 calculated the vertex normal inside a shader from the terrain height data. The source is available on the internet.

Cheers!

##### Share on other sites

Kauna: I may be a bit dense, or am overthinking it. Or perhaps a bit of both. I'm currently rewriting the normal calculation section, and it feels like this is overly complicated.

This is an example of the first (0,0) part of the mesh.

[attachment=19271:trianglenormals.png]

What I'm doing in my code is:

Calculate the indexes for the given vertexes 0-7, in relation to the middle point.

Calculate the normals for each of the 8 triangles.

Then I loop through my normals, and add all x's, y's and z's together and divide by 8. This new (dx, dy, dz) vector is my vertex vector.

This is the specific code I'm using for calculating the normals. p1 is always the center point, and p4 is always one of the opposite corners (0, 2, 5, 7 in the above example).

void GameMap::doTriangleNormal(GLint* p1, GLint* p2, GLint* p3, GLint* p4, GLfloat* n){

GLfloat vectorU[3] = {
(GLfloat)p2[0] - p1[0],
(GLfloat)p2[1] - p1[1],
(GLfloat)p2[2] - p1[2]
};

GLfloat vectorV[3] = {
(GLfloat)p3[0] - p1[0],
(GLfloat)p3[1] - p1[1],
(GLfloat)p3[2] - p1[2]
};

GLfloat normalVector[3] = {
(GLfloat) vectorU[1] * vectorV[2] - vectorU[2] * vectorV[1],
(GLfloat) vectorU[2] * vectorV[0] - vectorU[0] * vectorV[2],
(GLfloat) vectorU[0] * vectorV[1] - vectorU[1] * vectorV[0]
};

n[0] = normalVector[0];
n[1] = normalVector[1];
n[2] = normalVector[2];

float mag = sqrt(n[0] * n[0] +  n[1] * n[1] + n[2] * n[2]);

n[0] = n[0]/mag;
n[1] = n[1]/mag;
n[2] = n[2]/mag;
//Triangle 2
vectorU[0] = (GLfloat)p2[0] - p4[0];
vectorU[1] = (GLfloat)p2[1] - p4[1];
vectorU[2] = (GLfloat)p2[2] - p4[2];

vectorV[0] = (GLfloat)p3[0] - p4[0];
vectorV[1] = (GLfloat)p2[1] - p4[1];
vectorV[2] = (GLfloat)p3[2] - p4[2];

normalVector[0] = vectorU[1] *vectorV[2] - vectorU[2] * vectorV[1];
normalVector[1] = vectorU[2] *vectorV[0] - vectorU[0] * vectorV[2];
normalVector[2] = vectorU[0] *vectorV[1] - vectorU[1] * vectorV[0];
n[3] = normalVector[0];
n[4] = normalVector[1];
n[5] = normalVector[2];

mag = sqrt(n[3] * n[3] +  n[4] * n[4] + n[5] * n[5]);

n[3] = n[3]/mag;
n[4] = n[4]/mag;
n[5] = n[5]/mag;
}

##### Share on other sites

Went ahead and finished the idea above. Seems to sort of work. Got some weird glitch at the edge though - any ideas?[attachment=19272:updatedWorld.png]

##### Share on other sites

Hopefully you aren't indexing outside of your vertex data at the edge?

I'd suggest also writing / using a simple 3d vector library for the math code, it'll make the code less error-prone and much easier to read.

Cheers!

## Create an account

Register a new account

• ## Partner Spotlight

• ### Forum Statistics

• Total Topics
627638
• Total Posts
2978327

• 10
• 12
• 22
• 13
• 34