Sign in to follow this  
Tom Olsson

Distinctive triangles and lighting clipping

Recommended Posts

Tom Olsson    119

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++){
			//Add vertices: x, y, redChannel
			//Add colordata: the common-color.
			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 this post


Link to post
Share on other sites
wintertime    4108

- 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 this post


Link to post
Share on other sites
Tom Olsson    119

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 this post


Link to post
Share on other sites
kauna    2922

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 this post


Link to post
Share on other sites
Tom Olsson    119

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 this post


Link to post
Share on other sites
kauna    2922

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 this post


Link to post
Share on other sites
Tom Olsson    119

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 this post


Link to post
Share on other sites
kauna    2922

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!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this