DMGregory's answer explains how this is actually handled, but I'd like to point out a couple of misconceptions:
Assume that a face of a mesh is defined by vertices A, B, C
and that the normal of the face is defined by (B - A) x (C - A)
.
Note that this is only true for “flat shading”, where each triangle forms a visible facet, near-uniformly lit. Most meshes today have normals explicitly defined per vertex, which allows them to be interpolated to simulate curved surfaces. The normals will typically be similar to the cross product, but not equal — in particular, the 3 vertices of a triangle will have different normals, unless the surface the mesh is approximating is actually flat.
These normals cannot be calculated from the mesh, but rather are created at the same time as the mesh, and the algorithm (or author's manual operations) will depend on the intent. For example, a mesh which has the form of a hexagonal tube might be intended to be in fact hexagonal (and the normals will be perpendicular to the faces) or it might be intended to be a low-polygon approximation to a cylinder or wire (in which case the normal of each vertex will point radially outward, so faces have varying normals, and lighting is smooth across the edge between faces).
For game graphics purposes, you should understand normals as a property vertices have, which we wish to preserve the meaning of under various transformations; not as something you can derive from the mesh except when you are making the particular aesthetic restriction of using only flat shading.
“Face normals” are not used or computed at all (unless you are doing something unusual. Normals are interpolated per-pixel across the surface of a triangle, to approximate curved surfaces.
To my knowledge there is no algebraic property of the cross product that lets us simply do R((B - A) x (C - A))
to rotate the normal just like all the vertices.
Yes and no. The matrix can be such that R((B - A) x (C - A))
≠ R(B - A) x R(C - A)
, but in that case, it is not a rotation matrix — it is some other less restricted sort of matrix.
But, since normals are not in practice defined by cross products of vertex differences, the cross product doesn't matter. What does matter is that the vertex normals are transformed in a way which matches the transformation of the shape the mesh forms, so they remain appropriate.
In the simple case of a pure rotation matrix, which does not change the angles between vectors, it suffices to rotate the normals with the same matrix. Picture the normals as pins sticking out of the vertices, and understand that we are rotating — or more generally, performing a rigid transformation — of the entire object as a whole.
More generally — such as if the matrix contains non-uniform scale — it is necessary to use a different matrix to transform the normals (as DMGregory's answer mentions), and that is what is in fact done in general-purpose renderers.
R
is orthonormal, then we can useR((B - A) x (C - A))
to rotate the normal regardless of whether or not it is stored or computed on the fly? – Carpetfizz Oct 17 '23 at 03:33