threejs-core-renderer
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
- references/methods.md -- Complete WebGLRenderer and Camera API signatures
- references/examples.md -- Working code examples
- references/anti-patterns.md -- What NOT to do