2
\$\begingroup\$

I'm currently working on a project that requires me to load .md5mesh format and draw it.

Following this link I've managed to load the mesh into the engine successfully, but a problem arises when calculating normals; they just don't seem to smooth.

To clarify that it was not my rendering or shader code that was the problem, I loaded a model of .OBJ format, and that lights smoothly.

The mesh is calculated correctly too, as I am able to load in complex models with multiple joints and mesh parts.

Here's a screenshot of the lighting.

And here is how I currently calculate the normals (All normals are set to zero before computing):

EDIT Amended the psuedo-code to be more accurate to what I have. The original may have been confusing.

for (unsigned int i = 0; i < NumberOfTriangles; i++)
{
    Math3::vec3 r, s, result;
    Math3::vec3 p1, p2, p3;

    p1 = Triangle[i].Vertex[0];
    p2 = Triangle[i].Vertex[1];
    p3 = Triangle[i].Vertex[2];

    r = p2 - p1;
    s = p3 - p1;

    result = Math3::Cross(s, r);

    // Add the triangles face normal to each vertex
    // The Vertex's are not local to the triangles.
    // Triangle[i].Vertex[j] is just an index.
    Vertex[Triangle[i].Vertex[0]].normal += result;
    Vertex[Triangle[i].Vertex[1]].normal += result;
    Vertex[Triangle[i].Vertex[2]].normal += result;
}

After the loop I normalise each vertex normal to find the average normal.

EDIT Here is how I find the average normal:

for (unsigned int i = 0; i < NumberOfVerts; i++)
{
    float nx = Vertex[i].normal.x;
    float ny = Vertex[i].normal.y;
    float nz = Vertex[i].normal.z; 

    float len = sqrt(nx * nx + ny * ny + nz * nz);

    Vertex[i].normal.x /= len;
    Vertex[i].normal.y /= len;
    Vertex[i].normal.z /= len;
}    

As you can see in the image the .md5mesh model is shading flat instead of shading smoothly like the .OBJ model.

So what am I missing?

\$\endgroup\$
3
  • \$\begingroup\$ At a guess, your OBJ model shares vertices and the MD5 one duplicates them? \$\endgroup\$
    – Will
    Commented Aug 1, 2013 at 15:44
  • \$\begingroup\$ I wrote an answer without seeing "After the loop I normalise each vertex normal to find the average normal." so I deleted it, but my guess would be that this is where it goes wrong. Care to post that code? \$\endgroup\$
    – anthonyvd
    Commented Aug 1, 2013 at 15:57
  • \$\begingroup\$ I've edited the post to show that code. \$\endgroup\$
    – Soapy
    Commented Aug 1, 2013 at 16:11

2 Answers 2

2
\$\begingroup\$

The way you calculate your normals, you end up with this because you take the cross product of the triangle sides.

enter image description here

Where red circles are normals pointing directly at the screen. Sideways it looks like this (both black lines are triangle faces seen from the side)

enter image description here

Notice how for every face, the vertices that make it up are parallel to each other. Because of this, the interpolated value of the normals along the entire face is the same as the value at every vertex!

Instead, your normals should look like this

enter image description here

In which case the interpolated value will vary along the face and give you non-flat shading. I guess the easy way to calculate such normals would be to make another pass and set every vertex's normal to the normalized sum of the normal assigned to it for every face that shares it.

Your problem is that you're not calculating the average normals properly. Basically you seem to only be normalizing the ones you calculated.

Since your faces are all triangles, the pseudocode would look something like this (suboptimal naive algorithm warning)

foreach vertex in mesh.vertices
  //Find the 3 vertices belonging to different faces that are actually the same vertex but with different normals
  vertex shared_vertices[3] = mesh.find_vertices_at_same_position(vertex)
  normal = sum(shared_vertices)
  normal = normalize(normal)

  foreach v in shared_vertices
    v.normal = normal

The calculated normal will then be the blue arrow in this image:

enter image description here

Another cool thing is that once that work is done you could actually share the vertices between faces instead of having 3 copies since all their attributes are the same (thereby saving 2/3 of the memory required to hold your current vertex data)

\$\endgroup\$
6
  • \$\begingroup\$ Sorry for the confusion. This is what I'm already doing, see the edits in my post for clarification. \$\endgroup\$
    – Soapy
    Commented Aug 1, 2013 at 16:49
  • \$\begingroup\$ @Soapy That code should be correct so I can only assume your vertices aren't shared properly. Are you sure you only store 1 vertex, shared among 3 faces? \$\endgroup\$
    – anthonyvd
    Commented Aug 1, 2013 at 17:25
  • \$\begingroup\$ Yea I'm sure. Because for each triangle there is, I get each vertex that contributes to the triangle and work out the surface normal, and add this to each vertex normal. If there is a vertex that contributes to more than one triangle it will sum each triangles surface normal to the existing normal. \$\endgroup\$
    – Soapy
    Commented Aug 1, 2013 at 17:35
  • \$\begingroup\$ @Soapy Where are your files coming from? For example, in Blender, you have to explicitly set your normals to smoothed before exporting. \$\endgroup\$
    – anthonyvd
    Commented Aug 1, 2013 at 19:29
  • \$\begingroup\$ This is what happens when I use the smooth normals in blender. link \$\endgroup\$
    – Soapy
    Commented Aug 2, 2013 at 10:06
0
\$\begingroup\$

It turns out that it was an error in my rendering code.

The code in my question does work out the normals correctly, so if anyone is stuck, my code should work.

The problem was related to how I was interpreting the index buffer.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .