How to Create Split Screen Cameras in Three.js

Rendering multiple cameras simultaneously in Three.js is a powerful technique for creating multiplayer split-screen games, minimaps, or multi-angle CAD viewers. This article provides a straightforward, step-by-step guide on how to configure your renderer viewport and scissor test to display multiple camera views on a single canvas.

To display multiple cameras simultaneously, you do not need multiple renderers. Instead, you use a single WebGLRenderer and manipulate its active viewport and scissor dimensions during the render loop. This tells WebGL exactly which section of the canvas to draw to for each camera.

Step 1: Disable Auto-Clear

By default, the WebGL renderer clears the entire canvas before rendering a scene. To prevent the second camera’s render from erasing the first camera’s render, you must disable automatic clearing.

renderer.autoClear = false;

Step 2: Define Your Cameras

Create the cameras you want to display. In a standard two-player split-screen setup, you will need two cameras. You must also adjust their aspect ratios to match their respective halves of the screen.

const width = window.innerWidth;
const height = window.innerHeight;

// Left Camera (half width)
const cameraLeft = new THREE.PerspectiveCamera(60, (width / 2) / height, 0.1, 1000);
cameraLeft.position.set(-5, 2, 10);
cameraLeft.lookAt(0, 0, 0);

// Right Camera (half width)
const cameraRight = new THREE.PerspectiveCamera(60, (width / 2) / height, 0.1, 1000);
cameraRight.position.set(5, 2, 10);
cameraRight.lookAt(0, 0, 0);

Step 3: Implement the Render Loop

In your animation loop, you will manually clear the renderer, define the viewport and scissor area for each camera, and render them one by one.

The scissor test is essential here because it restricts rendering operations (like clearing background colors) to a specific pixel box, preventing overlapping cameras from bleeding into each other’s screen space.

function animate() {
    requestAnimationFrame(animate);

    const w = window.innerWidth;
    const h = window.innerHeight;
    const halfWidth = w / 2;

    // 1. Clear the entire canvas manually
    renderer.clear();

    // Enable the scissor test
    renderer.setScissorTest(true);

    // 2. Render Left Viewport
    // setViewport and setScissor parameters: (x, y, width, height)
    renderer.setViewport(0, 0, halfWidth, h);
    renderer.setScissor(0, 0, halfWidth, h);
    renderer.render(scene, cameraLeft);

    // 3. Render Right Viewport
    renderer.setViewport(halfWidth, 0, halfWidth, h);
    renderer.setScissor(halfWidth, 0, halfWidth, h);
    renderer.render(scene, cameraRight);

    // Disable scissor test back to default behavior if needed elsewhere
    renderer.setScissorTest(false);
}

Step 4: Handle Window Resizing

When the window resizes, update the renderer’s size and adjust the aspect ratios of both cameras to maintain correct proportions.

window.addEventListener('resize', () => {
    const w = window.innerWidth;
    const h = window.innerHeight;

    renderer.setSize(w, h);

    // Update left camera aspect ratio
    cameraLeft.aspect = (w / 2) / h;
    cameraLeft.updateProjectionMatrix();

    // Update right camera aspect ratio
    cameraRight.aspect = (w / 2) / h;
    cameraRight.updateProjectionMatrix();
});