Notes

Thoughts, write-ups, and things worth remembering.

Back-face culling

#graphics Updated Sep 21, 2026

Skip rendering triangles that face away from the camera — you’d never see their front anyway. Cuts the work roughly in half for closed meshes.

Two ways to test it

1. Dot product method (needs the surface normal):

N = triangle normal
V = vector from camera to the surface point
backFace = dot(N, V) > 0   // normal points same general direction as view = facing away

See Dot product for why the sign works this way.

2. Winding order method (needs nothing but the projected 2D vertex positions, no normal required):

e1 = B - A, e2 = C - A   (screen-space, after projection)
cross_z = e1.x*e2.y - e1.y*e2.x
CCW (cross_z > 0) -> front face   (or CW, depending on your engine's convention)

See Cross product for the z-component/CCW derivation. This is what GPUs actually use during rasterization — it’s cheaper than the dot product method since it doesn’t need a normal to be computed or stored, just uses the projected vertex order that’s already available.

Code

glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);  // tells the GPU which winding order counts as "front"

Gotchas

  • The CCW=front convention assumes a specific coordinate handedness. Screen/window coordinates are often y-down, which flips the sign relative to math (y-up) coordinates — don’t assume the sign, verify it on your actual pipeline.
  • If a model’s normals or triangle winding get flipped on import/export (common when mirroring or flipping an axis), everything culls backwards — front faces disappear, back faces show through. Classic “why is my mesh invisible/inside-out” bug.
  • Culling assumes a closed, consistently-wound mesh. Open geometry (planes, cloth, foliage cards) needs culling disabled or the mesh made double-sided, otherwise it vanishes from one side.