1
\$\begingroup\$

I am trying to generate vertex normals for a given triangles/vertices list with the following code:

**BASE METHOD:**

private void CalculateNormals()
{
    for (Int32 i = 0; i < m_Meshes.Count; ++i)
    {
        Mesh mesh = m_Meshes[i];

        List<Point3D> vertices = mesh.GetVertices();
        List<Triangle> triangles = mesh.GetTriangles();

        List<Point3D> normals = new List<Point3D>(vertices.Count);

        for (Int32 y = 0; y < vertices.Count; ++y)
            normals.Add(new Point3D(0.0f, 0.0f, 0.0f));

        for (Int32 y = 0; y < triangles.Count; ++y)
        {
            Triangle triangle = triangles[y];
            UInt16 index1 = triangle.Index1;
            UInt16 index2 = triangle.Index2;
            UInt16 index3 = triangle.Index3;

            Point3D vertex1 = vertices[index1];
            Point3D vertex2 = vertices[index2];
            Point3D vertex3 = vertices[index3];

            Point3D a = vertex2 - vertex1;
            Point3D b = vertex3 - vertex1;
            Point3D normal = a % b;

            normals[index1] += normal;
            normals[index2] += normal;
            normals[index3] += normal;
        }

        for (Int32 y = 0; y < normals.Count; ++y)
            normals[y] = normals[y].Normalize();

        mesh.AddNormals(normals);
    }
}


**POINT3D METHODS & OPERATORS:**

public static Point3D operator +(Point3D left, Point3D right)
{
    Single x = left.X + right.X;
    Single y = left.Y + right.Y;
    Single z = left.Z + right.Z;

    return (new Point3D(x, y, z));
}

public static Point3D operator -(Point3D left, Point3D right)
{
    Single x = left.X - right.X;
    Single y = left.Y - right.Y;
    Single z = left.Z - right.Z;

    return (new Point3D(x, y, z));
}

public static Point3D operator /(Point3D point, Single value)
{
    Single x = point.X / value;
    Single y = point.Y / value;
    Single z = point.Z / value;

    return (new Point3D(x, y, z));
}

// Crossed Product
public static Point3D operator %(Point3D left, Point3D right)
{
    Single x = (left.Y * right.Z) - (left.Z * right.Y);
    Single y = (left.Z * right.X) - (left.X * right.Z);
    Single z = (left.X * right.Y) - (left.Y * right.X);

    return (new Point3D(x, y, z));
}

public Point3D Normalize()
{
    Single magnitude = (Single)Math.Sqrt((m_X * m_X) + (m_Y * m_Y) + (m_Z * m_Z));

    if (magnitude == 0.0f)
        return (new Point3D(0.0f, 0.0f, 0.0f));

    return (this / magnitude);
}

This is what I get:

Result

What am I doing wrong? Can someone help me please?

EDIT:

I tried to do this process with a single mesh containing a single triangle to have a better control over what's going on in my algorithm. Here are the triangle vertices values:

Triangle Vertices

Here are the values I get:

My Values

And here are the correct values automatically calculated by a plugin tool:

Correct Values

\$\endgroup\$
1
  • \$\begingroup\$ Your algorithm is just working, if you don't have any vertices twice. Two neighboring faces should have at least one identical index pair. But I don't see any real mistake in your code \$\endgroup\$
    – Tobias B
    Jun 15, 2015 at 8:42

2 Answers 2

3
\$\begingroup\$

There is nothing inherently wrong with your original code when it comes to normalization. The only problem can be with meshes that do not properly share vertices, meaning your normalization code will always normalize the vertices that belong to a single Triangle.

If you wish to fix such a mesh, you should either:

  1. Weld all vertices during import stage
  2. Apply normals to all vertices that share coordinates

A brute force O(N^2) method to apply your normal to all vertices that share coordinates is pretty easy to create. Keep in mind that Vertex operator == compares the X/Y/Z values, not the references:

public class Mesh
{
    int numVertices; // number of vertices
    int numIndices;  // number of indices

    Vertex[] vertices; // [numVertices]
    int[]    indices;  // [numIndices]

    public void CalcVertexNormals()
    {
        // reset all normals (if you are recalculating them)
        for (int i = 0; i < numVertices; ++i)
            vertices[i].normal = Vector3.ZERO;

        // calculate all face normals
        // assuming faces are provided in sets of 3 vertex indices
        // like in most 3D formats and models
        for (int i = 0; i < numIndices; i += 3)
        {
            // get vertices for this triangle:
            Vertex v0 = vertices[indices[i]];
            Vertex v1 = vertices[indices[i + 1]];
            Vertex v2 = vertices[indices[i + 2]];

            // calculate Face normal:
            Vector3 ba = v1.pos - v0.pos;
            Vector3 ca = v2.pos - v0.pos;
            Vector3 normal = ba.cross(ca);

            // if large triangles dominate normal calculation
            // adding this will favor the side with most triangles
            normal.normalize();

            // add normals to any vertex that shares v0/v1/v2 coordinates
            for (int j = 0; j < numVertices; ++j)
            {
                Vertex v = vertices[j];
                if (v == v0 || v == v1 || v == v2)
                {
                    v.normal += normal;
                }
            }
        }

        // normalize all vertex normals
        for (int i = 0; i < numVertices; ++i)
            vertices[i].normal.normalize();
    }
}
\$\endgroup\$
5
  • 1
    \$\begingroup\$ The magnitude of the individual computed bivectors he calls normals is not one. Their magnitude will be dependent on triangle area and the angle between the edges. Normalizing each individual vector before summing may be required to avoid over/under-contribution. \$\endgroup\$ Jun 15, 2015 at 10:58
  • \$\begingroup\$ @LarsViklund That is a valid point. I haven't tested which effect this may have on the normals, but normalizing before addition might indeed prevent any issues. \$\endgroup\$ Jun 15, 2015 at 11:25
  • \$\begingroup\$ I just realized that individual normalization will shift the result toward any side with more triangles. I believe to honor triangle area in the way already done is probably the least wrong. There's no universal right for blending face normals into vertex normals. \$\endgroup\$ Jun 15, 2015 at 11:29
  • \$\begingroup\$ @LarsViklund The side with more face normals should not have that much effect, since the final normalization pass should give the correct direction. I think the problem of magnitude from the original version was more serious, since a very large triangle would completely dominate the normal vector. I guess the best (and only) way is to actually test how this affects the shading of the object in a 3D view. \$\endgroup\$ Jun 15, 2015 at 12:16
  • 1
    \$\begingroup\$ After fixing a few problems in my backend code and after using my original code and the code posted by Jorma, I can show you a comparison between the two results (on the right the Jorma's one): postimg.org/image/lztrsfzyr \$\endgroup\$
    – Zarathos
    Jun 15, 2015 at 12:42
1
\$\begingroup\$

The problem is that you need to actually average the normals. I see that you are accumulating a sum of each face normal that a vertice is apart of but I don't see where you actually divide it by the number of normals that you summed. What you would probably need to do is have another int array of the same size as your normal array and increment them after you add the normal to the normal array like this:

List<int> count_of_normals = new List<int>(vertices.Count);


for (Int32 y = 0; y < triangles.Count; ++y)
{
    count_of_normals[index1]++;
    count_of_normals[index2]++;
    count_of_normals[index3]++;
}

for(Int32 n = 0; n< vertices.Count; n++ )
{
    normal[n]/=count_of_normals[n];
}

Hope that helps

\$\endgroup\$
2
  • \$\begingroup\$ But he is normalizing the normals. Shouldn't do this exactly the same thing? \$\endgroup\$
    – Tobias B
    Jun 15, 2015 at 8:37
  • \$\begingroup\$ Ah yes, I missed part of the code he posted and made the assumption that the indices were shared between triangles. \$\endgroup\$
    – RobCurr
    Jun 16, 2015 at 16:18

You must log in to answer this question.

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