Gouraud shade a model from an array of vertices?

Gouraud shading: http://en.wikipedia.org/wiki/Gouraud_shading
Video: http://www.youtube.com/watch?v=PMgjVJogIbc

I have a model, expressed here:

My engine uses a standard model class, which is setup like this:

	private float[] vertX;
	private float[] vertY;
	private float[] vertZ;
	private float[] norX;
	private float[] norY;
	private float[] norZ;
	private float[] texX;
	private float[] texY;

I only know the math behind flat shading, so this is what I came up with:

public void flatShade() {
	for (int i = 0; i < amountVertices; i+=3) {
		float[] norm = CollisionHelper.getTriangleNormal(vertX[i], vertY[i], vertZ[i], vertX[i + 1], vertY[i + 1], vertZ[i + 1], vertX[i + 2], vertY[i + 2], vertZ[i + 2]);
		norX[i] = norm[0];
		norX[i + 1] = norm[0];
		norX[i + 2] = norm[0];
		
		norY[i] = norm[1];
		norY[i + 1] = norm[1];
		norY[i + 2] = norm[1];
		
		norZ[i] = norm[2];
		norZ[i + 1] = norm[2];
		norZ[i + 2] = norm[2];
	}
}

And while this works for light calculations (as seen in the picture above), it looks odd, as the triangles which share vertices have normals facing different directions.

What should I do for proper gouraud shading?

Instead of using the triangle normal vector, you have to use a average per-vertex normal vector.
A solution is to average the normal of all vertices which share the same position and to replace the existing normal vectors by the averaged one (precompute them).
So, for a given vertex position, find all vertices with the same position (that will contribute to normal vector), accumulate each normal vector of these vertices, and at the end normalize the accumulated normal vector. Finally this gives you the new smooth normal vector that can be applied to all contributed vertices.

Not very simple to explain but this is the principle :wink:

So say vertex a normal was a vector component “vna”
and vertex b normal was a vector component “vnb”
and vertex c normal was a vector component “vnc”

Assuming each of these vertices are on the same position…

I would do:
Vector average = vna.add(vnb).add(vnc).getNormal();

right?

If so, how would I introduce an angle maximum type of thing?

The accumulated vector must be normalized (divided by its length).

Can you explain ?

Implementation so far:

[quote]public void smoothShade() {
this.flatShade2();
for (int i = 0; i < amountVertices; i++) {
boolean changed = false;
Vector baseLocation = new Vector(vertX[i], vertY[i], vertZ[i]);
Vector current = new Vector(norX[i], norY[i], norZ[i]);
for (int ii = 0; ii < amountVertices; ii++) {
if (ii == i)
continue;
Vector checkLocation = new Vector(vertX[ii], vertY[ii], vertZ[ii]);
Vector check = new Vector(norX[ii], norY[ii], norZ[ii]);

		if (checkLocation.equals(baseLocation)) {
			current.add(check);
			changed = true;
		}
		
	}
	
	if (changed)
		current = current.getNormal();
	
	norX[i] = current.getX();
	norY[i] = current.getY();
	norZ[i] = current.getZ();
}

}
[/quote]
It works for /most/ surfaces, but it looks quite odd against others (the angle maximum).

If you watch the video in the OP, you’ll see a good example of the angle thing I am talking about.

Ok, I think I understand what is this “angle maximum” :wink:

So, you probably need to compare the dot product between triangles normals and a desired value to avoid normal contribution in the smooth normal calculation.

In some 3D File format, like 3DS, you have in the file a attribut giving the smoothing group that act like the dot product comparison and help to avoid wrong normal smoothing.

I don’t quite get how using the dot product between the two normals can be used o:

Say we have 2 vectors A and B

A dot B = lengthA * lengthB * cos(angle_between_vectors)

This means the angle is arccos( A dot B / (length A * length B)) (You can use Math.acos for arc cos, remember it returns radians)

If A and B are normalized, you can just use arccos( A dot B )

When calculating the normals for the vertices of a triangle, you can use that to find the angle between the triangle and the connected triangles (the ones that are being used to find the average normal for that triangle’s vertex). If the angle is too big then you can ignore it and not use it in the average.

Unless I’m mistaken though, this means that if a vertex is part of 3 faces, you will have to calculate and store the average normal 3 times for that vertex, once for each face.

You have to modify your code to loop throw your triangles list and use the triangle normals.
Do this for each vertex of each triangle with something like this :


// Loop thru triangles list
For triRef in triangleList
{
	// Loop thru vertices of the current triangle
	For vRef in triRef.verticesList
	{
		// Initialize vertex normal with triangle normal
		vRef.normal.set(triRef.normal);

		// Loop thru triangles list
		For triCheck in triangleList
		{
			// Avoid self check
			If (triCheck != triRef)
			{
				// Loop thru vertices of the check triangle
				For vCheck in triCheck.verticesList
				{
					// If vertices are at same position
					if (vCheck.position == vRef.position)
					{
						// DotProduct between the two triangle normals
						dot = dotProduct(triRef.normal, triCheck.normal);
						
						// Angle between the two normal vectors
						angle = arccos(dot);
					
						// Max angle test
						if (angle < MAXIMUM_ANGLE)
						{
							vRef.normal.add(triCheck.normal);
						}
					}
				}
			}	
		}
		
		vRef.normal.normalize();
	
	}
}

Whow, I’m exhausted ;D But here is the principle that works for me ! (I hope this explanation is clear enough ;))

Edit : There is a error… I check and will revise it !
Edit 2 : Error resolved !

Thank you both :wink:
That makes perfect sense.