
Threejs Core Renderer
- 20 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-core-renderer is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-core-renderer
- AI & Agent Building
- AI-coding skill
Threejs Core Renderer by the numbers
- 20 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,459 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-core-rendererAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 20 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
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'` |
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'` |
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 TextureALWAYS 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
Official Sources
- https://threejs.org/docs/#api/en/renderers/WebGLRenderer
- https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
- https://threejs.org/docs/#api/en/cameras/OrthographicCamera
- https://threejs.org/docs/#api/en/renderers/WebGLRenderTarget
threejs-core-renderer — Anti-Patterns
What NOT to do with WebGLRenderer and cameras.
Each anti-pattern shows the wrong code, explains why it fails, and provides the correct alternative.
---
Anti-Pattern 1: Uncapped Pixel Ratio
Wrong
renderer.setPixelRatio(window.devicePixelRatio);Why It Fails
On devices with devicePixelRatio of 3 or higher, this renders 9x+ more pixels than a 1x display. A 1920x1080 canvas at 3x becomes 5760x3240 — over 18 million pixels per frame. This causes severe frame drops, GPU overheating on mobile, and provides no visible quality benefit over 2x.
Correct
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));ALWAYS cap at 2. The visual difference between 2x and 3x is imperceptible, but the performance cost is massive.
---
Anti-Pattern 2: Forgetting updateProjectionMatrix
Wrong
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
});Why It Fails
Modifying camera.aspect (or fov, near, far, zoom) does NOT automatically recompute the projection matrix. The scene continues rendering with the old aspect ratio, causing stretched or squished output.
Correct
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});ALWAYS call camera.updateProjectionMatrix() after changing any projection parameter.
---
Anti-Pattern 3: Wrong Color Space for Data Textures
Wrong
import { TextureLoader, SRGBColorSpace } from 'three';
const loader = new TextureLoader();
const normalMap = loader.load('normal.png');
normalMap.colorSpace = SRGBColorSpace; // WRONG for data texturesWhy It Fails
Normal maps, roughness maps, metalness maps, AO maps, and displacement maps contain linear data values, not perceptual colors. Applying sRGB gamma decoding to these textures corrupts the data — normals point in wrong directions, roughness values are nonlinear, and lighting calculations produce incorrect results.
Correct
import { TextureLoader, SRGBColorSpace, LinearSRGBColorSpace } from 'three';
const loader = new TextureLoader();
// Color textures: SRGBColorSpace
const diffuseMap = loader.load('diffuse.png');
diffuseMap.colorSpace = SRGBColorSpace;
// Data textures: LinearSRGBColorSpace
const normalMap = loader.load('normal.png');
normalMap.colorSpace = LinearSRGBColorSpace;
const roughnessMap = loader.load('roughness.png');
roughnessMap.colorSpace = LinearSRGBColorSpace;---
Anti-Pattern 4: Changing Shadow Map Type After First Render
Wrong
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = PCFShadowMap;
// Later, at runtime...
renderer.shadowMap.type = PCFSoftShadowMap; // triggers full shader recompilationWhy It Fails
Changing shadowMap.type after the first render forces Three.js to recompile ALL shadow-receiving shaders. This causes a massive frame spike and temporary freeze. The shadow map type is baked into shader defines at compile time.
Correct
// Set shadow map type ONCE at initialization, before any render call
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = PCFSoftShadowMap;ALWAYS decide the shadow map type at initialization. NEVER change it at runtime.
---
Anti-Pattern 5: Using requestAnimationFrame Instead of setAnimationLoop
Wrong
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();Why It Fails
Raw requestAnimationFrame does not integrate with the WebXR session lifecycle. When entering VR/AR mode, the XR runtime provides its own frame callback. Using requestAnimationFrame means: (1) the XR session loop and your loop compete, (2) the timestamp is wrong for XR frames, (3) you must manually manage XR session start/stop.
Correct
renderer.setAnimationLoop((time) => {
renderer.render(scene, camera);
});setAnimationLoop automatically switches between requestAnimationFrame (desktop) and XRSession.requestAnimationFrame (XR mode).
---
Anti-Pattern 6: Forgetting to Dispose the Renderer
Wrong
// Component unmount or page navigation
document.body.removeChild(renderer.domElement);
// No dispose call — WebGL context leaksWhy It Fails
Browsers have a hard limit on active WebGL contexts (typically 8-16). Without dispose(), the WebGL context, all compiled shader programs, all GPU textures, and all framebuffers remain allocated. In single-page applications that create/destroy renderers, this causes "Too many active WebGL contexts" errors.
Correct
renderer.setAnimationLoop(null);
renderer.dispose();
renderer.domElement.remove();ALWAYS call dispose() before removing the renderer. Stop the animation loop first.
---
Anti-Pattern 7: preserveDrawingBuffer in Production
Wrong
const renderer = new WebGLRenderer({
antialias: true,
preserveDrawingBuffer: true, // "just in case"
});Why It Fails
preserveDrawingBuffer: true prevents the browser from using efficient buffer-swap techniques. Instead of swapping front/back buffers (near-instant), the browser must copy the buffer contents. This adds overhead to every single frame, even when no screenshot is ever taken.
Correct
// Production renderer — no preserveDrawingBuffer
const renderer = new WebGLRenderer({ antialias: true });
// When a screenshot IS needed, use a one-time render approach:
function takeScreenshot() {
renderer.render(scene, camera);
return renderer.domElement.toDataURL('image/png');
// Works because toDataURL is called immediately after render,
// before the buffer can be cleared
}If toDataURL must work at any time (not just immediately after render), create a separate renderer with preserveDrawingBuffer: true for screenshot purposes only.
---
Anti-Pattern 8: Setting near to Zero
Wrong
const camera = new PerspectiveCamera(50, aspect, 0, 10000);Why It Fails
The depth buffer precision is distributed logarithmically between near and far. Setting near: 0 (or very close to 0, like 0.0001) concentrates almost all depth precision in the first few centimeters. Objects beyond that suffer extreme Z-fighting — flickering surfaces where two faces compete for the same depth value.
Correct
// General 3D scenes
const camera = new PerspectiveCamera(50, aspect, 0.1, 1000);
// Large-scale scenes (architecture, geography)
const camera = new PerspectiveCamera(50, aspect, 0.1, 50000);
// AND enable logarithmic depth on the renderer:
const renderer = new WebGLRenderer({ logarithmicDepthBuffer: true });ALWAYS keep the far / near ratio under 10,000. For scenes that need a wider range, use logarithmicDepthBuffer: true.
---
Anti-Pattern 9: Multi-Pass Rendering with autoClear Enabled
Wrong
// Rendering two passes (e.g., split-screen or overlay)
renderer.render(sceneBackground, camera); // first pass
renderer.render(sceneOverlay, camera); // second pass — CLEARS the first!Why It Fails
autoClear defaults to true. Each render() call clears the color, depth, and stencil buffers before drawing. The second render erases everything from the first.
Correct
renderer.autoClear = false;
renderer.clear(); // explicit clear once
renderer.render(sceneBackground, camera);
renderer.clearDepth(); // clear only depth for overlay
renderer.render(sceneOverlay, camera);ALWAYS set autoClear = false when doing multi-pass rendering. Manage clears explicitly.
---
Anti-Pattern 10: Not Resizing Render Targets
Wrong
const renderTarget = new WebGLRenderTarget(1024, 1024);
window.addEventListener('resize', () => {
renderer.setSize(window.innerWidth, window.innerHeight);
// renderTarget is still 1024x1024 — resolution mismatch
});Why It Fails
If the render target is used for full-screen effects (like post-processing), it must match the output resolution. A fixed-size render target produces blurry or pixelated results when the window changes size.
Correct
const renderTarget = new WebGLRenderTarget(
window.innerWidth * Math.min(window.devicePixelRatio, 2),
window.innerHeight * Math.min(window.devicePixelRatio, 2)
);
window.addEventListener('resize', () => {
const pixelRatio = Math.min(window.devicePixelRatio, 2);
renderer.setSize(window.innerWidth, window.innerHeight);
renderTarget.setSize(
window.innerWidth * pixelRatio,
window.innerHeight * pixelRatio
);
});ALWAYS resize render targets alongside the renderer when they are used for full-screen effects.
threejs-core-renderer — Working Examples
All examples use ES module imports and are verified against Three.js r160+.
Every example is complete and runnable.
---
Example 1: Complete Application Setup
Full initialization with renderer, camera, resize handling, and animation loop.
import {
WebGLRenderer, Scene, PerspectiveCamera, SRGBColorSpace,
ACESFilmicToneMapping, PCFSoftShadowMap, BoxGeometry,
MeshStandardMaterial, Mesh, DirectionalLight, AmbientLight
} from 'three';
// Renderer
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);
// Scene
const scene = new Scene();
// Camera
const camera = new PerspectiveCamera(
50, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.set(3, 3, 5);
camera.lookAt(0, 0, 0);
// Lighting
const ambientLight = new AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
const dirLight = new DirectionalLight(0xffffff, 1.5);
dirLight.position.set(5, 10, 5);
dirLight.castShadow = true;
scene.add(dirLight);
// Mesh
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshStandardMaterial({ color: 0x4488ff });
const cube = new Mesh(geometry, material);
cube.castShadow = true;
scene.add(cube);
// Resize handler
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Animation loop
renderer.setAnimationLoop((time) => {
cube.rotation.y = time * 0.001;
renderer.render(scene, camera);
});---
Example 2: Render-to-Texture with WebGLRenderTarget
Renders a scene to a texture, then uses that texture on a plane in the main scene.
import {
WebGLRenderer, Scene, PerspectiveCamera, WebGLRenderTarget,
SRGBColorSpace, ACESFilmicToneMapping, BoxGeometry,
MeshStandardMaterial, Mesh, PlaneGeometry, MeshBasicMaterial,
AmbientLight, DirectionalLight, LinearFilter
} from 'three';
// Main renderer
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;
document.body.appendChild(renderer.domElement);
// Render target (off-screen framebuffer)
const renderTarget = new WebGLRenderTarget(512, 512, {
minFilter: LinearFilter,
magFilter: LinearFilter,
});
// Off-screen scene (rendered to texture)
const offScene = new Scene();
const offCamera = new PerspectiveCamera(50, 1, 0.1, 100);
offCamera.position.set(0, 0, 3);
const offCube = new Mesh(
new BoxGeometry(1, 1, 1),
new MeshStandardMaterial({ color: 0xff4444 })
);
offScene.add(offCube);
offScene.add(new AmbientLight(0xffffff, 0.5));
offScene.add(new DirectionalLight(0xffffff, 1.0));
// Main scene (displays the render target texture)
const mainScene = new Scene();
const mainCamera = new PerspectiveCamera(
50, window.innerWidth / window.innerHeight, 0.1, 100
);
mainCamera.position.set(0, 0, 4);
const screen = new Mesh(
new PlaneGeometry(3, 3),
new MeshBasicMaterial({ map: renderTarget.texture })
);
mainScene.add(screen);
// Resize
window.addEventListener('resize', () => {
mainCamera.aspect = window.innerWidth / window.innerHeight;
mainCamera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Animation loop
renderer.setAnimationLoop((time) => {
offCube.rotation.y = time * 0.001;
// Render to texture
renderer.setRenderTarget(renderTarget);
renderer.render(offScene, offCamera);
// Render main scene to screen
renderer.setRenderTarget(null);
renderer.render(mainScene, mainCamera);
});---
Example 3: Orthographic Camera with Resize
Demonstrates an orthographic setup that maintains consistent world-space units.
import {
WebGLRenderer, Scene, OrthographicCamera, SRGBColorSpace,
BoxGeometry, MeshNormalMaterial, Mesh, GridHelper
} from 'three';
const renderer = new WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.outputColorSpace = SRGBColorSpace;
document.body.appendChild(renderer.domElement);
const scene = new Scene();
// Orthographic camera showing 10 world units vertically
const frustumSize = 10;
let aspect = window.innerWidth / window.innerHeight;
const camera = new OrthographicCamera(
-frustumSize * aspect / 2,
frustumSize * aspect / 2,
frustumSize / 2,
-frustumSize / 2,
0.1, 1000
);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
scene.add(new GridHelper(10, 10));
scene.add(new Mesh(new BoxGeometry(1, 1, 1), new MeshNormalMaterial()));
// Resize: MUST update all six frustum values
window.addEventListener('resize', () => {
aspect = window.innerWidth / window.innerHeight;
camera.left = -frustumSize * aspect / 2;
camera.right = frustumSize * aspect / 2;
camera.top = frustumSize / 2;
camera.bottom = -frustumSize / 2;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});---
Example 4: Split-Screen with Viewport and Scissor
Renders the same scene from two different cameras side by side.
import {
WebGLRenderer, Scene, PerspectiveCamera, SRGBColorSpace,
ACESFilmicToneMapping, BoxGeometry, MeshNormalMaterial, Mesh
} 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.autoClear = false; // MUST disable for multi-pass rendering
document.body.appendChild(renderer.domElement);
const scene = new Scene();
const cube = new Mesh(new BoxGeometry(1, 1, 1), new MeshNormalMaterial());
scene.add(cube);
// Left camera (front view)
const cameraLeft = new PerspectiveCamera(50, 1, 0.1, 100);
cameraLeft.position.set(0, 0, 5);
// Right camera (top-down view)
const cameraRight = new PerspectiveCamera(50, 1, 0.1, 100);
cameraRight.position.set(0, 5, 0);
cameraRight.lookAt(0, 0, 0);
window.addEventListener('resize', () => {
renderer.setSize(window.innerWidth, window.innerHeight);
// Aspect ratio is 0.5 because each viewport is half-width
const halfAspect = (window.innerWidth / 2) / window.innerHeight;
cameraLeft.aspect = halfAspect;
cameraLeft.updateProjectionMatrix();
cameraRight.aspect = halfAspect;
cameraRight.updateProjectionMatrix();
});
// Trigger initial aspect setup
window.dispatchEvent(new Event('resize'));
renderer.setAnimationLoop((time) => {
cube.rotation.y = time * 0.001;
const halfWidth = Math.floor(window.innerWidth / 2);
const height = window.innerHeight;
renderer.clear(); // manual clear since autoClear is false
// Left viewport
renderer.setViewport(0, 0, halfWidth, height);
renderer.setScissor(0, 0, halfWidth, height);
renderer.setScissorTest(true);
renderer.render(scene, cameraLeft);
// Right viewport
renderer.setViewport(halfWidth, 0, halfWidth, height);
renderer.setScissor(halfWidth, 0, halfWidth, height);
renderer.render(scene, cameraRight);
renderer.setScissorTest(false);
});---
Example 5: Async Shader Compilation with Loading Screen
Pre-compiles shaders to avoid frame drops when the scene first appears.
import {
WebGLRenderer, Scene, PerspectiveCamera, SRGBColorSpace,
ACESFilmicToneMapping, PCFSoftShadowMap, BoxGeometry,
MeshStandardMaterial, Mesh, DirectionalLight, AmbientLight
} 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.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
);
camera.position.set(3, 3, 5);
camera.lookAt(0, 0, 0);
// Add objects
scene.add(new AmbientLight(0xffffff, 0.4));
const dirLight = new DirectionalLight(0xffffff, 1.5);
dirLight.position.set(5, 10, 5);
dirLight.castShadow = true;
scene.add(dirLight);
const cube = new Mesh(
new BoxGeometry(1, 1, 1),
new MeshStandardMaterial({ color: 0x44aa88, roughness: 0.4, metalness: 0.6 })
);
cube.castShadow = true;
scene.add(cube);
// Show loading indicator
const loadingEl = document.createElement('div');
loadingEl.textContent = 'Compiling shaders...';
loadingEl.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);font:20px sans-serif;';
document.body.appendChild(loadingEl);
// Pre-compile shaders asynchronously, then start rendering
async function init() {
await renderer.compileAsync(scene, camera);
loadingEl.remove();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
renderer.setAnimationLoop((time) => {
cube.rotation.y = time * 0.001;
renderer.render(scene, camera);
});
}
init();threejs-core-renderer — Method Reference
Complete API signatures for WebGLRenderer, PerspectiveCamera, OrthographicCamera, and related classes.
All signatures verified against Three.js r160+ official documentation.
---
WebGLRenderer
Constructor
new WebGLRenderer(parameters?: {
canvas?: HTMLCanvasElement;
context?: WebGLRenderingContext;
precision?: 'highp' | 'mediump' | 'lowp';
alpha?: boolean;
premultipliedAlpha?: boolean;
antialias?: boolean;
stencil?: boolean;
preserveDrawingBuffer?: boolean;
powerPreference?: 'high-performance' | 'low-power' | 'default';
failIfMajorPerformanceCaveat?: boolean;
depth?: boolean;
logarithmicDepthBuffer?: boolean;
})Properties
| Property | Type | Default | Mutable | Description |
|---|---|---|---|---|
domElement | HTMLCanvasElement | -- | Read-only | The canvas element |
autoClear | boolean | true | Yes | Auto-clear before render |
autoClearColor | boolean | true | Yes | Auto-clear color buffer |
autoClearDepth | boolean | true | Yes | Auto-clear depth buffer |
autoClearStencil | boolean | true | Yes | Auto-clear stencil buffer |
sortObjects | boolean | true | Yes | Auto-sort draw order |
clippingPlanes | Plane[] | [] | Yes | Global clipping planes |
localClippingEnabled | boolean | false | Yes | Enable per-material clipping |
outputColorSpace | string | SRGBColorSpace | Yes | Output color space |
toneMapping | ToneMapping | NoToneMapping | Yes | Tone mapping algorithm |
toneMappingExposure | number | 1 | Yes | Tone mapping exposure |
shadowMap | WebGLShadowMap | -- | Read-only | Shadow map config object |
shadowMap.enabled | boolean | false | Yes | Enable shadow mapping |
shadowMap.type | ShadowMapType | PCFShadowMap | Init only | Shadow map type |
info | object | -- | Read-only | Render statistics |
info.render.calls | number | -- | Read-only | Draw calls per frame |
info.render.triangles | number | -- | Read-only | Triangles per frame |
info.render.points | number | -- | Read-only | Points per frame |
info.render.lines | number | -- | Read-only | Lines per frame |
info.memory.geometries | number | -- | Read-only | Cached geometries |
info.memory.textures | number | -- | Read-only | Cached textures |
capabilities | object | -- | Read-only | WebGL capabilities |
capabilities.maxTextures | number | -- | Read-only | Max texture units |
capabilities.maxVertexTextures | number | -- | Read-only | Max vertex texture units |
capabilities.precision | string | -- | Read-only | Actual shader precision |
xr | WebXRManager | -- | Read-only | WebXR session manager |
Rendering Methods
render(scene: Scene, camera: Camera): voidRenders the scene using the given camera. ALWAYS call inside setAnimationLoop callback or after setRenderTarget.
setAnimationLoop(callback: ((time: DOMHighResTimeStamp) => void) | null): voidSets the animation loop function. Pass null to stop. Handles WebXR sessions automatically. ALWAYS prefer over raw requestAnimationFrame.
compile(scene: Scene, camera: Camera): Set<Material>Synchronously compiles all shaders in the scene. Returns the set of compiled materials. Blocks the main thread.
compileAsync(scene: Scene, camera: Camera): Promise<void>Asynchronously compiles all shaders. Returns a Promise. ALWAYS call after asset loading, before first visible render. (r160+)
Size and Pixel Ratio Methods
setSize(width: number, height: number, updateStyle?: boolean): voidSets the output canvas size. updateStyle (default true) controls whether the CSS width/height style attributes are updated. Set updateStyle: false when the canvas is sized by CSS.
setPixelRatio(value: number): voidSets the device pixel ratio. ALWAYS pass Math.min(window.devicePixelRatio, 2).
getPixelRatio(): numberReturns the current pixel ratio.
getSize(target: Vector2): Vector2Returns the current output canvas size (in CSS pixels) into the target Vector2.
Clear Methods
setClearColor(color: Color | string | number, alpha?: number): voidSets the clear color. alpha defaults to 1.
getClearColor(target: Color): ColorWrites the current clear color into target.
getClearAlpha(): numberReturns the current clear alpha.
clear(color?: boolean, depth?: boolean, stencil?: boolean): voidManually clears the buffers. All parameters default to true.
Render Target Methods
setRenderTarget(
renderTarget: WebGLRenderTarget | null,
activeCubeFace?: number,
activeMipmapLevel?: number
): voidRedirects rendering to the given render target. Pass null to restore the default framebuffer. activeCubeFace is for cube render targets (0-5). activeMipmapLevel is for mipmap-level rendering.
readRenderTargetPixels(
renderTarget: WebGLRenderTarget,
x: number, y: number,
width: number, height: number,
buffer: TypedArray
): voidReads pixel data from a render target into a typed array buffer. The buffer size MUST be width * height * 4 for RGBA format.
Viewport and Scissor Methods
setViewport(x: number, y: number, width: number, height: number): void
setViewport(v: Vector4): voidSets the viewport region. Coordinates are in pixels from the bottom-left of the canvas.
setScissor(x: number, y: number, width: number, height: number): void
setScissor(v: Vector4): voidSets the scissor region. Only pixels within this rectangle are affected by rendering.
setScissorTest(enable: boolean): voidEnables or disables the scissor test.
Context and Disposal
getContext(): WebGLRenderingContextReturns the underlying WebGL context.
dispose(): voidReleases the WebGL context and all associated resources. ALWAYS call on cleanup.
---
WebGLRenderTarget
Constructor
new WebGLRenderTarget(width: number, height: number, options?: {
minFilter?: TextureFilter; // Default: LinearFilter
magFilter?: TextureFilter; // Default: LinearFilter
format?: PixelFormat; // Default: RGBAFormat
type?: TextureDataType; // Default: UnsignedByteType
stencilBuffer?: boolean; // Default: false
depthBuffer?: boolean; // Default: true
samples?: number; // MSAA samples. Default: 0
colorSpace?: string; // Default: '' (NoColorSpace)
depthTexture?: DepthTexture; // Optional depth texture attachment
})Key Properties
| Property | Type | Description |
|---|---|---|
texture | Texture | The color attachment texture |
depthTexture | `DepthTexture \ | null` |
width | number | Render target width in pixels |
height | number | Render target height in pixels |
samples | number | MSAA sample count |
scissor | Vector4 | Scissor rectangle |
scissorTest | boolean | Scissor test state |
viewport | Vector4 | Viewport rectangle |
Methods
setSize(width: number, height: number): voidResizes the render target. ALWAYS call when the output size changes.
clone(): WebGLRenderTargetReturns a copy.
dispose(): voidReleases GPU resources. ALWAYS call when no longer needed.
---
WebGLCubeRenderTarget
Constructor
new WebGLCubeRenderTarget(size: number, options?: WebGLRenderTargetOptions)Used with CubeCamera for dynamic environment maps. size is the resolution per face.
---
PerspectiveCamera
Constructor
new PerspectiveCamera(
fov?: number, // Vertical FOV in degrees. Default: 50
aspect?: number, // Width / height. Default: 1
near?: number, // Near plane. Default: 0.1
far?: number // Far plane. Default: 2000
)Properties
| Property | Type | Default | Description |
|---|---|---|---|
fov | number | 50 | Vertical field of view in degrees |
aspect | number | 1 | Aspect ratio (width / height) |
near | number | 0.1 | Near clipping plane |
far | number | 2000 | Far clipping plane |
zoom | number | 1 | Zoom factor (>1 zooms in) |
filmGauge | number | 35 | Film gauge in mm |
filmOffset | number | 0 | Horizontal off-center offset in mm |
focus | number | 10 | Focus distance for stereo rendering |
view | `object \ | null` | null |
Methods
updateProjectionMatrix(): voidRecomputes the projection matrix. MUST call after modifying fov, aspect, near, far, or zoom.
setViewOffset(
fullWidth: number, fullHeight: number,
x: number, y: number,
width: number, height: number
): voidSets a view sub-frustum for multi-monitor/tiled rendering.
clearViewOffset(): voidRemoves the view offset.
getEffectiveFOV(): numberReturns the actual FOV accounting for zoom.
getFilmWidth(): number
getFilmHeight(): numberReturns effective film dimensions in mm.
---
OrthographicCamera
Constructor
new OrthographicCamera(
left: number,
right: number,
top: number,
bottom: number,
near?: number, // Default: 0.1
far?: number // Default: 2000
)Properties
| Property | Type | Default | Description |
|---|---|---|---|
left | number | -- | Left frustum plane |
right | number | -- | Right frustum plane |
top | number | -- | Top frustum plane |
bottom | number | -- | Bottom frustum plane |
near | number | 0.1 | Near clipping plane |
far | number | 2000 | Far clipping plane |
zoom | number | 1 | Zoom factor (>1 zooms in) |
Methods
updateProjectionMatrix(): voidMUST call after modifying any frustum parameter or zoom.
setViewOffset(
fullWidth: number, fullHeight: number,
x: number, y: number,
width: number, height: number
): void
clearViewOffset(): void---
ArrayCamera
Constructor
new ArrayCamera(cameras?: PerspectiveCamera[])Each sub-camera MUST have camera.viewport set to a Vector4(x, y, width, height) defining its render region.
---
CubeCamera
Constructor
new CubeCamera(near: number, far: number, renderTarget: WebGLCubeRenderTarget)Methods
update(renderer: WebGLRenderer, scene: Scene): voidRenders the scene from all 6 directions into the cube render target. This calls renderer.render() 6 times. NEVER call every frame for static environments.
---
Tone Mapping Constants
import {
NoToneMapping,
LinearToneMapping,
ReinhardToneMapping,
CineonToneMapping,
ACESFilmicToneMapping,
AgXToneMapping, // r160+
NeutralToneMapping // r160+
} from 'three';Shadow Map Type Constants
import {
BasicShadowMap,
PCFShadowMap,
PCFSoftShadowMap,
VSMShadowMap
} from 'three';Color Space Constants
import {
SRGBColorSpace,
LinearSRGBColorSpace,
NoColorSpace
} from 'three';