Switching Multiple Cameras in a glTF Scene

This article explains how to define multiple cameras within the glTF 2.0 file format and switch between them dynamically in WebGL engines. While glTF natively stores multiple camera definitions and binds them to the scene hierarchy, the actual switching mechanism is handled during runtime by 3D rendering engines such as Three.js or Babylon.js.

Defining Cameras in the glTF Schema

The glTF specification defines cameras inside a top-level "cameras" array. Each entry designates a camera type—either "perspective" or "orthographic"—along with its corresponding projection parameters (such as yfov, znear, and zfar).

"cameras": [
  {
    "type": "perspective",
    "perspective": {
      "aspectRatio": 1.777,
      "yfov": 0.785,
      "zfar": 1000.0,
      "znear": 0.1
    },
    "name": "OverviewCamera"
  },
  {
    "type": "perspective",
    "perspective": {
      "aspectRatio": 1.777,
      "yfov": 0.523,
      "zfar": 500.0,
      "znear": 0.1
    },
    "name": "CloseUpCamera"
  }
]

Assigning Cameras to Nodes

Cameras do not hold positional or rotational data directly. Instead, they are assigned to nodes in the "nodes" array using their zero-based index from the "cameras" array. The node provides the spatial transformation (translation, rotation, or matrix).

"nodes": [
  {
    "name": "OverviewNode",
    "camera": 0,
    "translation": [0.0, 5.0, 10.0]
  },
  {
    "name": "CloseUpNode",
    "camera": 1,
    "translation": [0.0, 1.5, 2.0]
  }
]

When authoring in software like Blender, any camera added to the scene collection will automatically export as a camera assigned to a node, preserving its transform and field of view.

Accessing and Toggling Cameras at Runtime

Because glTF is a transmission format rather than an executable application, toggling cameras requires reading the parsed camera array in your rendering engine and updating the active camera passed to the render loop.

In Three.js

When using GLTFLoader, all cameras defined in the file are extracted into an array located at gltf.cameras.

  1. Load and Store: Capture the array of cameras upon loading.
  2. Update Projections: Ensure that aspect ratios match the render window if resizing occurs.
  3. Switch Active Reference: Point your render loop to the desired camera instance.
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

let sceneCameras = [];
let activeCamera;

const loader = new GLTFLoader();
loader.load('scene.gltf', (gltf) => {
  scene.add(gltf.scene);
  
  // Store all cameras found in the glTF
  sceneCameras = gltf.cameras;
  
  // Set default camera (fallback to engine default if none exist)
  activeCamera = sceneCameras[0] || defaultCamera;
});

// Function to toggle between cameras
function switchCamera(index) {
  if (sceneCameras[index]) {
    activeCamera = sceneCameras[index];
    activeCamera.aspect = window.innerWidth / window.innerHeight;
    activeCamera.updateProjectionMatrix();
  }
}

// Render loop uses the active camera
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, activeCamera);
}

In Babylon.js

Babylon.js imports glTF cameras and appends them to the scene's camera list, accessible through scene.cameras.

BABYLON.SceneLoader.Append("", "scene.gltf", scene, function (scene) {
  // Set active camera by name or index
  scene.activeCamera = scene.getCameraByName("CloseUpCamera");
  scene.activeCamera.attachControl(canvas, true);
});

function toggleCamera(cameraName) {
  const targetCam = scene.getCameraByName(cameraName);
  if (targetCam) {
    scene.activeCamera.detachControl();
    scene.activeCamera = targetCam;
    scene.activeCamera.attachControl(canvas, true);
  }
}

By decoupling the camera properties in the "cameras" array, applying transforms through "nodes", and handling active view assignment via runtime loaders, glTF files can store complex multi-camera setups that are switchable on demand.