Camera

How three.js cameras compute the view and projection matrices that transform 3D world coordinates to 2D screen pixels, and how the frustum is used to cull invisible objects.

Three Coordinate Spaces

Every vertex passes through three transformations before reaching the screen:

Model Matrix View Matrix Projection Matrix Object Space ──────────────→ World Space ────────────→ Camera Space ──────────────────→ Clip Space (local coords) object.matrixWorld camera.matrixWorldInverse camera.projectionMatrix (= view matrix) Example vertex path: position = (0, 1, 0) ← in object's local space → world position = matrixWorld × (0,1,0,1) → camera position = matrixWorldInverse × worldPos ← "view transform" → clip position = projectionMatrix × cameraPos ← "projection" → NDC: divide xyz by w ← perspective divide → screen xy = (ndc.x * 0.5 + 0.5) * width, ... ← viewport transform
Camera Base Class Camera.js

Camera extends Object3D, so cameras live in the scene graph and can be parented, animated, and transformed like any other object.

PropertyLineDescription
matrixWorldInverseL43The view matrix: inverse of the camera's world transform. Transforms world coords → camera/eye space.
projectionMatrixL50Encodes the camera's field of view, aspect ratio, and clip planes into a 4×4 matrix.
projectionMatrixInverseL57Inverse of projectionMatrix — used to reconstruct world positions from depth (e.g. in SSAO).
coordinateSystemL64WebGLCoordinateSystem (clip Z in [-1,1]) or WebGPUCoordinateSystem (clip Z in [0,1])

updateMatrixWorld Override

// Camera.js
updateMatrixWorld( force ) {
  // First: recompute the world transform (inherited from Object3D)
  super.updateMatrixWorld( force );

  // Then: derive the view matrix = inverse of world transform
  this.matrixWorldInverse.copy( this.matrixWorld ).invert();
}

This is called automatically each frame. The result is sent to shaders as the viewMatrix uniform.

PerspectiveCamera — Projection Math PerspectiveCamera.js
PropertyLineDescription
fovL55Vertical field of view in degrees. 70° is wide, 35° is telephoto-like.
aspectL102Width / height of the viewport. Must match canvas ratio to avoid stretching.
nearL75Near clip plane distance. Vertices closer than this are invisible.
farL84Far clip plane distance. Vertices farther are invisible.
zoomL63Zoom factor (default 1). Increases effective focal length without changing position.
viewL111Optional viewport offset for tiled/multi-view rendering (null = full frame).

updateProjectionMatrix L353

// Simplified — the actual implementation in PerspectiveCamera.js
updateProjectionMatrix() {
  const near = this.near;
  let top = near * Math.tan(
    MathUtils.DEG2RAD * 0.5 * this.fov
  ) / this.zoom;

  let height = 2 * top;
  let width = this.aspect * height;
  let left = -0.5 * width;

  // Apply view offset if set (for tiled rendering)
  if ( this.view !== null && this.view.enabled ) {
    const fullWidth = this.view.fullWidth;
    const fullHeight = this.view.fullHeight;
    left += this.view.offsetX * width / fullWidth;
    top -= this.view.offsetY * height / fullHeight;
    width *= this.view.width / fullWidth;
    height *= this.view.height / fullHeight;
  }

  // Build the 4×4 perspective projection matrix
  this.projectionMatrix.makePerspective(
    left, left + width,
    top, top - height,
    near, this.far,
    this.coordinateSystem
  );

  // Keep the inverse up to date (used for ray-casting, SSAO, etc.)
  this.projectionMatrixInverse
    .copy( this.projectionMatrix )
    .invert();
}
Call camera.updateProjectionMatrix() after changing fov, aspect, near, or far. Forgetting this is a very common bug — the change won't take effect until the matrix is recomputed.

Near/Far and Z-buffer Precision

The Z-buffer has limited precision (typically 24 bits, giving ~16 million values). The usable range is distributed non-linearly — the near plane gets most of the precision. Setting a very small near and very large far causes Z-fighting (flickering when two surfaces are close).

Rule of thumb: keep far/near ratio below ~10,000. For a scene in meters with objects at 0.1–1000m, use near=0.1, far=5000 (ratio=50,000 is too large). Prefer near=1, far=5000.

The Viewing Frustum & Culling

The frustum is the truncated pyramid of visible space. Objects entirely outside it are skipped during rendering.

near plane ┌────┐ / \ / VIEW \ Objects outside are CULLED / FRUSTUM \ (projectObject() skips them) /____________\ far plane Top view: camera · / \ fov/2 / \ /─────\── near / \ /─────────\── mid / \ /─────────────\── far The 6 frustum planes (near, far, left, right, top, bottom) are extracted from the combined projectionMatrix × viewMatrix. Any object whose bounding sphere lies entirely outside ANY plane is culled.
// In WebGLRenderer.render():
// 1. Compute the combined VP matrix:
_projScreenMatrix.multiplyMatrices(
  camera.projectionMatrix,
  camera.matrixWorldInverse
);
// 2. Extract the 6 frustum planes from it:
_frustum.setFromProjectionMatrix( _projScreenMatrix );

// 3. In projectObject() for each mesh:
if ( ! _frustum.intersectsObject( object ) ) {
  return; // skip this object and all its children
}

Frustum.intersectsObject() checks the object's bounding sphere (auto-computed from geometry if needed) against all 6 planes. This is very fast — one dot product per plane.

OrthographicCamera — Parallel Projection

OrthographicCamera uses parallel projection — no perspective distortion, objects don't get smaller with distance. Used for 2D UIs, isometric games, technical drawings.

// OrthographicCamera — defines a box-shaped frustum
const camera = new THREE.OrthographicCamera(
  left,   // -width/2
  right,  // +width/2
  top,    // +height/2
  bottom, // -height/2
  near,   // 0.1
  far     // 1000
);

// Matrix is built with makOrthographic() instead of makePerspective()
// Result: no perspective divide — w component stays 1.0
Camera as an Object3D — Placement & Targeting

Because Camera extends Object3D, you can position and orient it like any object:

// Direct positioning
camera.position.set( 5, 3, 10 );
camera.lookAt( 0, 0, 0 ); // point at world origin

// Attach to a moving object (car chase camera)
car.add( camera );        // camera is now a child of car
camera.position.set( 0, 2, -5 ); // 5 units behind car roof

// Animate along a path
camera.position.lerpVectors( pointA, pointB, t );

// Get where the camera is pointing
const dir = new THREE.Vector3();
camera.getWorldDirection( dir ); // unit vector in camera's -Z direction
External References