threejs-core-renderer

Installation
SKILL.md

threejs-core-renderer

Quick Reference

Standard Initialization Pattern

import {
  WebGLRenderer, SRGBColorSpace, ACESFilmicToneMapping,
  PCFSoftShadowMap, PerspectiveCamera, Scene
} from 'three';

const renderer = new WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.outputColorSpace = SRGBColorSpace;
renderer.toneMapping = ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);

const scene = new Scene();
const camera = new PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);

renderer.setAnimationLoop((time) => {
  renderer.render(scene, camera);
});

Constructor Parameters

Parameter Type Default Description
canvas HTMLCanvasElement new canvas Existing canvas element
context WebGLRenderingContext new context Existing WebGL context
precision 'highp' | 'mediump' | 'lowp' 'highp' Shader precision
alpha boolean false Transparent canvas background
premultipliedAlpha boolean true Premultiplied alpha blending
antialias boolean false MSAA anti-aliasing
stencil boolean true Stencil buffer
preserveDrawingBuffer boolean false Required for screenshots
powerPreference 'high-performance' | 'low-power' | 'default' 'default' GPU selection hint
failIfMajorPerformanceCaveat boolean false Fail if software renderer
depth boolean true Depth buffer
logarithmicDepthBuffer boolean false Fix Z-fighting for large scenes

NEVER change antialias after construction -- it CANNOT be modified post-creation.

Key Properties

Property Type Default Description
domElement HTMLCanvasElement -- The canvas. ALWAYS append to DOM
shadowMap.enabled boolean false MUST set true for any shadows
shadowMap.type ShadowMapType PCFShadowMap Shadow filtering algorithm
toneMapping ToneMapping NoToneMapping HDR tone mapping algorithm
toneMappingExposure number 1 Exposure for tone mapping
outputColorSpace string SRGBColorSpace Output color space
autoClear boolean true Clear before each render call
sortObjects boolean true Automatic draw order sorting
clippingPlanes Plane[] [] Global clipping planes
localClippingEnabled boolean false Enable per-material clipping
info object -- Render stats (calls, triangles, memory)

Critical Warnings

NEVER forget to cap pixel ratio -- uncapped devicePixelRatio on 3x+ screens causes 9x+ pixel load. ALWAYS use Math.min(window.devicePixelRatio, 2).

NEVER change shadowMap.type after the first render -- it forces full shader recompilation. ALWAYS set it once at initialization.

NEVER use preserveDrawingBuffer: true in production render loops -- it disables buffer-swap optimizations. ONLY enable it when screenshots are needed.

NEVER set near: 0 on cameras -- it causes Z-fighting everywhere. ALWAYS keep far / near ratio under 10,000.

NEVER forget camera.updateProjectionMatrix() after modifying fov, aspect, near, far, or zoom -- the projection matrix is NOT auto-updated.

NEVER use raw requestAnimationFrame in Three.js r160+ -- ALWAYS use renderer.setAnimationLoop() which handles WebXR sessions automatically.

NEVER skip renderer.dispose() on cleanup -- it leaks WebGL contexts, programs, textures, and framebuffers.


Color Management

Three.js r160+ uses a linear workflow with automatic sRGB conversion on output.

Color Space Rules

Texture Type colorSpace Examples
Color / albedo SRGBColorSpace Diffuse maps, emissive maps
Data LinearSRGBColorSpace Normal maps, roughness, metalness, AO, displacement

Rule: Color textures are ALWAYS SRGBColorSpace. Data textures are ALWAYS LinearSRGBColorSpace. Mixing these up produces washed-out or over-saturated renders.

Tone Mapping Decision Tree

Constant When to Use
NoToneMapping Non-photorealistic rendering, UI overlays, unlit scenes
LinearToneMapping Basic HDR clamping with minimal color transformation
ReinhardToneMapping General-purpose, preserves color hues well
CineonToneMapping Cinematic film stock look
ACESFilmicToneMapping Default choice for PBR. Industry-standard filmic curve
AgXToneMapping Better than ACES for saturated colors. Avoids ACES hue shift on bright blues/reds. (r160+)
NeutralToneMapping When color accuracy is paramount, minimal artistic transformation

ALWAYS use ACESFilmicToneMapping or AgXToneMapping for PBR workflows. AgXToneMapping is preferred when saturated colors must remain accurate.


Shadow Map Configuration

import { PCFSoftShadowMap } from 'three';

renderer.shadowMap.enabled = true;
renderer.shadowMap.type = PCFSoftShadowMap;
Type Quality Performance Notes
BasicShadowMap Low (hard edges) Fastest No filtering
PCFShadowMap Medium Medium Default. Percentage-Closer Filtering
PCFSoftShadowMap High (soft edges) Slower Bilinear PCF. Most popular choice
VSMShadowMap High (very soft) Slowest Can exhibit light bleeding artifacts

Render Loop and Resize

setAnimationLoop (preferred)

renderer.setAnimationLoop((time) => {
  // time is DOMHighResTimeStamp in milliseconds
  renderer.render(scene, camera);
});

// Stop the loop
renderer.setAnimationLoop(null);

Window Resize Handler

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

ALWAYS update camera.aspect AND call updateProjectionMatrix() before setSize().


Camera System

PerspectiveCamera

import { PerspectiveCamera } from 'three';

const camera = new PerspectiveCamera(
  50,                                        // fov (vertical, degrees)
  window.innerWidth / window.innerHeight,    // aspect ratio
  0.1,                                       // near
  1000                                       // far
);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);

After modifying fov, aspect, near, far, or zoom, you MUST call camera.updateProjectionMatrix().

OrthographicCamera

import { OrthographicCamera } from 'three';

const frustumSize = 10;
const aspect = window.innerWidth / window.innerHeight;
const camera = new OrthographicCamera(
  -frustumSize * aspect / 2,   // left
   frustumSize * aspect / 2,   // right
   frustumSize / 2,            // top
  -frustumSize / 2,            // bottom
  0.1, 1000
);

ALWAYS update all six frustum parameters (left, right, top, bottom, near, far) on resize, then call updateProjectionMatrix().

ArrayCamera

Renders multiple viewports in a single render() call. Each sub-camera has camera.viewport = new Vector4(x, y, width, height). Used for split-screen and VR stereo.

CubeCamera

Renders the scene from all 6 directions into a WebGLCubeRenderTarget. Used for dynamic environment maps. NEVER call cubeCamera.update() every frame for static environments -- it renders the scene 6 times per call.


Render Targets

import { WebGLRenderTarget } from 'three';

const renderTarget = new WebGLRenderTarget(1024, 1024);
renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null); // restore default framebuffer
// renderTarget.texture is now usable as a regular Texture

ALWAYS call renderTarget.dispose() when no longer needed.


Shader Compilation

// Synchronous -- blocks the thread
renderer.compile(scene, camera);

// Asynchronous -- non-blocking, prevents frame drops (r160+)
await renderer.compileAsync(scene, camera);

ALWAYS call compileAsync() after loading assets but before the first visible render to prevent jank from just-in-time shader compilation.


Viewport and Scissor

// Render to a sub-region of the canvas
renderer.setViewport(x, y, width, height);
renderer.setScissor(x, y, width, height);
renderer.setScissorTest(true);
renderer.render(scene, camera);

// Reset to full canvas
renderer.setScissorTest(false);
renderer.setViewport(0, 0, canvas.width, canvas.height);

Clipping Planes

import { Plane, Vector3 } from 'three';

// Global clipping (affects all objects)
renderer.clippingPlanes = [new Plane(new Vector3(0, -1, 0), 1)];

// Per-material clipping (MUST enable localClippingEnabled)
renderer.localClippingEnabled = true;
material.clippingPlanes = [plane];
material.clipIntersection = false; // false = union, true = intersection
material.clipShadows = true;       // also clip shadow geometry

Cleanup and Disposal

renderer.setAnimationLoop(null);
renderer.dispose();
renderer.domElement.remove();

ALWAYS call dispose() when removing a renderer. This releases the WebGL context, all compiled shader programs, textures, and framebuffers.


WebGPU Renderer

Three.js includes an experimental WebGPURenderer with TSL (Three Shading Language) node-based materials. For WebGPU-specific guidance, see the threejs-impl-webgpu skill.


Reference Links

Official Sources

Related skills
Installs
9
GitHub Stars
1
First Seen
Apr 1, 2026