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:
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.
| Property | Line | Description |
|---|---|---|
| matrixWorldInverse | L43 | The view matrix: inverse of the camera's world transform. Transforms world coords → camera/eye space. |
| projectionMatrix | L50 | Encodes the camera's field of view, aspect ratio, and clip planes into a 4×4 matrix. |
| projectionMatrixInverse | L57 | Inverse of projectionMatrix — used to reconstruct world positions from depth (e.g. in SSAO). |
| coordinateSystem | L64 | WebGLCoordinateSystem (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
| Property | Line | Description |
|---|---|---|
| fov | L55 | Vertical field of view in degrees. 70° is wide, 35° is telephoto-like. |
| aspect | L102 | Width / height of the viewport. Must match canvas ratio to avoid stretching. |
| near | L75 | Near clip plane distance. Vertices closer than this are invisible. |
| far | L84 | Far clip plane distance. Vertices farther are invisible. |
| zoom | L63 | Zoom factor (default 1). Increases effective focal length without changing position. |
| view | L111 | Optional 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();
}
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.
// 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