Three.js Design Theory
The Camera Is a Game Mechanic, Not a Recording Device
A mathematically correct camera can still make a game feel slow, unfair, or impossible to control. Valley Racer and Peak Hopper need different answers because their players need different information.
Production context
This article studies decisions visible in Valley Racer. Repository source: biggames/valley-racer/game.js. The excerpts are shortened to expose one engineering decision at a time.
Start with the Information the Player Needs
A platform camera must expose the landing surface, nearby threats, and the direction of the next jump. A racing camera must communicate speed, turn direction, altitude, and upcoming gates. An exploration camera must preserve orientation while allowing the player to inspect the world. One generic follow offset cannot satisfy all three jobs.
Camera design begins with a gameplay question: what mistake should the player be able to avoid because the camera showed it early enough? That answer determines distance, height, look target, field of view, damping, and whether the player controls the camera directly.
Transform a Local Rig into World Space
A chase rig is easiest to describe relative to the controlled object. The offset says how far above and behind the player the camera should sit. Rotating that offset by the player's quaternion turns it into a world-space target without manually reconstructing forward and right axes.
The look target should not always be the model origin. Looking slightly ahead at speed reveals more track; looking above a character's feet keeps the body centered. Store the position target and look target separately so composition can evolve without breaking movement.
const localOffset = new THREE.Vector3(0, 3.5, 9);
const desiredCamera = new THREE.Vector3();
const desiredLook = new THREE.Vector3();
function buildCameraTargets(player, speed) {
desiredCamera.copy(localOffset)
.applyQuaternion(player.quaternion)
.add(player.position);
desiredLook.copy(player.position)
.addScaledVector(forward, 2 + speed * 0.08);
}
Damping Must Survive Different Frame Rates
A fixed lerp value such as 0.1 means ten percent per rendered frame. At 144 FPS the camera converges much faster than at 30 FPS, so the same game feels tight on one display and heavy on another. Exponential damping expresses a rate per second and turns delta time into a consistent blend.
Different channels can use different rates. Position may need gentle damping, while look direction reacts faster to avoid delayed steering information. During a jump, vertical damping can be slower than horizontal damping to reduce bobbing without making lateral movement feel disconnected.
function dampVector(current, target, rate, dt) {
const blend = 1 - Math.exp(-rate * dt);
current.lerp(target, blend);
}
dampVector(camera.position, desiredCamera, 7, dt);
dampVector(smoothedLook, desiredLook, 11, dt);
camera.lookAt(smoothedLook);
The Desired Position Is Only a Proposal
A camera target can sit underground, inside a cliff, or behind a wall. Terrain height provides a cheap first constraint: camera Y must stay above the sampled surface plus clearance. A ray from player to desired camera then handles obstacles between them and shortens the rig before clipping occurs.
Use simplified collision layers rather than raycasting every leaf and particle. When the obstruction disappears, restore distance with slower damping so the camera does not spring outward. In narrow geometry, a small sphere cast is more robust than a zero-width ray.
const direction = desiredCamera.clone().sub(player.position);
const distance = direction.length();
raycaster.set(player.position, direction.normalize());
raycaster.far = distance;
raycaster.layers.set(CAMERA_COLLISION_LAYER);
const hit = raycaster.intersectObjects(cameraBlockers, true)[0];
if (hit) desiredCamera.copy(hit.point).addScaledVector(hit.face.normal, 0.35);
desiredCamera.y = Math.max(desiredCamera.y, terrainHeightAt(desiredCamera.x, desiredCamera.z) + 0.6);
Compose Speed Instead of Only Increasing FOV
Valley Racer needs the drone to feel fast before motion blur or particles are added. Looking farther ahead, allowing a controlled increase in camera distance, banking the horizon with drone roll, and modestly widening field of view all contribute. Any one effect pushed too far becomes uncomfortable.
Field of view should be damped and followed by updateProjectionMatrix. Speed composition also comes from near-ground reference objects, gate scale, audio pitch, and stable frame pacing. A very wide FOV on an empty landscape can still feel slow because there is little relative motion.
Mobile Cameras Need Fewer Simultaneous Demands
Touch players already cover part of the screen and divide attention between movement and look controls. Strong automatic composition reduces the need for constant camera correction. If manual look remains, use a dedicated swipe zone that does not overlap steering or action buttons.
Test camera height and look-ahead on the actual phone aspect ratio. A desktop framing that comfortably shows the next platform may hide it behind HUD controls on a narrow screen. The best mobile camera is often more predictive and less cinematic than its desktop counterpart.