PDA

View Full Version : Problem with declaring vertex normals



chris_jan
04-08-2012, 07:22 AM
Hello,
I'm programming a small game with BulletPhysics and OpenGL (2.1).
Yesterday I added lighting to the game, so I needed normals for the objects, too.
But I've got a problem with that:
I'm creating a cuboid with the following code:


m_vVertices.pObj = new CVector3D[8];
GLfloat corners[] = {-vHalfExtents.x, vHalfExtents.y, vHalfExtents.z,
vHalfExtents.x, vHalfExtents.y, vHalfExtents.z,
vHalfExtents.x, -vHalfExtents.y, vHalfExtents.z,
-vHalfExtents.x, -vHalfExtents.y, vHalfExtents.z,
-vHalfExtents.x, vHalfExtents.y, -vHalfExtents.z,
vHalfExtents.x, vHalfExtents.y, -vHalfExtents.z,
vHalfExtents.x, -vHalfExtents.y, -vHalfExtents.z,
-vHalfExtents.x, -vHalfExtents.y, -vHalfExtents.z};
for (int i = 0; i < 8; ++i)
{
m_vVertices.pObj[i].x = corners[i*3];
m_vVertices.pObj[i].y = corners[i*3+1];
m_vVertices.pObj[i].z = corners[i*3+2];
}

m_VertexIndices.pObj = new GLuint[6*4];
GLubyte indices[] = {0, 1, 2, 3,
4, 5, 1, 0,
3, 2, 6, 7,
5, 4, 7, 6,
1, 5, 6, 2,
4, 0, 3, 7};
for (int i = 0; i < 24; ++i)
{
m_VertexIndices.pObj[i] = indices[i];
}


Later, I'm rendering with this code:



glEnableClientState(GL_VERTEX_ARRAY);

glVertexPointer(3, GL_FLOAT, 0, static_cast<void*>(m_vVertices.pObj));
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT,
static_cast<void*>(m_VertexIndices.pObj));

glDisableClientState(GL_VERTEX_ARRAY);


But when I wanted to add normals, I had a problem: As far as I know OpenGL doesn't support surface normals, only vertex normals.
But because I'm reusing shared vertices with the help of an index array, I can't specify normals per vertex beacause - dependent on which face a vertex belongs to - each vertex can have different normals. Is there a way I can still use indices to specify the order of the vertices drawn or do I have to declare shared vertices multiple times (for every face needed)?

Thank you for your answers in advance!

chris_jan
04-08-2012, 08:31 AM
OK, I've just noticed that I misunderstood what vertex normals are ( http://www.opengl.org/discussion_boards/...;gonew=1#UNREAD (http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&amp;Number=312178&amp;gonew=1# UNREAD) : "for the normal at a vertex, sum the normals for all the triangles it is shared by; divided by the number of triangles; then normalise this vector" ).
So I tried it with the following code:



m_vNormals.pObj = new CVector3D[8];
// Every vertex normal is set here
for (size_t i = 0; i < 8; ++i)
{
size_t iNumI = 0; // number of this vertex in the indices
GLubyte *pIndices = 0; // locations where the vertex appears
CVector3D vVertNorm = CVector3D(0.0f, 0.0f, 0.0f); // the normal
for (size_t ii = 0; ii < 24; ++ii)
{
if (m_VertexIndices.pObj[ii] == i) ++iNumI; // number of this vertex is determined
}
pIndices = new GLubyte[iNumI];
size_t iNumICounter = 0;
for (size_t ii = 0; ii < 24; ++ii)
{
// if this vertex appears in this location
if (m_VertexIndices.pObj[ii] == i)
{
pIndices[iNumICounter] = ii;
++iNumICounter;
}
}
for (size_t j = 0; j < iNumI; ++j)
{
size_t uiStartPos = pIndices[j]/4;
cerr << "uiStartPos: " << uiStartPos << "; iNumI: " << iNumI << endl;
CVector3D vAB = m_vVertices.pObj[m_VertexIndices.pObj[uiStartPos+1]]-m_vVertices.pObj[m_VertexIndices.pObj[uiStartPos]];
CVector3D vAC = m_vVertices.pObj[m_VertexIndices.pObj[uiStartPos+2]]-m_vVertices.pObj[m_VertexIndices.pObj[uiStartPos]];
vVertNorm += Vector3DNormalize(Vector3DCross(vAB, vAC));
}
vVertNorm /= iNumI;
vVertNorm = Vector3DNormalize(vVertNorm);
m_vVertices.pObj[i] = vVertNorm;
delete[] pIndices;
}


But it showed some very strange result and doesn't seem to be correct.

Does somebody know what I've done wrong?

tonyo_au
04-08-2012, 08:55 AM
ok I check my code and I was wrong about the divide.

I have tried to make your code look like mine - beware it may not compile



CVector normal[8], temp;

for (int i = 0; i < 8; i++)
{
normal[i].x = normal[i].y = normal[i].z = 0;
}

for (int i = 0; i < 24; i += 3)
{
CVector3D vAB = m_vVertices.pObj[m_VertexIndices.pObj[i+1]]-m_vVertices.pObj[m_VertexIndices.pObj[i];
CVector3D vAC = m_vVertices.pObj[m_VertexIndices.pObj[i+2]]-m_vVertices.pObj[m_VertexIndices.pObj[i];
// normal for triangle face
temp = Vector3DCross(vAB, vAC);
// add normal to all vertices in triangle
for (int j = 0; j < 3; j++)
{
normal[m_VertexIndices.pObj[i+1]] += temp
}
}
// normalise vertex normals
for (int i = 0; i < 8; i++)
{
Vector3DNormalize(normal);
}

chris_jan
04-08-2012, 09:48 AM
Yes, the code seems to work (with some small corrections). But the faces seem to be drawn in a wrong way (only the faces inside seem to be drawn).
But it doesn't seem to help to to put a minus sign in front of "normal" (code:


// normalise vertex normals
for (int i = 0; i < 8; i++)
{
normal[i] = Vector3DNormalize(normal[i]);
m_vNormals.pObj[i] = -normal[i];
}

).
Does someone know what I have to change in the code so that it works correctly?

tonyo_au
04-08-2012, 06:34 PM
The normal has a direction, so it can point in or out depending on the order of the vertices. You can reverse the normal by reversing the cross product

if Vector3DCross(vAB, vAC) points in, then Vector3DCross(vAC, vAB) points out.

Google a bit on normals and you find something with a diagram.

chris_jan
04-09-2012, 02:20 AM
I've tried it with reversed normals but it didn't seem to help ( here you can see the results (the right picture uses vAB, vAC and the left picture vAC, vAB): http://dl.dropbox.com/u/32334047/VertexNormals.png ).
Here the output of the program that produces the right image (with "cerr << (m_vNormals.pObj[i]).x << "; " << (m_vNormals.pObj[i]).y << "; " << (m_vNormals.pObj[i]).z << endl;"):


0; 0; 1
-0.707107; 0; -0.707107
-0.0499376; -0.998752; 0
1; 0; 0
0; -1; -2.48353e-09
-nan; -nan; -nan
0; 1; 0
-nan; -nan; -nan

And here the output of the program that produces the left image:


0; 0; -1
0.707107; 0; 0.707107
0.0499376; 0.998752; 0
-1; 0; 0
0; 1; 2.48353e-09
-nan; -nan; -nan
0; -1; 0
-nan; -nan; -nan

tonyo_au
04-09-2012, 06:33 AM
The normals are reversing; your problem must be that you triangles at not all using the same winding; ie clockwise or counter clockwise.

For normals to give consistent results the winding must be consistent - this is easy to say than do if you are manually creating the objects.

If you have access to a 3D program, create a cube and export it as .obj and look the the ordering of the face indices.

Also with flat surfaces like cubes, you will not want to average the normal at a vertex; instead you have to duplicate the vertex and use the face normal for that vertex. If you don't the light interpolation across the surface will be wrong.

chris_jan
04-10-2012, 01:12 AM
I've checked the winding of the vertices; it seems that I always used clockwise winding instead of counter clockwise winding.
I've corrected that and did what you said:

Also with flat surfaces like cubes, you will not want to average the normal at a vertex; instead you have to duplicate the vertex and use the face normal for that vertex. If you don't the light interpolation across the surface will be wrong.

Now it seems to work OK.
Thank you for your help!