Skip to content
Three.js Cameras & Controls — Complete Guide to Every Camera Type and Control Scheme

Three.js Cameras & Controls — Complete Guide to Every Camera Type and Control Scheme

DodaTech Updated Jun 6, 2026 11 min read

The camera defines how the viewer sees your Three.js scene — it’s the audience’s window into your 3D world. Three.js provides multiple camera types for different perspectives and a rich set of controls for user interaction.

What You’ll Learn

By the end of this tutorial, you’ll understand the difference between Perspective and Orthographic cameras, master six control schemes (Orbit, Trackball, Fly, FirstPerson, DeviceOrientation, PointerLock), build multi-viewport layouts, animate camera transitions, and create a complete Camera Studio mini-project.

Why Cameras & Controls Matter

Choosing the wrong camera or control scheme breaks user experience. A product viewer needs OrbitControls — free rotation around a target. A first-person game needs PointerLockControls. At DodaTech’s Durga Antivirus Pro, the 3D threat dashboard uses multiple cameras: a PerspectiveCamera for the main threat landscape view and an OrthographicCamera for the mini-map overlay, both controlled via custom OrbitControls with constrained zoom limits.

    flowchart LR
    A[Camera Type] --> B{Perspective or Orthographic?}
    B --> C[PerspectiveCamera: Realistic, 3D scenes]
    B --> D[OrthographicCamera: Isometric, 2D, CAD]
    C --> E[Add Controls]
    D --> E
    E --> F{Control Scheme}
    F --> G[Orbit: Product viewer]
    F --> H[Fly: Flight sim]
    F --> I[FirstPerson: FPS game]
    F --> J[Trackball: Unconstrained]
    G --> K[Multi-Viewport Layout]
    H --> K
    I --> K
    K --> L[Camera Studio Project]
    style G fill:#44aa88,color:#fff,stroke:none
  
🎥
Prerequisites: Complete https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-basics/ first. You need to understand Scene, Camera, and Renderer setup before diving into camera types.

Camera Fundamentals — The Eye Analogy

Think of the camera as your eyes:

  • Position = where you stand.
  • LookAt = where you’re looking.
  • Field of View = how wide you open your eyes. Narrow = focused (like binoculars). Wide = panoramic (can feel distorted).
  • Near/Far clipping = how close/far you can see. Things too close or too far vanish.

PerspectiveCamera — Realistic Human Vision

PerspectiveCamera mimics how human eyes work: objects farther away appear smaller. This is the camera you’ll use 90% of the time.

var camera = new THREE.PerspectiveCamera(
    75,                          // fov — Field of view (degrees). 50-75 is natural.
    window.innerWidth / window.innerHeight,  // aspect — width divided by height
    0.1,                         // near — anything closer is not rendered
    1000                         // far — anything farther is not rendered
);

Parameters explained:

  • fov (Field of View): Vertical angle in degrees. 50-75 feels natural. Below 30 = telescopic/zoomed. Above 90 = fish-eye/dramatic. Experiment with the slider in the Camera Studio project below.

  • aspect: Must match your canvas aspect ratio (width/height). If they don’t match, objects look stretched horizontally or vertically. Always update this in the resize handler.

  • near / far: These define the clipping planes — the visible depth range. Objects closer than near or farther than far are not rendered (they’re “clipped”). Keep near as large as possible and far as small as possible to avoid z-fighting (a flickering artifact when depth precision runs out).

camera.position.set(5, 3, 5);   // Move camera to (5, 3, 5)
camera.lookAt(0, 0, 0);         // Point at the origin
camera.zoom = 1.5;               // Zoom in 1.5x
camera.updateProjectionMatrix(); // ⚠️ ALWAYS call after changing fov, aspect, near, far, or zoom

OrthographicCamera — Isometric / 2D View

No perspective distortion. Objects stay the same size regardless of distance. Think of a blueprint or a 2D platformer.

var frustumSize = 10;
var aspect = window.innerWidth / window.innerHeight;

var camera = new THREE.OrthographicCamera(
    -frustumSize * aspect / 2,   // left
    frustumSize * aspect / 2,    // right
    frustumSize / 2,             // top
    -frustumSize / 2,            // bottom
    0.1,                         // near
    1000                         // far
);

camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);

When to use Orthographic vs Perspective:

Use CaseCameraWhy
3D games, product viewersPerspectiveDepth perception matters
2D games, UI overlaysOrthographicNo distortion, exact measurements
CAD / architectural plansOrthographicParallel lines stay parallel
Mini-mapsOrthographicBird’s-eye with consistent scale
Data visualizationPerspectiveMore visually engaging

Switching Between Camera Types

function switchCamera(type) {
    var pos = camera.position.clone();
    var target = new THREE.Vector3(0, 0, 0);

    if (type === "perspective") {
        camera = new THREE.PerspectiveCamera(50, w / h, 0.1, 1000);
    } else {
        var s = 10;
        camera = new THREE.OrthographicCamera(-s * w/h, s * w/h, s/2, -s/2, 0.1, 1000);
    }

    camera.position.copy(pos);
    camera.lookAt(target);
}

Controls — Letting Users Move the Camera

Controls map user input (mouse, touch, keyboard) to camera movement. Most controls require controls.update() in the animation loop.

OrbitControls — The Universal 3D Viewer

This is the most widely used control in Three.js. The camera orbits around a target point — think of a satellite orbiting Earth.

import { OrbitControls } from "three/addons/controls/OrbitControls.js";

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

controls.enableDamping = true;      // Smooth inertia — feels polished
controls.dampingFactor = 0.05;      //   (0-1, lower = smoother motion)
controls.autoRotate = false;
controls.autoRotateSpeed = 2.0;    // Degrees per second when autoRotating

controls.target.set(0, 0, 0);      // The point the camera orbits around

// Limit rotation (polar angle = up/down angle)
controls.minPolarAngle = 0;        // 0 = looking straight down
controls.maxPolarAngle = Math.PI;  // PI = looking straight up

// Limit zoom distance
controls.minDistance = 1;
controls.maxDistance = 50;

controls.screenSpacePanning = true; // Pan parallel to screen vs ground plane

controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;

controls.update();  // Apply settings

Key interactions: Left-drag = rotate, scroll = zoom, right-drag = pan. Touch: one finger = rotate, pinch = zoom, two-finger = pan.

Why damping matters: Without enableDamping: true, movement stops instantly when you release the mouse — it feels mechanical. Damping adds momentum, making the experience feel polished and professional.

TrackballControls — Unconstrained Orbit

Like OrbitControls but without an “up” reference. You can spin completely upside-down.

import { TrackballControls } from "three/addons/controls/TrackballControls.js";

var controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.staticMoving = false;           // True = instant stop, false = damping
controls.dynamicDampingFactor = 0.05;
controls.target.set(0, 0, 0);
controls.update();

When to use: Inspecting 3D models from any angle, VR previews, anywhere the user needs full 6-DOF movement.

FlyControls — Free Flight

First-person flight simulation. WASD to move, mouse to look. No gravity — you float freely.

import { FlyControls } from "three/addons/controls/FlyControls.js";

var controls = new FlyControls(camera, renderer.domElement);
controls.movementSpeed = 10;
controls.rollSpeed = 0.5;      // How fast the camera rolls (tilts sideways)
controls.dragToLook = true;    // Hold mouse button to look around
controls.autoForward = false;  // Auto-move forward without pressing W

function animate() {
    controls.update(delta);    // ⚠️ Pass clock delta in seconds!
    renderer.render(scene, camera);
}

Controls: W/S = forward/back, A/D = strafe left/right, R/F = up/down, Q/E = roll.

FirstPersonControls — FPS-Style

Traditional first-person shooter controls. Movement locked to horizontal plane — no roll.

import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.js";

var controls = new FirstPersonControls(camera, renderer.domElement);
controls.movementSpeed = 10;
controls.lookSpeed = 0.1;
controls.lookVertical = true;
controls.constrainVertical = true;
controls.verticalMin = 1.0;  // Radians from top
controls.verticalMax = 2.0;  // Radians from top
controls.lon = 0;            // Horizontal angle
controls.lat = 0;            // Vertical angle

function animate() {
    controls.update(delta);
    renderer.render(scene, camera);
}

Key difference from FlyControls: FirstPersonControls keeps the camera upright (no roll). FlyControls allows full 3D rotation including roll.

DeviceOrientationControls — Mobile AR

Uses the device’s gyroscope for head-tracking on mobile.

import { DeviceOrientationControls } from "three/addons/controls/DeviceOrientationControls.js";

var controls = new DeviceOrientationControls(camera);

// iOS 13+ requires explicit permission via user gesture
if (typeof DeviceOrientationEvent !== "undefined" &&
    typeof DeviceOrientationEvent.requestPermission === "function") {
    DeviceOrientationEvent.requestPermission()
        .then(function(state) {
            if (state === "granted") controls.connect();
        });
} else {
    controls.connect();
}

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

PointerLockControls — Immersive FPS

Locks the mouse pointer for games. Mouse movement directly controls camera rotation.

import { PointerLockControls } from "three/addons/controls/PointerLockControls.js";

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

document.addEventListener("click", function() {
    controls.lock();  // Request pointer lock
});

Multi-Viewport Layouts (Split Screen)

Render different camera views to different regions of the same canvas.

var leftCamera = new THREE.PerspectiveCamera(50, 0.5, 0.1, 1000);
leftCamera.position.set(-4, 2, 4);
leftCamera.lookAt(0, 0, 0);

var rightCamera = new THREE.PerspectiveCamera(50, 0.5, 0.1, 1000);
rightCamera.position.set(4, 2, 4);
rightCamera.lookAt(0, 0, 0);

function render() {
    var w = renderer.domElement.width;
    var h = renderer.domElement.height;
    var halfWidth = Math.floor(w / 2);

    // Left half — set viewport and scissor, then render
    renderer.setViewport(0, 0, halfWidth, h);
    renderer.setScissor(0, 0, halfWidth, h);
    renderer.setScissorTest(true);
    renderer.render(scene, leftCamera);

    // Right half
    renderer.setViewport(halfWidth, 0, halfWidth, h);
    renderer.setScissor(halfWidth, 0, halfWidth, h);
    renderer.render(scene, rightCamera);
}

Why scissor test? Without setScissorTest(true), each viewport render clears the entire canvas. The scissor test restricts clearing to the specific viewport rectangle.

Camera Transitions & Animation

Lerp (Linear Interpolation) — Simple & Built-in

var targetPosition = new THREE.Vector3(10, 5, 10);
var targetLookAt = new THREE.Vector3(0, 0, 0);
var speed = 0.03;

function animate() {
    camera.position.lerp(targetPosition, speed);  // Move toward target

    var currentLook = new THREE.Vector3();
    camera.getWorldDirection(currentLook);
    var desiredLook = new THREE.Vector3().copy(targetLookAt).sub(camera.position).normalize();
    currentLook.lerp(desiredLook, speed);
    camera.lookAt(camera.position.clone().add(currentLook));

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

GSAP — Full Easing Control

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script>
    gsap.to(camera.position, {
        x: 10, y: 5, z: 10,
        duration: 2,
        ease: "power2.inOut",
        onUpdate: function() {
            camera.lookAt(0, 0, 0);
            camera.updateProjectionMatrix();
        }
    });
</script>

Common Mistakes

1. Near/far clipping too far apart

Setting near = 0.0001 and far = 100000 sounds safe but causes z-fighting — surfaces flicker because the depth buffer ran out of precision. Rule: keep far / near < 10000.

// Bad — depth fighting on distant objects
var camera = new THREE.PerspectiveCamera(75, w/h, 0.0001, 100000);

// Good — tight range for the scene
var camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 1000);

2. Wrong aspect ratio

If camera.aspect doesn’t match the canvas size, objects appear stretched. Always update on resize and don’t forget updateProjectionMatrix().

3. Forgetting controls.update() in the loop

Controls with damping or auto-rotate silently do nothing without controls.update() being called every frame.

4. Camera inside a mesh

If the camera is inside an object (e.g., walking through walls), set near small enough (0.01) but watch for z-fighting.

5. iOS DeviceOrientation not connecting

iOS 13+ blocks device orientation without user permission. Always wrap .connect() in a button click handler.

6. OrbitControls target vs lookAt confusion

controls.target is the orbit center. If you call camera.lookAt() directly, OrbitControls fights it. Update controls.target instead.

7. Multi-viewport without scissor test

Each viewport render clears the entire canvas without setScissorTest(true). Always set the scissor rectangle per viewport.

Practice Questions

Q1: What happens if camera.aspect doesn’t match the canvas aspect ratio? A: Objects appear stretched horizontally or vertically. The camera’s projection matrix doesn’t match the render surface.

Q2: Why does OrbitControls need controls.update() in the animation loop? A: Controls with enableDamping: true simulate inertia. The damping state changes every frame and must be recalculated. Without update(), the controls stay frozen in their last state.

Q3: When would you use OrthographicCamera instead of PerspectiveCamera? A: For 2D games, CAD tools, isometric views, mini-maps, and any situation where you need objects to stay the same size regardless of distance.

Q4: What causes z-fighting and how do you prevent it? A: Z-fighting is flickering between overlapping surfaces due to insufficient depth buffer precision. Fix by tightening near/far planes or using renderer.logarithmicDepthBuffer = true.

Q5: How do you prevent OrbitControls from going through the floor? A: Limit controls.maxPolarAngle to something less than Math.PI (e.g., Math.PI / 2.1).

Challenge: Build a scene with four viewports (top-left, top-right, bottom-left, bottom-right). Each viewport shows the same scene from a different camera position (front, side, top, and perspective). Use OrthographicCamera for three views and PerspectiveCamera for one.

FAQ

What is the difference between Perspective and Orthographic camera?
PerspectiveCamera mimics human vision — objects shrink with distance. OrthographicCamera has no perspective — all objects appear the same size. Use Perspective for 3D scenes, Orthographic for 2D/CAD/isometric views.
Which controls should I use for a 3D product viewer?
OrbitControls is the standard. It provides intuitive rotate/pan/zoom around a target with configurable limits and damping for a polished feel.
How do I prevent the camera from going through the floor?
Limit controls.maxPolarAngle to something less than PI, or clamp camera.position.y to a minimum value in your animation loop.
What is z-fighting and how do I fix it?
Z-fighting is flickering between overlapping surfaces from insufficient depth precision. Fix by tightening near/far planes, enabling renderer.logarithmicDepthBuffer, or slightly separating overlapping objects.
Can I use multiple cameras in one scene?
Yes. Use renderer.setViewport() and renderer.setScissor() to render different cameras to different canvas regions.
Why does OrbitControls feel laggy?
You likely have enableDamping: true without calling controls.update() in the animation loop. With damping enabled, update() is required every frame.

Try It Yourself

Copy the Camera Studio HTML from the mini project above (in the original file). Save as camera-studio.html and open in any browser. Experiment with switching between Perspective/Orthographic cameras and Orbit/Trackball/Fly/FirstPerson controls.

What’s Next

TopicDescriptionLink
Geometries & MaterialsDeep dive into shapes and PBR materialshttps://tutorials.dodatech.com/frontend/libraries/threejs/threejs-geometries-materials/
Lights & ShadowsLight types, shadow maps, and strategieshttps://tutorials.dodatech.com/frontend/libraries/threejs/threejs-lights-shadows/
AnimationClocks, easing, TWEEN.js, and GSAPhttps://tutorials.dodatech.com/frontend/libraries/threejs/threejs-animation/
Interactivity & UIRaycasting, click detection, and overlayshttps://tutorials.dodatech.com/frontend/libraries/threejs/threejs-interactivity-ui/
Three.js BasicsScene, Camera, Renderer fundamentalshttps://tutorials.dodatech.com/frontend/libraries/threejs/threejs-basics/
JavaScript FundamentalsThe language behind the scenesJavaScript
WebGLThe low-level API Three.js wrapsWebGL

You now have a complete understanding of Three.js cameras and controls — from the Perspective vs Orthographic choice to six different control schemes and multi-viewport layouts. This knowledge powers the multi-camera threat visualization dashboard in Durga Antivirus Pro, where different camera angles reveal different aspects of the security landscape.

What’s Next

Congratulations on completing this Threejs Cameras Controls tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro