How to calculate vertex normals

I am loading in 3DS files and am currently alanyzing the faces and calculating the face normals. What is the proceedure for calculating vertex normals? I’ve been told that you find the average of the normals of the faces adjoined to the vertex and that’s your vertex normal. Is this correct? Do I have to calculate all the face normals first and then find all the faces that use a particular vertex and then average all those face normals to arrive at the vertex normal? Or is there a way to simply start with the geometry and just straight to the vertex normal? If the answer is the former, what’s the best algorithm to use to find all the faces that use a particular vertex? Thanks!

hi,

here’s a little bit of code that I use to calculate vertex normal :

// first calculate face normal
// then …

int shared = 0;

for(i=0; i<nbvertices; i++)
{
for(j=0; j<nbfaces; j++)
{
if ( (faces[j].vertices[0] == i)| |(faces[j].vertices[1] == i)| |(faces[j].vertices[2] == i) )
{
sum += faces[j].normal;
shared++;
}
}

sum /= float(shared);		
sum.normalize();		

vertices[i].normal[X] = sum.x;
vertices[i].normal[Y] = sum.y;
vertices[i].normal[Z] = sum.z;

shared = 0;
sum    = m_Vector::ZERO;

}

Okay, cool… Only problem is, I think I’ve found a problem with my own face normal calculations. I’ve loaded some stuff in that the lighting doesn’t look right AT ALL! Some faces seem to be lit properly, others don’t. Currently, I’m calculating the face normal by finding the cross product of two of the face’s vectors. I find the vectors like so (psudo code):
vect1 = face.v1-face.v2
vect2 = face.v1-face.v3
crossproduct(vect1,vect2);

Is there a flaw in this? Is there something I’m not taking into consideration? Thanks!

Hi,
This may have nothing to do with your problem, but I thought I’d comment anyway.
I’m loading ASE files (not 3DS) and the normals were screwed up. I found that I had to negate the Z value and swap it with the Y
value. This fixed my problem. I don’t know if it would be the same in the 3DS format but it’s worth a try.

This negate Z then swap Y/Z thinige should be in all formats exported by 3D Studio MAX. This is because MAX uses a coordinate system where Y-axis is into the screen, and Z-axis is upwards, which is different thatn the standard one in OpenGL It’s not a problem related to either ASE-files nor 3DS-files, it’s just that the two programs (MAX and your own) uses different types of coordinate system.

Hmmm… My models look fine… they don’t seem to have their coordinates reversed or anything… And I’m not getting the actual normals from the 3DS file because I’m using the 3DSFTK and it doesn’t seem to have a way to extract any kind of normal information. I don’t know much about 3DS files but from what I can tell, it doesn’t store normals in the files. If the coordinate reversal was the problem, wouldn’t you think my models would look screwy since I’m not reversing or exchanging them?

Hey punchey, I think you are calculating the normals all wrong

Originally posted by Punchey:
[b]Okay, cool… Only problem is, I think I’ve found a problem with my own face normal calculations. I’ve loaded some stuff in that the lighting doesn’t look right AT ALL! Some faces seem to be lit properly, others don’t. Currently, I’m calculating the face normal by finding the cross product of two of the face’s vectors. I find the vectors like so (psudo code):
vect1 = face.v1-face.v2
vect2 = face.v1-face.v3
crossproduct(vect1,vect2);

Is there a flaw in this? Is there something I’m not taking into consideration? Thanks![/b]

it should be
vect1 = face.v2-face.v1;
vect2 = face.v3-face.v1;
crossproduct(vect1,vect2);
Your normals are pointing the wrong way.
check out: http://www.tasteofhoney.freeserve.co.uk/math_rot2.html

[This message has been edited by JiggyWithIt (edited 12-20-2000).]

Errrmmmm…, this y and -z thing has nothing to do with coordinate REVERSAL, it’s just a simple rotation around the x-axis by 90 degree. You have to use this to position the model correctly. You can also use glRotate, but then you get an unnecessary performance hit (very small) and its rather unintuitive.

Regards
Marc

I’ve always used the formula below. I believe this is the formula they describe in one of the appendices in the red book as well.

vect1 = face.v1 - face.v2
vect2 = face.v2 - face.v3
norm = crossproduct(vect1, vect2)
normalize(norm)

[This message has been edited by Deiussum (edited 12-21-2000).]

funny that…
Every tutorial (even math books) I’ve seen has my algo:
vect1 = face.v2-face.v1;
vect2 = face.v3-face.v1;
crossproduct(vect1,vect2);
I guess it all depends if v2 is to the right of v1 and v3 is to the left. Since you have to go from right to left for the cross to point up.

[This message has been edited by JiggyWithIt (edited 12-21-2000).]

I did some math and found that your formula and mine are mathematically identical.

Just take the norm.x term for an example. After taking the cross product norm.x will equal temp1.ytemp2.z - temp1.ztemp1.y

I won’t go through all the algebra, but if you substitute the terms that you’d get for my method, then multiply it out, you get the same result as if you had substituted the terms from your method.

Well, it turned out to be a really stupid thing. First, however, my method is correct. I tried it your way, and it is totally wrong. After changing it back to my mothod, namely v1-v2,v1-v3, it works correctly. My problem was that I had changed my coordinates to GLdoubles from GLfloats to try to get more accuracy. Later, I changed it back but forgot to change the GL_DOUBLE in my glNormalArray call. This was why I was getting the weird shimmers and stuff. But, thanks to you guys, I got my vertex normals working great! Thanks!

That’s strange. I’ve never had any problems calculating the normals using my formula. Are you normalizing the normals after calculating them? I didn’t do any algebra to see if your method was mathematically eqivalent, but it could be possible. Seems kind of strange if it is, since you are doing the opposite subtraction that JiggyWithIt’s formula does. Just for reference, here’s the excerpt from the Red Book…

To find the normal for a flat polygon, take any three vertices v1, v2, and v3 of the polygon that do not lie in a straight line. The cross product

[v1 - v2] × [v2 - v3]

is perpendicular to the polygon. (Typically, you want to normalize the resulting vector.)

Hello.

I took a look to this previous code, to compute the normals of my 3DS mesh. However, something weird happen when I open different 3DS files…

int shared = 0;
for(i=0; i<nbvertices; i++)
{
for(j=0; j<nbfaces; j++)
{
if ( (faces[j].vertices[0] == i)| |(faces
[j].vertices[1] == i)| |(faces[j].vertices
[2] == i) )
{
sum += faces[j].normal;
shared++;
}
}

sum /= float(shared);
sum.normalize();

vertices[i].normal = sum.x;
vertices[i].normal[Y] = sum.y;
vertices[i].normal[Z] = sum.z;

shared = 0;
sum = m_Vector::ZERO;
}

Sometime I need to negate -(shared) to get a correct lightning, and other time, I need it absolute value Abs(shared)

What’s could be the problem ? Different file/mesh version, normal computed differently or something else ?

Thanks
Martin

I wrote a 3ds file reader a bit ago…

I did not pay attention to the name of the people who said it, but once again, 3d studio max inverts the Y with the Z axis… If you think it is irrelevant, you are right… If you compute your normals properly it will be lit regardless with the difference that you will have your model positioned differently than when you open it with 3ds max…

This is the code to compute my average normals… I know it may not be easy to follow…

bool C3ds::NormalizeVectors()
{
if( numOfObjects < 1 ) return false;

vector3 temp_polygons[3];
float shared_vertices = 0;

tObjInfo *currObj;

for( unsigned int obj_index = 0; obj_index &lt; numOfObjects; obj_index++ )
{
	currObj	= &(m_Objects[obj_index]);

	currObj-&gt;m_Normals		= new vector3[currObj-&gt;numOfVertices];
	vector3 *pTempNormals	= new vector3[currObj-&gt;numOfFaces];

	for( int face_index = 0; face_index &lt; currObj-&gt;numOfFaces; face_index++ )
	{
		temp_polygons[0] = currObj-&gt;m_Vertices[currObj-&gt;m_Faces[face_index].VertexIndex[0]];
		temp_polygons[1] = currObj-&gt;m_Vertices[currObj-&gt;m_Faces[face_index].VertexIndex[1]];
		temp_polygons[2] = currObj-&gt;m_Vertices[currObj-&gt;m_Faces[face_index].VertexIndex[2]];

		// Finding the perpendicular vector //
		CrossProduct3v( temp_polygons, &pTempNormals[face_index] );
	}

	vector3 newNormal;

	for( int vertex_index = 0; vertex_index &lt; currObj-&gt;numOfVertices; vertex_index++ )
	{
		memset(&newNormal, 0, sizeof(vector3));
		//&gt;--------------------------------------------------------------------&lt;//
		//	Computing average perpendicular vectors								//
		//&gt;--------------------------------------------------------------------&lt;//
		//
		for( int face_index = 0; face_index &lt; currObj-&gt;numOfFaces; face_index++ )
		{
			if( currObj-&gt;m_Faces[face_index].VertexIndex[0] == vertex_index | |
				currObj-&gt;m_Faces[face_index].VertexIndex[1] == vertex_index | |
				currObj-&gt;m_Faces[face_index].VertexIndex[2] == vertex_index )
			{
				newNormal.V[0] += pTempNormals[face_index].V[0];
				newNormal.V[1] += pTempNormals[face_index].V[1];
				newNormal.V[2] += pTempNormals[face_index].V[2];

				shared_vertices++;
			}
		}

		if( shared_vertices &gt; 0 )
			shared_vertices = shared_vertices;

		// Averaging the perpendicular vectors //
		newNormal.V[0] /= ((float)shared_vertices);
		newNormal.V[1] /= ((float)shared_vertices);
		newNormal.V[2] /= ((float)shared_vertices);

		// Normalizing the vector //
		currObj-&gt;m_Normals[vertex_index] = Normal3v( &newNormal );

		shared_vertices = 0;
	}

	delete [] pTempNormals;
}

return true;

}

Wow… another 2 yr old thread brought back to life…