Role of Matrix3 for Normals in Three.js
In Three.js, the Matrix3 class is primarily used to
handle the transformation of normal vectors, ensuring they remain
perpendicular to a 3D model’s surface during rendering. While 3D
positions are transformed using a 4x4 matrix (Matrix4),
normal vectors represent directions and are highly sensitive to
non-uniform scaling. This article explains why Matrix3 is
required to calculate the “normal matrix,” how it prevents visual
distortion, and how it is implemented in WebGL shaders.
Understanding the Problem: Why Matrix4 Fails Normals
When a 3D model is translated, rotated, or scaled, its vertex positions are transformed using a 4x4 model-view matrix. However, applying this same matrix to normal vectors (which define the perpendicular direction of a surface) causes issues:
- Translation: Normals are direction vectors, meaning they should not move when the object translates. A 4x4 matrix includes translation data that is irrelevant and destructive to directions.
- Non-Uniform Scaling: If you scale an object unevenly (for example, stretching a sphere along the X-axis to make an ellipsoid), multiplying the normals by the standard model-view matrix distorts them. They will no longer point perpendicular to the newly deformed surface, leading to incorrect lighting and shading.
The Solution: The Normal Matrix
To transform normals correctly, WebGL requires a specific 3-by-3 matrix called the normal matrix. The normal matrix is mathematically defined as the inverse transpose of the upper-left 3x3 portion of the model-view matrix.
Using the inverse transpose mathematically cancels out the stretching
effect of non-uniform scaling while correctly applying any rotation.
Because translation is not needed for direction vectors, a 3x3 matrix
(Matrix3) is the mathematically efficient and correct
choice to store this transformation.
How Three.js Utilizes Matrix3
Three.js automates this complex math using the Matrix3
class. When rendering a scene, Three.js calculates the normal matrix for
each mesh and passes it to the GPU.
If you are writing custom shaders or need to calculate this manually,
Three.js provides a built-in method to convert a Matrix4
into the correct Matrix3 normal matrix:
const normalMatrix = new THREE.Matrix3();
normalMatrix.getNormalMatrix( mesh.modelViewMatrix );The .getNormalMatrix() method automatically extracts the
3x3 rotation and scale components, calculates the inverse, transposes
it, and stores the result in your Matrix3 instance.
Role in Shaders (GLSL)
In a WebGL vertex shader, the Matrix3 normal matrix is
passed as a uniform variable of type mat3 (named
normalMatrix). The shader uses it to transform the vertex
normals from model space into view space before lighting calculations
are performed:
uniform mat3 normalMatrix;
attribute vec3 normal;
void main() {
// Transform the normal vector to view space
vec3 transformedNormal = normalize(normalMatrix * normal);
// Continue with vertex positioning...
}By utilizing Matrix3 to compute and pass the normal
matrix, Three.js ensures that your 3D models reflect light accurately,
regardless of how they are rotated or scaled in the 3D scene.