Skip to main content

Camera

Cameras define the viewpoint and projection for rendering 3D scenes.

Camera Types

PerspectiveCamera

Most common camera type with realistic perspective projection.

const camera = new THREE.PerspectiveCamera(
75, // Field of view (degrees)
window.innerWidth / window.innerHeight, // Aspect ratio
0.1, // Near clipping plane
1000 // Far clipping plane
);

// Position and orientation
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);

// Update aspect ratio
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

OrthographicCamera

Parallel projection without perspective distortion.

const camera = new THREE.OrthographicCamera(
-10,
10, // Left, right
10,
-10, // Top, bottom
0.1,
100 // Near, far
);

// For responsive orthographic camera
const aspect = window.innerWidth / window.innerHeight;
const frustumSize = 10;
const camera = new THREE.OrthographicCamera(
(-frustumSize * aspect) / 2, // Left
(frustumSize * aspect) / 2, // Right
frustumSize / 2, // Top
-frustumSize / 2, // Bottom
0.1,
100 // Near, far
);

Camera Controls

OrbitControls

Allows orbiting around a target point.

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

const controls = new OrbitControls(camera, renderer.domElement);

// Configuration
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.enableZoom = true;
controls.enablePan = true;
controls.enableRotate = true;
controls.autoRotate = false;
controls.autoRotateSpeed = 2.0;

// Limits
controls.minDistance = 1;
controls.maxDistance = 100;
controls.minPolarAngle = 0;
controls.maxPolarAngle = Math.PI;
controls.minAzimuthAngle = -Infinity;
controls.maxAzimuthAngle = Infinity;

// Target
controls.target.set(0, 0, 0);

// Update in animation loop
function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

TrackballControls

Free rotation without up vector constraints.

import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js';

const controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;

FlyControls

Flight simulator style controls.

import { FlyControls } from 'three/examples/jsm/controls/FlyControls.js';

const controls = new FlyControls(camera, renderer.domElement);
controls.movementSpeed = 10;
controls.rollSpeed = 0.005;
controls.autoForward = false;
controls.dragToLook = true;

// Update with delta time
function animate() {
const delta = clock.getDelta();
controls.update(delta);
renderer.render(scene, camera);
requestAnimationFrame(animate);
}

FirstPersonControls

First-person shooter style controls.

import { FirstPersonControls } from 'three/examples/jsm/controls/FirstPersonControls.js';

const controls = new FirstPersonControls(camera, renderer.domElement);
controls.movementSpeed = 10;
controls.lookSpeed = 0.1;
controls.lookVertical = true;
controls.constrainVertical = true;
controls.verticalMin = 1.0;
controls.verticalMax = 2.0;

PointerLockControls

Mouse pointer lock for immersive experiences.

import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls.js';

const controls = new PointerLockControls(camera, renderer.domElement);

// Lock pointer on click
renderer.domElement.addEventListener('click', () => {
controls.lock();
});

// Listen for lock/unlock events
controls.addEventListener('lock', () => {
console.log('Pointer locked');
});

controls.addEventListener('unlock', () => {
console.log('Pointer unlocked');
});

// Movement
const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();

function animate() {
if (controls.isLocked) {
// Handle movement input
direction.z = Number(moveForward) - Number(moveBackward);
direction.x = Number(moveRight) - Number(moveLeft);
direction.normalize();

if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta;
if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta;

controls.moveRight(-velocity.x * delta);
controls.moveForward(-velocity.z * delta);

velocity.x *= 0.9;
velocity.z *= 0.9;
}

renderer.render(scene, camera);
requestAnimationFrame(animate);
}

Camera Animation

Manual Animation

// Animate camera position
function animate() {
const time = Date.now() * 0.001;

// Orbit around target
camera.position.x = Math.cos(time) * 10;
camera.position.z = Math.sin(time) * 10;
camera.lookAt(0, 0, 0);

// Smooth movement
camera.position.lerp(targetPosition, 0.1);

renderer.render(scene, camera);
requestAnimationFrame(animate);
}

Tween Animation

import { Tween, Easing } from '@tweenjs/tween.js';

// Animate to new position
const currentPosition = camera.position.clone();
const targetPosition = new THREE.Vector3(10, 5, 10);

new Tween(currentPosition)
.to(targetPosition, 2000)
.easing(Easing.Quadratic.Out)
.onUpdate(() => {
camera.position.copy(currentPosition);
camera.lookAt(0, 0, 0);
})
.start();

Camera Helpers

// Camera helper (visualizes camera frustum)
const cameraHelper = new THREE.CameraHelper(camera);
scene.add(cameraHelper);

// Update helper when camera changes
camera.updateProjectionMatrix();
cameraHelper.update();

Multiple Cameras

// Multiple cameras
const camera1 = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
const camera2 = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.1, 100);

let currentCamera = camera1;

// Switch cameras
function switchCamera() {
currentCamera = currentCamera === camera1 ? camera2 : camera1;
controls.object = currentCamera;
}

// Render with current camera
renderer.render(scene, currentCamera);

Viewport and Scissor

// Split screen with multiple cameras
const size = renderer.getSize(new THREE.Vector2());

// Left viewport
renderer.setViewport(0, 0, size.width / 2, size.height);
renderer.setScissor(0, 0, size.width / 2, size.height);
renderer.setScissorTest(true);
renderer.render(scene, camera1);

// Right viewport
renderer.setViewport(size.width / 2, 0, size.width / 2, size.height);
renderer.setScissor(size.width / 2, 0, size.width / 2, size.height);
renderer.render(scene, camera2);

// Reset
renderer.setScissorTest(false);
renderer.setViewport(0, 0, size.width, size.height);

Camera Utilities

World to Screen Coordinates

function worldToScreen(worldPosition, camera, renderer) {
const vector = worldPosition.clone();
vector.project(camera);

const size = renderer.getSize(new THREE.Vector2());
const x = ((vector.x + 1) * size.width) / 2;
const y = ((-vector.y + 1) * size.height) / 2;

return new THREE.Vector2(x, y);
}

Screen to World Coordinates

function screenToWorld(screenPosition, camera, renderer) {
const size = renderer.getSize(new THREE.Vector2());
const mouse = new THREE.Vector2(
(screenPosition.x / size.width) * 2 - 1,
-(screenPosition.y / size.height) * 2 + 1
);

const vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject(camera);

const direction = vector.sub(camera.position).normalize();
return direction;
}

Raycasting from Camera

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

function onMouseClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);

if (intersects.length > 0) {
console.log('Clicked object:', intersects[0].object);
}
}

window.addEventListener('click', onMouseClick);

Responsive Camera

function onWindowResize() {
const aspect = window.innerWidth / window.innerHeight;

if (camera instanceof THREE.PerspectiveCamera) {
camera.aspect = aspect;
} else if (camera instanceof THREE.OrthographicCamera) {
const frustumSize = 10;
camera.left = (-frustumSize * aspect) / 2;
camera.right = (frustumSize * aspect) / 2;
camera.top = frustumSize / 2;
camera.bottom = -frustumSize / 2;
}

camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}

window.addEventListener('resize', onWindowResize);

Advanced Camera Techniques

Dolly Zoom Effect

function dollyZoom(camera, target, distance, fov) {
const currentDistance = camera.position.distanceTo(target);
const newDistance = distance;

// Calculate new FOV to maintain apparent size
const newFov =
(2 *
Math.atan(
Math.tan((fov * Math.PI) / 360) * (currentDistance / newDistance)
) *
180) /
Math.PI;

camera.fov = newFov;
camera.position.normalize().multiplyScalar(newDistance).add(target);
camera.updateProjectionMatrix();
}

Smooth Camera Follow

class CameraFollow {
constructor(camera, target) {
this.camera = camera;
this.target = target;
this.offset = new THREE.Vector3(0, 5, 10);
this.smoothing = 0.1;
}

update() {
const desiredPosition = this.target.position.clone().add(this.offset);
this.camera.position.lerp(desiredPosition, this.smoothing);
this.camera.lookAt(this.target.position);
}
}

const cameraFollow = new CameraFollow(camera, player);

function animate() {
cameraFollow.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}