Face Normals

How to 3D

Chapter 3: Meshes

Face Normals

To compute a normal for an individual triangle, we need two vectors that run through—are tangent to—the triangle. The triangle's edges are good candidates. Take any two of the three vectors and cross them to get a vector perpendicular to both. The resulting vector points away from the triangle; it's the triangle's normal.

This renderer shows a triangle with the two edge vectors that are crossed to generate the normal:

Let's compute normals in the Trimesh class. We start with a new instance variable and getter for its typed array:

export class Trimesh {
  // ...
  normals: Vector3[] | null;
  
  constructor(positions: Vector3[], faces: number[][]) {
    // ...
    this.normals = null;
  }

  // ...

  normalBuffer() {
    const xyzs = this.normals!.flatMap(p => p.xyz);
    return new Float32Array(xyzs);
  }
}
export class Trimesh {
  // ...
  normals: Vector3[] | null;
  
  constructor(positions: Vector3[], faces: number[][]) {
    // ...
    this.normals = null;
  }

  // ...

  normalBuffer() {
    const xyzs = this.normals!.flatMap(p => p.xyz);
    return new Float32Array(xyzs);
  }
}

Not every renderer needs normals, so we use a type that allows the array to be null. The client opts in to computing the normals by calling this computeNormals method, which iterates through the faces and crosses two edge vectors:

export class Trimesh {
  computeNormals() {
    for (let face of this.faces) {
      const positionA = this.positions[face[0]];
      const positionB = this.positions[face[1]];
      const positionC = this.positions[face[2]];

      const vectorAB = positionB.subtract(positionA);
      const vectorAC = positionC.subtract(positionA);

      const faceNormal = vectorAB.cross(vectorAC).normalize();
    }
  }
}
export class Trimesh {
  computeNormals() {
    for (let face of this.faces) {
      const positionA = this.positions[face[0]];
      const positionB = this.positions[face[1]];
      const positionC = this.positions[face[2]];

      const vectorAB = positionB.subtract(positionA);
      const vectorAC = positionC.subtract(positionA);

      const faceNormal = vectorAB.cross(vectorAC).normalize();
    }
  }
}

This code's not complete. We haven't defined a subtract method for Vector3.

Add this method to your Vector3 class. Also define an add method, which we'll need momentarily.

Having a face's normal is nice, but we have only two options for sending data to a shader: vertex attributes and uniforms. Let's use these face normals to define normals at the vertices.

← Cross ProductVertex Normals →