Vertex Normals
This renderer is misleading because it shows a single face normal on the triangle:
There is no way in WebGL to associate data with an entire triangle. We must define normals as vertex attributes, just as we define positions and colors:
attributes.addAttribute('position', nvertices, 3, positions);
attributes.addAttribute('color', nvertices, 3, colors);
attributes.addAttribute('normal', nvertices, 3, normals);
attributes.addAttribute('position', nvertices, 3, positions); attributes.addAttribute('color', nvertices, 3, colors); attributes.addAttribute('normal', nvertices, 3, normals);
In the renderer above, the face normal is duplicated at all three vertices. How should vertex normals be defined when there's more than one triangle stitched together? We have a couple of options.
Discrete Faces
The simplest approach is to disconnect the faces from each other. Wherever two faces share a vertex, a clone is added to the vertex positions buffer. Each face gets its own copy. Then the face normals are written to the vertex normals buffer.
This renderer defaults to rendering its cube with discrete faces:
At first glance, the cube appears to only have 8 vertices. In truth, it has 24. Each position appears in the buffer three times for the three faces and three normals with which it's associated.
Shared Vertices
The other alternative is to keep the faces connected and have them share vertices and normals. Toggle the checkbox in the cube renderer above to see this alternative. It doesn't look good. Sharing doesn't make sense when faces form sharp bends, as they do on a cube, but it does make sense for smooth shapes, like the sphere we saw earlier:
With sharing enabled, the sharp bends are blurred. We can significantly reduce the number of triangles needed in a scene and still get a smooth rendering.
What should the normal of a shared vertex be? The vertex normal is computed as the average of the adjoining faces' normals. To compute the average, we sum up the face normals, which we can do as we process each triangle:
for each triangle
# compute faceNormal as described earlier
for each vertex in triangle
add faceNormal to vertex's normal
for each triangle # compute faceNormal as described earlier for each vertex in triangle add faceNormal to vertex's normal
The vertex normals must be initialized to zero vectors before the face normals start accumulating. And they need to averaged after they are accumulated. Usually averaging is done by dividing the sum by the size of a population. In the case of normals, you can just normalize them. That leads to this complete algorithm for computing a mesh's normals:
for each vertex
initialize vertex's normal to zero-vector
for each triangle
# Look up vertex positions.
positionA = triangle's first position
positionB = triangle's second position
positionC = triangle's third position
# Find two edge vectors.
vectorAB = positionB - positionA
vectorAC = positionC - positionA
faceNormal = cross vectorAB and vectorAC
normalize faceNormal
for each vertex in triangle
add faceNormal to vertex's normal
for each vertex
normalize vertexNormal
for each vertex initialize vertex's normal to zero-vector for each triangle # Look up vertex positions. positionA = triangle's first position positionB = triangle's second position positionC = triangle's third position # Find two edge vectors. vectorAB = positionB - positionA vectorAC = positionC - positionA faceNormal = cross vectorAB and vectorAC normalize faceNormal for each vertex in triangle add faceNormal to vertex's normal for each vertex normalize vertexNormal
This algorithm deserves its own method in your Trimesh
class. See the algorithm at work in this renderer:
The blue normals are the face normals. They are averaged together to form the orange vertex normals. Observe how these normals change as you drag the vertex positions around.