When it is time to render a mesh, we normally render that mesh multiplying all the vertices by a
Model-View-Projection matrix. This matrix will move, rotate, and scale all the vertices; however, we don't want to do this for the normals associated with those vertices.
To correctly rotate normals we will need another matrix, normally called
Normal Matrix. This matrix is a 3x3 matrix with a rotation encoded in it, and the short answer on how to get this matrix is to compute the transpose of the inverse of the Model matrix.
normal_mat = transpose(inverse(model_mat));
If you want to know more behind the math involved in this computation, please take a look at this fantastic post.
The only problem here is that
inverse(...) operation is expensive :).
While trying to optimize our normal matrix computation, these are some characteristics that we were required to maintain (and others to take advantage of).
- Our matrices are always orthogonal.
- Our matrices can contain non-uniform scale.
At the same time, our goal is to compute a matrix that will rotate the normal associated with a vertex in the same way the model matrix does, but removing any non-uniform scale and any translation at the same time.
Lets put an example:
X Y Z 1.0 0.0 0.0 0.0 0.0 2.0 0.0 0.0 0.0 0.0 1.5 0.0 0.0 0.0 0.0 1.0
Normal matrix (expected)
X Y Z 1.0 0.0 0.0 0.0 0.0 0.5 0.0 0.0 0.0 0.0 0.66 0.0 0.0 0.0 0.0 1.0
In the example above we can see how the
Z axis have a non-uniform scale, and if we want to remove that scale, the axis itself will need to multiply by the inverse of the scale.
So, we can create a
Normal Matrix with this idea in mind:
We extract the axis of the matrix to operate with them.
Matrix44 m = model_mat; vec3 x = vec3(m, m, m); vec3 y = vec3(m, m, m); vec3 z = vec3(m, m, m);
We compute the length of those axis.
float lx = length(x); float ly = length(y); float lz = length(z);
After we have the length, we will normalize them.
normalize(x); normalize(y); normalize(z);
We multiply them by the inverse of the length(scale) to remove any non-uniform scale on that axis.
x = x * (1.0 / lx); y = y * (1.0 / ly); z = z * (1.0 / lz);
We create a new Normal Matrix with the axis we have just computed.
Matrix33 normal_mat = identity_matrix; normal_mat = x; normal_mat = x; normal_mat = x; normal_mat = y; normal_mat = y; normal_mat = y; normal_mat = z; normal_mat = z; normal_mat = z;
At this point,
normal_mat will contain a Normal Matrix that will be able to rotate normals and remove the non-uniform scale of them. This will be very important to allow a correct computation of the light.
This way of computing the Normal Matrix was ~55% faster than computing it by