Three.js Cameras

Cameras in Three.js define the point of view from which a scene is rendered. Different types of cameras allow for various perspectives and effects.

1. Camera (Base Class)

A basic abstract camera class from which all cameras inherit. Not intented to use directly.

Three.js Example

const camera = new THREE.Camera()
camera.position.z = 5
scene.add(camera)

2. PerspectiveCamera

Simulates the human eye with perspective projection.

PerspectiveCamera(fov?: 50, aspect?: 1, near?: 0.1, far?: 2000)

fov — Camera frustum vertical field of view. aspect — Camera frustum aspect ratio. near — Camera frustum near plane. far — Camera frustum far plane.

Three.js Example

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 1, 5)

const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const scene = new THREE.Scene()
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)

React-Three-Fiber Example

import { Canvas } from '@react-three/fiber'

export default function PerspectiveCamera() {
  return (
    <Canvas 
      camera={{ position: [0, 1, 5], fov: 75 }} 
      // perspective camera properties
    >
    </Canvas>
  )
}

or

import { PerspectiveCamera } from "@react-three/drei"

<PerspectiveCamera makeDefault args={...} />
  • camera={{...}} on <Canvas> — which only sets camera on initialization. This won't automatically update the camera once it's created.
  • makeDefault tells R3F to use this camera instead of the auto-generated one.
  • This camera will now update automatically when fov, near, etc., change.
  • OrbitControls will now control this camera, not the stale default.
  • R3F + Drei handle aspect by default using the viewport dimensions.
  • To update aspect ratio, use updateProjectionMatrix

perspective

3. OrthographicCamera

Uses orthographic projection where objects appear the same size regardless of distance.

OrthographicCamera(left?: Number, right?: Number, top?: Number, bottom?: Number, near?: 0.1, far?: 2000)

Three.js Example

const aspect = window.innerWidth / window.innerHeight
const camera = new THREE.OrthographicCamera(-aspect, aspect, 1, -1, 0.1, 1000)
camera.position.set(0, 1, 5)

const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const scene = new THREE.Scene()
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)

React-Three-Fiber Example

import { OrthographicCamera } from '@react-three/drei'

<OrthographicCamera makeDefault args={...} />
  • The args prop only sets the initial values when the camera is first created by React Three Fiber.
  • After the component is mounted, changing args has no effect.
  • This is because R3F doesn't recreate the camera every time props change — it reuses the same camera instance (for performance reasons).
  • So if you want changes to left, right, etc. to reflect dynamically, you must manually update the camera and call updateProjectionMatrix().

orthographic

4. CubeCamera

Captures a scene from multiple directions (for reflections and environment maps).

CubeCamera(near?: 0.1, far?: 2000, renderTarget: WebGLCubeRenderTarget)

renderTarget -- The destination cube render target.

Three.js Example

const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256)
const cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget)
scene.add(cubeCamera)

React-Three-Fiber Example

import { CubeCamera, Environment } from '@react-three/drei'

function CubeCameraExample() {
  return (
    <>
      <Environment preset="city" background />

      <CubeCamera resolution={256} frames={1} position={[2, 0, 0]}>
        {(texture) => (
          <mesh position={[2, 0, 0]}>
            <boxGeometry />
            <meshStandardMaterial 
              envMap={texture}
              metalness={metalness}
              roughness={roughness}
              envMapIntensity={1}
            />
          </mesh>
        )}
      </CubeCamera>
    </>
  )
}
PropertyDescription
metalnessReflectivity of the object
roughnessBlurriness of reflection
resolutionReflection texture quality
framesHow often the reflection updates
envMapIntensityBrightness of the reflection (on material)
  • We do not need to manually create or pass a renderTarget unless you have a very specific need (like reusing it across multiple cameras or custom postprocessing pipelines).

cube

5. ArrayCamera

ArrayCamera can be used in order to efficiently render a scene with a predefined set of cameras. This is an important performance aspect for rendering VR scenes. An instance of ArrayCamera always has an array of sub cameras. It's mandatory to define for each sub camera the viewport property which determines the part of the viewport that is rendered with this camera.

ArrayCamera(array: Array of Cameras)

Three.js Example

const cameras = [
  new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000),
  new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
]
const arrayCamera = new THREE.ArrayCamera(cameras)

React-Three-Fiber Example

Currently, ArrayCamera is not natively supported in @react-three/fiber. However, it can be implemented using custom cameras and multiple render passes.

import { Canvas, useFrame, useThree } from "@react-three/fiber"
import * as THREE from "three"

function Scene() {
  const arrayCameraRef = useRef<THREE.ArrayCamera>(null)
  const { size } = useThree()

  const height = size.height
  const width = size.width
  const halfW = width / 2
  const halfH = height / 2

  if (!arrayCameraRef.current) {
    const cam1 = new THREE.PerspectiveCamera(50, halfW / halfH, 0.1, 100)
    cam1.position.set(5, 0, 0)
    cam1.lookAt(0, 0, 0)
    cam1.viewport = new THREE.Vector4(0, halfH, halfW, halfH)

    const cam2 = new THREE.PerspectiveCamera(50, halfW / halfH, 0.1, 100)
    cam2.position.set(0, 5, 0)
    cam2.lookAt(0, 0, 0)
    cam2.up.set(0, 0, -1)
    cam2.viewport = new THREE.Vector4(halfW, halfH, halfW, halfH)

    const cam3 = new THREE.PerspectiveCamera(50, halfW / halfH, 0.1, 100)
    cam3.position.set(0, 0, 5)
    cam3.lookAt(0, 0, 0)
    cam3.viewport = new THREE.Vector4(0, 0, halfW, halfH)

    const cam4 = new THREE.PerspectiveCamera(50, halfW / halfH, 0.1, 100)
    cam4.position.set(5, 0, 0)
    cam4.lookAt(0, 0, 0)
    cam4.viewport = new THREE.Vector4(halfW, 0, halfW, halfH)

    arrayCameraRef.current = new THREE.ArrayCamera([cam1, cam2, cam3, cam4])
  }

  useFrame((state) => {
    const { gl, scene } = state
    const camera = arrayCameraRef.current!
    gl.setScissorTest(true)

    for (const cam of camera.cameras) {
      const vp = cam?.viewport
      if (vp) {
        gl.setViewport(vp.x, vp.y, vp.z, vp.w)
        gl.setScissor(vp.x, vp.y, vp.z, vp.w)
        gl.render(scene, cam)
      }
    }

    gl.setScissorTest(false)
  }, 1)

  return <mesh />
}

function Array() {
  return (
    <Canvas gl={{ preserveDrawingBuffer: true }}>
      <Scene />
    </Canvas>
  )
}
  • To use ArrayCamera, you should not try to let React Three Fiber use it as the default camera. Instead, you manually render the scene using the ArrayCamera in useFrame.

array

6. StereoCamera

Dual PerspectiveCameras used for effects such as 3D Anaglyph or Parallax Barrier. Renders scenes for stereoscopic (3D) effects. The StereoCamera in Three.js creates a stereoscopic effect for VR or 3D anaglyph rendering.

StereoCamera()

Three.js Example

const stereoCamera = new THREE.StereoCamera()
stereoCamera.position.set(0, 1, 5)

React-Three-Fiber Example

import { Canvas, useFrame } from "@react-three/fiber"
import * as THREE from "three"

function Scene() {
  const stereoCameraRef = useRef<THREE.StereoCamera>(null)

  useEffect(() => {
    const camera = new THREE.StereoCamera()
    stereoCameraRef.current = camera
  }, [])

  useFrame(({ gl, scene, camera, size }) => {
    const stereo = stereoCameraRef.current
    if (!stereo) return

    stereo.update(camera as any)

    gl.setScissorTest(true)

    const halfWidth = size.width / 2
    const height = size.height

    gl.setViewport(0, 0, halfWidth, height)
    gl.setScissor(0, 0, halfWidth, height)
    gl.render(scene, stereo.cameraL)

    gl.setViewport(halfWidth, 0, halfWidth, height)
    gl.setScissor(halfWidth, 0, halfWidth, height)
    gl.render(scene, stereo.cameraR)

    gl.setScissorTest(false)
  }, 1)

  return <mesh />
}

function Stereo() {
  return (
    <Canvas gl={{ preserveDrawingBuffer: true }}>
      <Scene />
    </Canvas>
  )
}

stereo