
Threejs Webgl
- 2.4k installs
- 629 repo stars
- Updated November 20, 2025
- freshtechbro/claudedesignskills
threejs-webgl is an agent skill that Comprehensive skill for Three.js 3D web development. Use this skill when building interactive 3D scenes, WebGL/WebGPU applications, product configurators, 3D visualizatio.
About
The threejs-webgl skill. Comprehensive skill for Three.js 3D web development. Use this skill when building interactive 3D scenes, WebGL/WebGPU applications, product configurators, 3D visualizations, or immersive web experiences. Triggers on tasks involving Three.js, 3D rendering, scenes, cameras, meshes, materials, lights, animations, textures, or WebGL/WebGPU rendering. This skill provides comprehensive guidance for building performant, interactive 3D experiences including scenes, cameras, renderers, geometries, materials, lights, textures, and animations. **Scene**: Container for all 3D objects 2. **Camera**: Defines the viewing perspective 3. **Renderer**: Draws the scene to canvas (WebGL or WebGPU) 4. **Geometry**: Defines the shape of objects 5. **Material**: Defines the surface appearance 6. The workflow follows the source SKILL.md contract with progressive reference loading, clear trigger phrases, and practical steps developers can apply directly in agent sessions.
- Scene: Container for all 3D objects
- Camera: Defines the viewing perspective
- Renderer: Draws the scene to canvas (WebGL or WebGPU)
- Geometry: Defines the shape of objects
- Material: Defines the surface appearance
Threejs Webgl by the numbers
- 2,373 all-time installs (skills.sh)
- +192 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #202 of 2,244 Frontend Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
threejs-webgl capabilities & compatibility
- Capabilities
- scene: container for all 3d objects · camera: defines the viewing perspective · renderer: draws the scene to canvas (webgl or we · geometry: defines the shape of objects · material: defines the surface appearance
- Use cases
- frontend · ui design · api development
What threejs-webgl says it does
This skill provides comprehensive guidance for building performant, interactive 3D experiences including scenes, cameras, renderers, geometries, materials, lights, textures, and animations.
**Scene**: Container for all 3D objects 2.
npx skills add https://github.com/freshtechbro/claudedesignskills --skill threejs-webglAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.4k |
|---|---|
| repo stars | ★ 629 |
| Security audit | 2 / 3 scanners passed |
| Last updated | November 20, 2025 |
| Repository | freshtechbro/claudedesignskills ↗ |
How do I apply threejs-webgl correctly using the SKILL.md workflows and reference files?
Comprehensive skill for Three.js 3D web development. Use this skill when building interactive 3D scenes, WebGL/WebGPU applications, product configurators, 3D visualizations, or immersive web experienc
Who is it for?
Developers and software engineers working with threejs-webgl patterns from the skill documentation.
Skip if: Skip when cached docs are empty, boilerplate-only, or outside the skill documented scope.
When should I use this skill?
Comprehensive skill for Three.js 3D web development. Use this skill when building interactive 3D scenes, WebGL/WebGPU applications, product configurators, 3D visualizations, or immersive web experiences. Triggers on task
What you get
Grounded threejs-webgl guidance with highlights, triggers, and evidence quotes from SKILL.md.
- Three.js scene and renderer code
- Mesh, material, and animation configurations
Files
Three.js WebGL/WebGPU Development
Overview
Three.js is the industry-standard JavaScript library for creating 3D graphics in web browsers using WebGL and WebGPU. This skill provides comprehensive guidance for building performant, interactive 3D experiences including scenes, cameras, renderers, geometries, materials, lights, textures, and animations.
Core Concepts
Scene Graph Architecture
Three.js uses a hierarchical scene graph where all 3D objects are organized in a tree structure:
Scene
├── Camera
├── Lights
│ ├── AmbientLight
│ ├── DirectionalLight
│ └── PointLight
├── Meshes
│ ├── Mesh (Geometry + Material)
│ └── InstancedMesh
└── GroupsEssential Components
Every Three.js application requires these core elements:
1. Scene: Container for all 3D objects 2. Camera: Defines the viewing perspective 3. Renderer: Draws the scene to canvas (WebGL or WebGPU) 4. Geometry: Defines the shape of objects 5. Material: Defines the surface appearance 6. Mesh: Combines geometry and material
Quick Start Pattern
Basic Scene Setup
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene, Camera, Renderer
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);
const camera = new THREE.PerspectiveCamera(
75, // FOV
window.innerWidth / window.innerHeight, // Aspect ratio
0.1, // Near clipping plane
1000 // Far clipping plane
);
camera.position.set(0, 2, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true;
scene.add(directionalLight);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Animation Loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
// Handle Resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});WebGPU Setup (Modern Alternative)
import * as THREE from 'three/webgpu';
const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
renderer.toneMapping = THREE.LinearToneMapping;
renderer.toneMappingExposure = 1;
document.body.appendChild(renderer.domElement);Common Patterns
1. Creating Meshes with Materials
// Basic Mesh
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({
color: 0x00ff00,
roughness: 0.5,
metalness: 0.5
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// Textured Mesh
const loader = new THREE.TextureLoader();
const texture = loader.load('texture.jpg');
texture.colorSpace = THREE.SRGBColorSpace;
const texturedMaterial = new THREE.MeshStandardMaterial({
map: texture
});
const mesh = new THREE.Mesh(geometry, texturedMaterial);
scene.add(mesh);2. Lighting Strategies
// Three-Point Lighting Setup
function setupThreePointLight(scene) {
// Key Light (Main)
const keyLight = new THREE.DirectionalLight(0xffffff, 3);
keyLight.position.set(5, 10, 7.5);
keyLight.castShadow = true;
scene.add(keyLight);
// Fill Light (Softens shadows)
const fillLight = new THREE.DirectionalLight(0xffffff, 1);
fillLight.position.set(-5, 5, -5);
scene.add(fillLight);
// Rim Light (Edge definition)
const rimLight = new THREE.DirectionalLight(0xffffff, 0.5);
rimLight.position.set(0, 5, -10);
scene.add(rimLight);
// Ambient (Base illumination)
const ambient = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambient);
}
// Physical Light (Realistic)
const bulbLight = new THREE.PointLight(0xffee88, 1, 100, 2);
bulbLight.power = 1700; // Lumens (100W bulb equivalent)
bulbLight.castShadow = true;
scene.add(bulbLight);
// Hemisphere Light (Sky + Ground)
const hemiLight = new THREE.HemisphereLight(
0xddeeff, // Sky color
0x0f0e0d, // Ground color
0.02
);
scene.add(hemiLight);3. Instanced Geometry (Performance)
// For rendering thousands of similar objects efficiently
const geometry = new THREE.SphereGeometry(0.1, 16, 16);
const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);
const matrix = new THREE.Matrix4();
const color = new THREE.Color();
for (let i = 0; i < 1000; i++) {
matrix.setPosition(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5
);
instancedMesh.setMatrixAt(i, matrix);
instancedMesh.setColorAt(i, color.setHex(Math.random() * 0xffffff));
}
instancedMesh.instanceMatrix.needsUpdate = true;
scene.add(instancedMesh);4. Loading 3D Models (glTF)
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
// Setup loaders
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
// Load model
gltfLoader.load('model.glb', (gltf) => {
const model = gltf.scene;
// Enable shadows
model.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add(model);
// Handle animations
if (gltf.animations.length > 0) {
const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
// In animation loop:
// mixer.update(deltaTime);
}
});5. Shadow Configuration
// Enable shadows on renderer
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // or VSMShadowMap
// Configure light shadows
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
directionalLight.shadow.radius = 4;
directionalLight.shadow.blurSamples = 8;
// Objects casting/receiving shadows
mesh.castShadow = true;
mesh.receiveShadow = true;6. Raycasting (Interaction)
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
function onMouseClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const object = intersects[0].object;
object.material.color.set(0xff0000);
}
}
window.addEventListener('click', onMouseClick);Integration Patterns
With GSAP for Animation
import gsap from 'gsap';
// Animate camera
gsap.to(camera.position, {
x: 5,
y: 3,
z: 10,
duration: 2,
ease: "power2.inOut",
onUpdate: () => {
camera.lookAt(scene.position);
}
});
// Animate mesh properties
gsap.to(mesh.rotation, {
y: Math.PI * 2,
duration: 3,
repeat: -1,
ease: "none"
});With React (see react-three-fiber skill)
// Three.js integrates naturally with React Three Fiber
// Use the react-three-fiber skill for React integration patternsWith Post-Processing
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // strength
0.4, // radius
0.85 // threshold
);
composer.addPass(bloomPass);
// In animation loop:
composer.render();Performance Optimization
1. Geometry Reuse
// Bad: Creates new geometry for each mesh
for (let i = 0; i < 100; i++) {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
// Good: Reuse geometry
const sharedGeometry = new THREE.BoxGeometry(1, 1, 1);
for (let i = 0; i < 100; i++) {
const mesh = new THREE.Mesh(sharedGeometry, material);
scene.add(mesh);
}2. Use InstancedMesh for Repeated Objects
For hundreds/thousands of identical objects, use InstancedMesh (see pattern above).
3. Texture Optimization
// Compress textures
texture.generateMipmaps = true;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
// Use power-of-two dimensions (512, 1024, 2048)
// Consider texture atlases for multiple small textures4. Level of Detail (LOD)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // 0-50 units
lod.addLevel(mediumDetailMesh, 50); // 50-100 units
lod.addLevel(lowDetailMesh, 100); // 100+ units
scene.add(lod);5. Frustum Culling
Three.js automatically culls objects outside the camera's view. Ensure objects have correct bounding spheres:
mesh.geometry.computeBoundingSphere();6. Dispose Resources
function disposeScene() {
scene.traverse((object) => {
if (object.geometry) object.geometry.dispose();
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach(material => material.dispose());
} else {
object.material.dispose();
}
}
});
renderer.dispose();
}Best Practices
1. Use Animation Clocks for Consistent Timing
const clock = new THREE.Clock();
function animate() {
const deltaTime = clock.getDelta();
const elapsedTime = clock.getElapsedTime();
// Use deltaTime for frame-independent animations
mesh.rotation.y += deltaTime * Math.PI * 0.5; // 90° per second
renderer.render(scene, camera);
}2. Camera Setup Guidelines
- FOV: 45-75° for most applications
- Near plane: As far as possible (avoid z-fighting)
- Far plane: As close as possible (precision)
- Aspect ratio: Always match canvas dimensions
3. Material Selection
- MeshBasicMaterial: Unlit, flat colors (debugging, UI)
- MeshLambertMaterial: Cheap diffuse lighting (mobile)
- MeshPhongMaterial: Specular highlights (older standard)
- MeshStandardMaterial: PBR, realistic (recommended)
- MeshPhysicalMaterial: Advanced PBR (clearcoat, transmission)
4. Coordinate System
- Three.js uses right-handed coordinate system
- +Y is up, +Z is toward camera, +X is right
- Rotations use radians (Math.PI = 180°)
5. Scene Organization
// Group related objects
const building = new THREE.Group();
building.add(walls, roof, windows);
scene.add(building);
// Use meaningful names
mesh.name = 'player-character';
const found = scene.getObjectByName('player-character');Common Pitfalls
1. Not Updating Aspect Ratio on Resize
Always update camera aspect ratio and projection matrix when window resizes.
2. Creating New Objects in Animation Loop
// Bad: Memory leak
function animate() {
const geometry = new THREE.BoxGeometry(); // Created every frame!
// ...
}
// Good: Create once outside loop
const geometry = new THREE.BoxGeometry();
function animate() {
// Reuse geometry
}3. Forgetting to Enable Shadows
Remember to enable shadows on renderer, lights, and objects.
4. Z-Fighting (Flickering)
- Increase near plane distance
- Decrease far plane distance
- Avoid overlapping coplanar surfaces
- Use
material.polygonOffset = truewithmaterial.polygonOffsetFactor
5. Color Space Issues
// Always set color space for textures
texture.colorSpace = THREE.SRGBColorSpace;
// Set renderer output encoding
renderer.outputColorSpace = THREE.SRGBColorSpace;6. Not Disposing Resources
Always call .dispose() on geometries, materials, textures, and renderers when no longer needed.
Resources
This skill includes bundled resources to accelerate Three.js development:
references/
api_reference.md: Quick API reference for core classes (Scene, Camera, Renderer, etc.)materials_guide.md: Comprehensive material types and propertiesoptimization_checklist.md: Performance optimization strategies
scripts/
setup_scene.py: Generate boilerplate Three.js scene setup codetexture_optimizer.py: Batch optimize textures for web (resize, compress)gltf_validator.py: Validate glTF models before use
assets/
starter_scene/: Complete HTML/JS boilerplate projectshaders/: Custom GLSL shader examples (vertex, fragment)hdri/: Environment maps for PBR lightingdraco/: DRACO decoder for compressed models
Advanced Topics
Custom Shaders (GLSL)
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0.0 },
uColor: { value: new THREE.Color(0x00ff00) }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float uTime;
uniform vec3 uColor;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(uColor * vUv.x, 1.0);
}
`
});Render Targets (Render-to-Texture)
const renderTarget = new THREE.WebGLRenderTarget(512, 512);
// Render scene to texture
renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null);
// Use texture
const material = new THREE.MeshBasicMaterial({
map: renderTarget.texture
});GPU Computation (GPGPU)
Use GPUComputationRenderer for particle simulations, cloth physics, etc.
When to Use This Skill
Use this skill when:
- Building interactive 3D web experiences
- Creating product configurators or visualizers
- Implementing WebGL/WebGPU rendering
- Working with 3D models, scenes, or animations
- Optimizing Three.js performance
- Integrating Three.js with other libraries (GSAP, React, etc.)
- Debugging Three.js rendering issues
For React integration, use the react-three-fiber skill. For animation, combine with the gsap-scrolltrigger skill. For UI animations, use the motion-framer skill.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Starter Scene</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
canvas {
display: block;
}
#info {
position: absolute;
top: 20px;
left: 20px;
padding: 15px 20px;
background: rgba(0, 0, 0, 0.7);
color: white;
border-radius: 8px;
font-size: 14px;
line-height: 1.6;
pointer-events: none;
backdrop-filter: blur(10px);
}
#info h1 {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
#info p {
margin: 0;
opacity: 0.8;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 30px 40px;
border-radius: 12px;
font-size: 16px;
backdrop-filter: blur(10px);
}
#loading.hidden {
display: none;
}
</style>
</head>
<body>
<div id="loading">Loading Scene...</div>
<div id="info">
<h1>Three.js Starter Scene</h1>
<p>Click and drag to orbit • Scroll to zoom</p>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module" src="./main.js"></script>
</body>
</html>
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene, Camera, Renderer
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
scene.fog = new THREE.Fog(0x1a1a2e, 10, 50);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(5, 3, 8);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild(renderer.domElement);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 3;
controls.maxDistance = 20;
controls.maxPolarAngle = Math.PI / 2;
controls.target.set(0, 1, 0);
// Lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
scene.add(directionalLight);
// Add point light for accent
const pointLight = new THREE.PointLight(0x00ffff, 0.5, 15);
pointLight.position.set(-3, 3, -3);
scene.add(pointLight);
// Floor
const floorGeometry = new THREE.PlaneGeometry(20, 20);
const floorMaterial = new THREE.MeshStandardMaterial({
color: 0x2a2a4a,
roughness: 0.8,
metalness: 0.2
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
// Grid Helper
const gridHelper = new THREE.GridHelper(20, 20, 0x444466, 0x222244);
gridHelper.position.y = 0.01;
scene.add(gridHelper);
// Demo Objects
const objectsGroup = new THREE.Group();
scene.add(objectsGroup);
// Spinning cube
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
roughness: 0.3,
metalness: 0.7
});
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(-2, 1, 0);
cube.castShadow = true;
cube.receiveShadow = true;
objectsGroup.add(cube);
// Sphere
const sphereGeometry = new THREE.SphereGeometry(0.7, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({
color: 0x4ecdc4,
roughness: 0.2,
metalness: 0.8
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(0, 1.2, 0);
sphere.castShadow = true;
sphere.receiveShadow = true;
objectsGroup.add(sphere);
// Torus
const torusGeometry = new THREE.TorusGeometry(0.6, 0.25, 16, 32);
const torusMaterial = new THREE.MeshStandardMaterial({
color: 0xffe66d,
roughness: 0.4,
metalness: 0.6
});
const torus = new THREE.Mesh(torusGeometry, torusMaterial);
torus.position.set(2, 1, 0);
torus.castShadow = true;
torus.receiveShadow = true;
objectsGroup.add(torus);
// Animation Loop
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
const elapsedTime = clock.getElapsedTime();
// Animate objects
cube.rotation.y += deltaTime * 0.5;
cube.rotation.x += deltaTime * 0.25;
sphere.position.y = 1.2 + Math.sin(elapsedTime * 2) * 0.3;
torus.rotation.x += deltaTime * 0.3;
torus.rotation.y += deltaTime * 0.5;
// Animate point light
pointLight.position.x = Math.sin(elapsedTime * 0.5) * 5;
pointLight.position.z = Math.cos(elapsedTime * 0.5) * 5;
// Update controls
controls.update();
// Render
renderer.render(scene, camera);
}
// Handle Resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Hide loading screen and start animation
document.getElementById('loading').classList.add('hidden');
animate();
Three.js Starter Scene
A complete, production-ready Three.js boilerplate project with modern best practices.
Features
✅ WebGL renderer with antialiasing ✅ PBR materials (MeshStandardMaterial) ✅ Shadow mapping ✅ Tone mapping (ACES Filmic) ✅ Orbit controls with damping ✅ Responsive design ✅ Loading screen ✅ Animated demo objects ✅ Performance optimizations
Quick Start
Option 1: Local Development
# Serve with any static server
python -m http.server 8000
# Or
npx serveOpen http://localhost:8000 in your browser.
Option 2: Add to Existing Project
Copy index.html and main.js to your project and customize as needed.
Project Structure
starter_scene/
├── index.html # HTML with embedded styles and import map
├── main.js # Main Three.js scene setup
└── README.md # This fileCustomization
Change Background Color
// In main.js
scene.background = new THREE.Color(0x1a1a2e); // Change hex colorAdd Your Own Objects
// After existing objects in main.js
const myGeometry = new THREE.BoxGeometry(1, 1, 1);
const myMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const myMesh = new THREE.Mesh(myGeometry, myMaterial);
myMesh.position.set(x, y, z);
myMesh.castShadow = true;
scene.add(myMesh);Load 3D Models
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
scene.add(gltf.scene);
});Adjust Performance
// Reduce pixel ratio for better performance
renderer.setPixelRatio(1);
// Disable shadows
renderer.shadowMap.enabled = false;
// Simplify materials
material.roughness = 1.0; // More matte = fasterBuilt-in Demo
The starter scene includes:
- Red Cube: Rotating on multiple axes
- Cyan Sphere: Bobbing up and down
- Yellow Torus: Rotating ring
- Animated Point Light: Orbiting the scene
- Grid Floor: With shadows
Controls
- Left Click + Drag: Orbit camera
- Scroll: Zoom in/out
- Right Click + Drag: Pan (if enabled)
Browser Compatibility
- Chrome/Edge: ✅ Excellent
- Firefox: ✅ Excellent
- Safari: ✅ Good (WebGL only)
Performance Tips
1. Reduce renderer.setPixelRatio() to 1 on mobile 2. Lower shadow map size for better FPS 3. Use simpler materials (MeshLambertMaterial) on mobile 4. Implement LOD for complex scenes 5. Use InstancedMesh for repeated objects
Next Steps
1. Replace demo objects with your own content 2. Add textures and materials 3. Implement raycasting for interactivity 4. Add post-processing effects 5. Optimize for production
Resources
License
This starter template is provided as-is for use in any project.
Three.js API Quick Reference
Core Classes
Scene
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.Fog(0xffffff, 10, 100);
scene.add(object);
scene.remove(object);
scene.getObjectByName('name');Camera
PerspectiveCamera
new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(x, y, z);
camera.lookAt(target);
camera.updateProjectionMatrix(); // Call after changing propertiesCommon FOVs:
- 45° - Natural perspective
- 50° - Default for many apps
- 75° - Wide, immersive feel
OrthographicCamera
new THREE.OrthographicCamera(left, right, top, bottom, near, far);
// Useful for 2D/isometric viewsRenderer
WebGLRenderer
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: "high-performance"
});
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;WebGPURenderer
const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setAnimationLoop(animate);Geometry
Built-in Geometries
new THREE.BoxGeometry(width, height, depth);
new THREE.SphereGeometry(radius, widthSegments, heightSegments);
new THREE.PlaneGeometry(width, height);
new THREE.CylinderGeometry(radiusTop, radiusBottom, height, radialSegments);
new THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments);BufferGeometry (Custom)
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([...]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.computeVertexNormals();
geometry.computeBoundingSphere();Geometry Operations
geometry.dispose(); // Free memory
geometry.center(); // Center geometry at origin
geometry.scale(x, y, z);
geometry.rotateX(angle);
geometry.translate(x, y, z);Materials
Common Properties
{
color: 0xff0000,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide, // FrontSide, BackSide, DoubleSide
depthWrite: true,
depthTest: true,
wireframe: false
}Material Types
new THREE.MeshBasicMaterial({}); // Unlit
new THREE.MeshLambertMaterial({}); // Simple diffuse
new THREE.MeshPhongMaterial({ shininess: 30 }); // Specular
new THREE.MeshStandardMaterial({ roughness: 0.5, metalness: 0.5 }); // PBR
new THREE.MeshPhysicalMaterial({ // Advanced PBR
roughness: 0.0,
metalness: 0.0,
clearcoat: 1.0,
clearcoatRoughness: 0.1,
transmission: 1.0,
ior: 1.5
});Lights
AmbientLight
new THREE.AmbientLight(0xffffff, 0.5);
// Illuminates all objects equallyDirectionalLight
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 7.5);
light.castShadow = true;
// Parallel rays (like sunlight)PointLight
const light = new THREE.PointLight(0xffffff, 1, distance, decay);
light.castShadow = true;
// Radiates in all directionsSpotLight
const light = new THREE.SpotLight(0xffffff, 1, distance, angle, penumbra, decay);
light.target.position.set(x, y, z);
light.castShadow = true;HemisphereLight
new THREE.HemisphereLight(skyColor, groundColor, intensity);
// Sky and ground hemisphere lightingLight Properties
light.intensity = 1.0;
light.color.set(0xff0000);
light.power = 1700; // Lumens (for PointLight)
light.visible = false;Textures
TextureLoader
const loader = new THREE.TextureLoader();
const texture = loader.load('texture.jpg', onLoad, onProgress, onError);
texture.colorSpace = THREE.SRGBColorSpace;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);
texture.offset.set(0.5, 0.5);
texture.rotation = Math.PI / 4;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();Texture Types
material.map = diffuseTexture;
material.normalMap = normalTexture;
material.roughnessMap = roughnessTexture;
material.metalnessMap = metalnessTexture;
material.emissiveMap = emissiveTexture;
material.aoMap = aoTexture;
material.bumpMap = bumpTexture;
material.displacementMap = displacementTexture;
material.alphaMap = alphaTexture;Mesh
Creation
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, z);
mesh.rotation.set(x, y, z); // Radians
mesh.scale.set(x, y, z);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.visible = true;
mesh.name = 'my-mesh';Transformations
mesh.translateX(distance);
mesh.translateY(distance);
mesh.translateZ(distance);
mesh.rotateX(angle);
mesh.rotateY(angle);
mesh.rotateZ(angle);
mesh.lookAt(targetVector);Matrix Operations
mesh.updateMatrix();
mesh.updateMatrixWorld(force);
mesh.applyMatrix4(matrix);Groups
const group = new THREE.Group();
group.add(mesh1, mesh2, mesh3);
group.position.set(x, y, z);
scene.add(group);Animation
AnimationMixer
const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
// In animation loop:
const delta = clock.getDelta();
mixer.update(delta);AnimationAction Methods
action.play();
action.stop();
action.pause();
action.reset();
action.setLoop(THREE.LoopRepeat, Infinity);
action.setDuration(seconds);
action.clampWhenFinished = true;
action.fadeIn(duration);
action.fadeOut(duration);
action.crossFadeTo(otherAction, duration);Loaders
GLTFLoader
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
scene.add(gltf.scene);
}, onProgress, onError);DRACOLoader
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
gltfLoader.setDRACOLoader(dracoLoader);OBJLoader, FBXLoader
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';Controls
OrbitControls
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 5;
controls.maxDistance = 50;
controls.maxPolarAngle = Math.PI / 2;
controls.target.set(0, 0, 0);
controls.update(); // Call in animation loop if damping enabledMapControls, TrackballControls, FlyControls
import { MapControls } from 'three/addons/controls/MapControls.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import { FlyControls } from 'three/addons/controls/FlyControls.js';Raycaster
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const object = intersects[0].object;
const point = intersects[0].point; // Intersection point
const face = intersects[0].face; // Intersected face
const distance = intersects[0].distance;
}Math Utilities
Vector3
const v = new THREE.Vector3(x, y, z);
v.set(x, y, z);
v.add(otherVector);
v.sub(otherVector);
v.multiply(otherVector);
v.multiplyScalar(scalar);
v.normalize();
v.length();
v.distanceTo(otherVector);
v.lerp(otherVector, alpha);
v.cross(otherVector);
v.dot(otherVector);Quaternion
const q = new THREE.Quaternion();
q.setFromEuler(euler);
q.setFromAxisAngle(axis, angle);
mesh.quaternion.copy(q);Clock
const clock = new THREE.Clock();
const delta = clock.getDelta(); // Time since last call
const elapsed = clock.getElapsedTime(); // Total time
clock.start();
clock.stop();Post-Processing
EffectComposer
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.render();Common Passes
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';Helpers
new THREE.AxesHelper(size);
new THREE.GridHelper(size, divisions);
new THREE.CameraHelper(camera);
new THREE.DirectionalLightHelper(light, size);
new THREE.SpotLightHelper(light);
new THREE.BoxHelper(object, color);Constants
Side
THREE.FrontSide(default)THREE.BackSideTHREE.DoubleSide
Blending Modes
THREE.NormalBlending(default)THREE.AdditiveBlendingTHREE.SubtractiveBlendingTHREE.MultiplyBlending
Shadow Map Types
THREE.BasicShadowMapTHREE.PCFShadowMapTHREE.PCFSoftShadowMapTHREE.VSMShadowMap
Tone Mapping
THREE.NoToneMappingTHREE.LinearToneMappingTHREE.ReinhardToneMappingTHREE.CineonToneMappingTHREE.ACESFilmicToneMapping
Color Spaces
THREE.SRGBColorSpaceTHREE.LinearSRGBColorSpace
Performance Tips
// Dispose resources
geometry.dispose();
material.dispose();
texture.dispose();
renderer.dispose();
// Frustum culling (automatic)
mesh.frustumCulled = true;
// Matrix updates
mesh.matrixAutoUpdate = false; // Manual control
mesh.updateMatrix();
// Render on demand
function render() {
renderer.render(scene, camera);
}
// Call render() only when needed
// Use InstancedMesh for repeated objects
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);Common Gotchas
1. Always update projection matrix after changing camera properties 2. Set texture.colorSpace = THREE.SRGBColorSpace for diffuse textures 3. Enable shadows on renderer, lights, and objects 4. Dispose geometries, materials, and textures to prevent memory leaks 5. Use Clock.getDelta() for frame-independent animations 6. Call controls.update() in animation loop if damping is enabled
Three.js Materials Comprehensive Guide
Material Selection Decision Tree
Need lighting?
├─ NO → MeshBasicMaterial
└─ YES → Need PBR realism?
├─ NO → Need specular highlights?
│ ├─ YES → MeshPhongMaterial
│ └─ NO → MeshLambertMaterial
└─ YES → Need advanced effects?
├─ YES → MeshPhysicalMaterial
└─ NO → MeshStandardMaterialMaterial Comparison Table
| Material | Lighting | PBR | Performance | Use Case |
|---|---|---|---|---|
| MeshBasicMaterial | ✗ | ✗ | Excellent | UI, debugging, unlit scenes |
| MeshLambertMaterial | ✓ | ✗ | Very Good | Mobile, simple diffuse |
| MeshPhongMaterial | ✓ | ✗ | Good | Legacy, specular highlights |
| MeshStandardMaterial | ✓ | ✓ | Moderate | Most realistic scenes |
| MeshPhysicalMaterial | ✓ | ✓✓ | Lower | Advanced materials (glass, car paint) |
| MeshToonMaterial | ✓ | ✗ | Very Good | Cel-shaded / cartoon style |
| ShaderMaterial | Custom | Custom | Varies | Complete custom control |
MeshBasicMaterial
Use for: UI elements, debugging, flat-colored objects, unlit scenes
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
wireframe: false,
transparent: false,
opacity: 1.0,
side: THREE.FrontSide,
map: texture,
alphaMap: alphaTexture,
envMap: environmentMap,
combine: THREE.MultiplyOperation, // For envMap
reflectivity: 1.0,
refractionRatio: 0.98
});Key Features:
- No lighting calculations (fastest)
- Flat, unshaded appearance
- Always visible regardless of lights
- Good for backgrounds, UI, or stylized looks
Performance: ⭐⭐⭐⭐⭐
MeshLambertMaterial
Use for: Mobile devices, simple diffuse surfaces, performance-critical scenes
const material = new THREE.MeshLambertMaterial({
color: 0xff0000,
emissive: 0x000000,
emissiveIntensity: 1.0,
emissiveMap: null,
map: texture,
lightMap: lightMapTexture,
lightMapIntensity: 1.0,
aoMap: aoTexture,
aoMapIntensity: 1.0
});Key Features:
- Simple diffuse (matte) lighting
- No specular highlights
- Cheaper than Phong/Standard
- Good for organic, non-reflective surfaces
Performance: ⭐⭐⭐⭐
MeshPhongMaterial
Use for: Legacy projects, objects with visible specular highlights
const material = new THREE.MeshPhongMaterial({
color: 0xff0000,
specular: 0x111111,
shininess: 30,
emissive: 0x000000,
emissiveIntensity: 1.0,
map: texture,
normalMap: normalTexture,
normalScale: new THREE.Vector2(1, 1),
bumpMap: bumpTexture,
bumpScale: 1.0,
specularMap: specTexture
});Key Features:
- Diffuse + specular highlights
- Adjustable shininess
- Per-pixel lighting
- Legacy, prefer MeshStandardMaterial for new projects
Performance: ⭐⭐⭐
MeshStandardMaterial (PBR)
Use for: Realistic materials, production-quality scenes, modern workflows
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
roughness: 0.5, // 0 = mirror, 1 = matte
metalness: 0.5, // 0 = dielectric, 1 = metal
map: diffuseTexture,
normalMap: normalTexture,
normalScale: new THREE.Vector2(1, 1),
roughnessMap: roughnessTexture,
metalnessMap: metalnessTexture,
aoMap: aoTexture,
aoMapIntensity: 1.0,
emissive: 0x000000,
emissiveMap: emissiveTexture,
emissiveIntensity: 1.0,
envMap: environmentMap,
envMapIntensity: 1.0,
bumpMap: bumpTexture,
bumpScale: 1.0,
displacementMap: dispTexture,
displacementScale: 1.0,
displacementBias: 0.0,
alphaMap: alphaTexture,
flatShading: false
});Key Features:
- Physically Based Rendering (PBR)
- Energy-conserving reflections
- Works with HDR environment maps
- Industry-standard workflow (glTF)
Roughness Guide:
- 0.0 - Perfect mirror (chrome, polished metal)
- 0.2 - Very glossy (wet surfaces, varnished wood)
- 0.5 - Moderate (painted metal, plastic)
- 0.8 - Matte (fabric, unpolished wood)
- 1.0 - Completely diffuse (clay, concrete)
Metalness Guide:
- 0.0 - Non-metal (wood, plastic, skin, fabric)
- 1.0 - Metal (gold, silver, copper, iron)
- Avoid values between 0-1 (physically incorrect)
Performance: ⭐⭐⭐
MeshPhysicalMaterial (Advanced PBR)
Use for: Glass, car paint, clearcoat, transmission, iridescence
const material = new THREE.MeshPhysicalMaterial({
// All MeshStandardMaterial properties, plus:
clearcoat: 1.0, // 0-1, adds glossy layer on top
clearcoatRoughness: 0.1,
clearcoatMap: clearcoatTexture,
clearcoatRoughnessMap: clearcoatRoughnessTexture,
clearcoatNormalMap: clearcoatNormalTexture,
clearcoatNormalScale: new THREE.Vector2(1, 1),
transmission: 1.0, // 0-1, for glass/transparency
thickness: 1.0, // Subsurface thickness
thicknessMap: thicknessTexture,
ior: 1.5, // Index of refraction (glass ~1.5, water ~1.33, diamond ~2.4)
sheen: 1.0, // Fabric-like sheen
sheenRoughness: 0.5,
sheenColor: new THREE.Color(0xffffff),
iridescence: 1.0, // Soap bubble, oil slick effect
iridescenceIOR: 1.3,
iridescenceThicknessRange: [100, 400]
});Use Cases:
Glass
{
roughness: 0.0,
metalness: 0.0,
transmission: 1.0,
thickness: 1.0,
ior: 1.5
}Car Paint
{
roughness: 0.4,
metalness: 0.8,
clearcoat: 1.0,
clearcoatRoughness: 0.1,
color: 0xff0000
}Fabric (Velvet, Satin)
{
roughness: 0.8,
metalness: 0.0,
sheen: 1.0,
sheenRoughness: 0.5,
sheenColor: new THREE.Color(0xffffff)
}Soap Bubble
{
roughness: 0.0,
metalness: 0.0,
transmission: 1.0,
thickness: 0.5,
iridescence: 1.0,
iridescenceIOR: 1.3
}Performance: ⭐⭐
MeshToonMaterial
Use for: Cel-shaded, cartoon, or stylized looks
const material = new THREE.MeshToonMaterial({
color: 0xff0000,
map: texture,
gradientMap: gradientTexture, // Controls toon shading steps
emissive: 0x000000
});Key Features:
- Discrete shading levels (cel-shaded)
- Stylized, non-realistic look
- Good performance
Performance: ⭐⭐⭐⭐
Material Properties Deep Dive
Common Properties (All Materials)
{
// Visibility
visible: true,
transparent: false,
opacity: 1.0,
alphaTest: 0.5, // Discard pixels below this alpha
// Rendering
side: THREE.FrontSide, // FrontSide, BackSide, DoubleSide
depthTest: true,
depthWrite: true,
blending: THREE.NormalBlending,
// Color
color: 0xffffff,
vertexColors: false,
// Wireframe
wireframe: false,
wireframeLinewidth: 1, // Not all platforms support
// Clipping
clipShadows: false,
clipIntersection: false,
clippingPlanes: [],
// Precision
precision: "highp", // "lowp", "mediump", "highp"
// Fog
fog: true
}Texture Properties
const texture = loader.load('texture.jpg');
// Color space (IMPORTANT!)
texture.colorSpace = THREE.SRGBColorSpace; // For diffuse/color maps
texture.colorSpace = THREE.LinearSRGBColorSpace; // For data maps (normal, roughness)
// Wrapping
texture.wrapS = THREE.RepeatWrapping; // ClampToEdgeWrapping, MirroredRepeatWrapping
texture.wrapT = THREE.RepeatWrapping;
// Repeat & Offset
texture.repeat.set(2, 2); // Tile 2x2
texture.offset.set(0.5, 0.5);
texture.rotation = Math.PI / 4;
texture.center.set(0.5, 0.5); // Rotation center
// Filtering
texture.minFilter = THREE.LinearMipmapLinearFilter; // Minification
texture.magFilter = THREE.LinearFilter; // Magnification
texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); // Reduce blur at angles
// Mipmaps
texture.generateMipmaps = true;Texture Types & Color Spaces
| Texture Type | Color Space | Purpose |
|---|---|---|
| map (diffuse) | SRGB | Base color |
| normalMap | Linear | Surface detail |
| roughnessMap | Linear | Surface roughness |
| metalnessMap | Linear | Metallic areas |
| aoMap | Linear | Ambient occlusion |
| emissiveMap | SRGB | Glow |
| bumpMap | Linear | Height data |
| displacementMap | Linear | Vertex displacement |
| alphaMap | Linear | Transparency mask |
Material Optimization Tips
1. Texture Atlas
Combine multiple textures into one to reduce draw calls.
2. Share Materials
// Good: Share material across meshes
const sharedMaterial = new THREE.MeshStandardMaterial({...});
const mesh1 = new THREE.Mesh(geo1, sharedMaterial);
const mesh2 = new THREE.Mesh(geo2, sharedMaterial);
// Bad: New material per mesh
const mesh1 = new THREE.Mesh(geo1, new THREE.MeshStandardMaterial({...}));3. Texture Size
- Use power-of-two dimensions (512, 1024, 2048)
- Compress textures (JPEG for photos, PNG for alpha)
- Consider KTX2/Basis Universal for web
4. Disable Unnecessary Features
material.needsUpdate = false; // After initial setup
renderer.shadowMap.autoUpdate = false; // If shadows don't change5. Use InstancedMesh with Materials
For identical objects with the same material.
Custom Shaders (ShaderMaterial)
Use for: Complete custom control, special effects, optimizations
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0.0 },
uColor: { value: new THREE.Color(0xff0000) },
uTexture: { value: texture }
},
vertexShader: `
uniform float uTime;
varying vec2 vUv;
varying vec3 vNormal;
void main() {
vUv = uv;
vNormal = normal;
vec3 pos = position;
pos.z += sin(pos.x * 10.0 + uTime) * 0.1;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
uniform vec3 uColor;
uniform sampler2D uTexture;
varying vec2 vUv;
varying vec3 vNormal;
void main() {
vec4 texColor = texture2D(uTexture, vUv);
gl_FragColor = vec4(uColor * texColor.rgb, 1.0);
}
`,
transparent: false,
side: THREE.DoubleSide
});
// Update uniforms in animation loop
material.uniforms.uTime.value = elapsedTime;Material Disposal
Always dispose materials when no longer needed:
material.dispose();
// Dispose textures too
if (material.map) material.map.dispose();
if (material.normalMap) material.normalMap.dispose();
if (material.roughnessMap) material.roughnessMap.dispose();
// ... etcTroubleshooting
Material Appears Black
- No lights in scene (for Lambert/Phong/Standard)
- Normals inverted
- Material side setting incorrect
Material Too Shiny
- Reduce roughness (Standard/Physical)
- Reduce shininess (Phong)
- Check roughnessMap is loaded
Material Not Transparent
material.transparent = true;
material.opacity = 0.5;
material.depthWrite = false; // For glass-like materialsTexture Not Showing
- Check texture.colorSpace (SRGB for diffuse)
- Ensure geometry has UV coordinates
- Verify texture loaded successfully
Z-Fighting / Flickering
- Adjust material.polygonOffset
material.polygonOffset = true;
material.polygonOffsetFactor = -1;
material.polygonOffsetUnits = -1;Material Performance Ranking
1. MeshBasicMaterial - Fastest 2. MeshToonMaterial - Very Fast 3. MeshLambertMaterial - Fast 4. MeshPhongMaterial - Moderate 5. MeshStandardMaterial - Moderate-Slow 6. MeshPhysicalMaterial - Slowest
Use the simplest material that achieves your visual goals.
Three.js Performance Optimization Checklist
Quick Wins (High Impact, Low Effort)
✅ Geometry Optimization
- [ ] Reuse geometries across multiple meshes
const sharedGeometry = new THREE.BoxGeometry(1, 1, 1);
// Use for all boxes instead of creating new geometry each time- [ ] Use InstancedMesh for repeated objects (>50 identical objects)
const mesh = new THREE.InstancedMesh(geometry, material, 1000);- [ ] Reduce polygon count where not visible
- Use simpler geometries for distant objects
- Implement LOD (Level of Detail)
- [ ] Dispose unused geometries
geometry.dispose();✅ Material Optimization
- [ ] Share materials across meshes when possible
- [ ] Use simpler materials:
- MeshBasicMaterial for unlit objects
- MeshLambertMaterial for mobile
- MeshStandardMaterial only when PBR needed
- [ ] Dispose unused materials
material.dispose();✅ Texture Optimization
- [ ] Use power-of-two dimensions (512, 1024, 2048)
- [ ] Compress textures:
- JPEG for photos (smaller file size)
- PNG for transparency
- Consider KTX2/Basis Universal for web
- [ ] Set correct color space:
diffuseTexture.colorSpace = THREE.SRGBColorSpace;
normalMap.colorSpace = THREE.LinearSRGBColorSpace;- [ ] Limit texture resolution:
- 2048x2048 max for most cases
- 1024x1024 for mobile
- 512x512 for background/UI elements
- [ ] Enable mipmaps and anisotropy:
texture.generateMipmaps = true;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();- [ ] Dispose unused textures:
texture.dispose();✅ Rendering Optimization
- [ ] Set pixel ratio appropriately:
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// Don't use full devicePixelRatio on high-DPI displays- [ ] Disable antialiasing on mobile
- [ ] Use render-on-demand when scene is static:
function render() {
renderer.render(scene, camera);
}
// Call only when needed, not in requestAnimationFrame loop✅ Shadow Optimization
- [ ] Limit number of shadow-casting lights (2-3 max)
- [ ] Reduce shadow map size:
light.shadow.mapSize.width = 1024; // Lower for mobile
light.shadow.mapSize.height = 1024;- [ ] Optimize shadow camera frustum:
light.shadow.camera.near = 1;
light.shadow.camera.far = 20; // Only as far as needed
light.shadow.camera.left = -10;
light.shadow.camera.right = 10;
// ... etc - Tight bounds around scene- [ ] Disable shadow updates when static:
renderer.shadowMap.autoUpdate = false;
renderer.shadowMap.needsUpdate = true; // Only when changedMedium Effort Optimizations
🔧 Culling & Visibility
- [ ] Enable frustum culling (enabled by default):
mesh.frustumCulled = true;- [ ] Compute bounding spheres for custom geometries:
geometry.computeBoundingSphere();- [ ] Hide offscreen objects:
if (distanceToCamera > threshold) {
mesh.visible = false;
}- [ ] Use layers for selective rendering:
mesh.layers.set(1);
camera.layers.enable(1);🔧 Level of Detail (LOD)
- [ ] Implement LOD for complex objects:
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);
lod.addLevel(mediumDetailMesh, 50);
lod.addLevel(lowDetailMesh, 100);
scene.add(lod);🔧 Draw Call Reduction
- [ ] Merge static geometries:
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
const merged = mergeGeometries([geo1, geo2, geo3]);- [ ] Use texture atlases to combine multiple textures
- [ ] Batch similar materials together
🔧 Animation Optimization
- [ ] Use Clock.getDelta() for frame-independent animations:
const delta = clock.getDelta();
mixer.update(delta);- [ ] Pause animations when offscreen:
if (!mesh.visible) {
mixer.stop();
}- [ ] Limit AnimationMixer updates to visible objects
🔧 Post-Processing Optimization
- [ ] Reduce effect quality on mobile
- [ ] Limit bloom/blur passes
- [ ] Use lower resolution render targets:
const renderTarget = new THREE.WebGLRenderTarget(
window.innerWidth * 0.5,
window.innerHeight * 0.5
);Advanced Optimizations
⚙️ Memory Management
- [ ] Dispose all resources when removing from scene:
function disposeObject(obj) {
if (obj.geometry) obj.geometry.dispose();
if (obj.material) {
if (Array.isArray(obj.material)) {
obj.material.forEach(m => m.dispose());
} else {
obj.material.dispose();
}
}
if (obj.dispose) obj.dispose();
}
scene.traverse(disposeObject);- [ ] Clear render targets when done:
renderTarget.dispose();- [ ] Monitor memory usage:
console.log(renderer.info.memory);
console.log(renderer.info.render);⚙️ Matrix Optimization
- [ ] Disable auto-update for static objects:
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();- [ ] Update world matrix manually when needed:
mesh.matrixWorldNeedsUpdate = true;⚙️ Custom Shaders
- [ ] Use low precision where possible:
precision mediump float; // Instead of highp- [ ] Minimize texture samples in fragment shader
- [ ] Move calculations to vertex shader when possible
- [ ] Use built-in GLSL functions (faster than custom)
⚙️ Lighting Optimization
- [ ] Limit number of real-time lights (3-5 max)
- [ ] Use baked lighting for static scenes:
- Lightmaps
- AO maps
- Environment maps
- [ ] Combine directional lights where possible
- [ ] Use AmbientLight + DirectionalLight as base setup
⚙️ Model Optimization
- [ ] Use glTF with Draco compression:
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
gltfLoader.setDRACOLoader(dracoLoader);- [ ] Remove unused data from models:
- Multiple UV sets
- Unused vertex colors
- Unused morph targets
- [ ] Optimize mesh topology:
- Remove hidden faces
- Reduce triangle count
- Use instancing for repeated elements
Mobile-Specific Optimizations
📱 Mobile Best Practices
- [ ] Lower pixel ratio:
renderer.setPixelRatio(1);- [ ] Disable antialiasing
- [ ] Use simpler materials (MeshLambertMaterial)
- [ ] Reduce texture resolution (512-1024px max)
- [ ] Limit particle count (<1000)
- [ ] Disable shadows or use lower resolution
- [ ] Reduce geometry complexity by 50%
- [ ] Disable post-processing or use minimal effects
- [ ] Implement aggressive LOD
- [ ] Pause rendering when tab is hidden:
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// Stop animation loop
} else {
// Resume animation loop
}
});Profiling & Debugging
🔍 Performance Monitoring
- [ ] Use Stats.js:
import Stats from 'three/examples/jsm/libs/stats.module.js';
const stats = new Stats();
document.body.appendChild(stats.dom);- [ ] Monitor renderer info:
console.log('Geometries:', renderer.info.memory.geometries);
console.log('Textures:', renderer.info.memory.textures);
console.log('Draw Calls:', renderer.info.render.calls);
console.log('Triangles:', renderer.info.render.triangles);- [ ] Use browser DevTools:
- Performance tab (frame rate)
- Memory tab (heap snapshots)
- Rendering tab (FPS meter, paint flashing)
- [ ] WebGL Performance Tools:
- Spector.js (WebGL inspector)
- Chrome GPU Profiler
🔍 Common Performance Bottlenecks
1. Too many draw calls → Merge geometries, use instancing 2. Too many triangles → Reduce geometry complexity, use LOD 3. Large textures → Compress, reduce resolution 4. Too many lights → Limit lights, use baked lighting 5. Complex shaders → Simplify materials 6. Memory leaks → Dispose resources properly 7. Expensive post-processing → Reduce effects, lower resolution
Performance Targets
🎯 Desktop
- 60 FPS (16.67ms per frame)
- Draw calls: <100
- Triangles: <1M visible
- Texture memory: <500MB
- Pixel ratio: 1-2
🎯 Mobile
- 30-60 FPS (16.67-33ms per frame)
- Draw calls: <50
- Triangles: <100K visible
- Texture memory: <200MB
- Pixel ratio: 1
Optimization Workflow
1. Profile first - Identify actual bottlenecks 2. Optimize bottlenecks - Focus on highest impact 3. Measure improvement - Verify gains 4. Iterate - Repeat process
Remember: Premature optimization is the root of all evil. Profile before optimizing!
Quick Optimization Checklist Summary
✅ Reuse geometries and materials
✅ Use InstancedMesh for repeated objects
✅ Optimize texture size and format
✅ Set pixel ratio to max 2
✅ Limit shadow-casting lights
✅ Dispose unused resources
✅ Implement LOD for complex objects
✅ Reduce draw calls via merging
✅ Profile with Stats.js
✅ Test on target devices (mobile!)#!/usr/bin/env python3
"""
Three.js Scene Setup Generator
Generates boilerplate Three.js scene code with customizable options.
"""
import argparse
import os
from pathlib import Path
TEMPLATES = {
"basic": """import * as THREE from 'three';
import {{ OrbitControls }} from 'three/addons/controls/OrbitControls.js';
// Scene
const scene = new THREE.Scene();
scene.background = new THREE.Color({background_color});
// Camera
const camera = new THREE.PerspectiveCamera(
{fov},
window.innerWidth / window.innerHeight,
{near},
{far}
);
camera.position.set({camera_x}, {camera_y}, {camera_z});
// Renderer
const renderer = new THREE.WebGLRenderer({{ antialias: {antialias} }});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
{shadow_setup}document.body.appendChild(renderer.domElement);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Lights
{lights}
// Animation Loop
const clock = new THREE.Clock();
function animate() {{
requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
controls.update();
renderer.render(scene, camera);
}}
animate();
// Handle Resize
window.addEventListener('resize', () => {{
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}});
""",
"webgpu": """import * as THREE from 'three/webgpu';
import {{ OrbitControls }} from 'three/addons/controls/OrbitControls.js';
// Scene
const scene = new THREE.Scene();
scene.background = new THREE.Color({background_color});
// Camera
const camera = new THREE.PerspectiveCamera(
{fov},
window.innerWidth / window.innerHeight,
{near},
{far}
);
camera.position.set({camera_x}, {camera_y}, {camera_z});
// Renderer (WebGPU)
const renderer = new THREE.WebGPURenderer({{ antialias: {antialias} }});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild(renderer.domElement);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Lights
{lights}
// Animation Loop
const clock = new THREE.Clock();
function animate() {{
const deltaTime = clock.getDelta();
controls.update();
}}
// Handle Resize
window.addEventListener('resize', () => {{
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}});
"""
}
LIGHT_SETUPS = {
"basic": """const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
scene.add(directionalLight);
""",
"shadows": """const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
scene.add(directionalLight);
""",
"physical": """const hemiLight = new THREE.HemisphereLight(0xddeeff, 0x0f0e0d, 0.02);
scene.add(hemiLight);
const bulbLight = new THREE.PointLight(0xffee88, 1, 100, 2);
bulbLight.power = 1700; // 100W bulb
bulbLight.position.set(0, 2, 0);
bulbLight.castShadow = true;
scene.add(bulbLight);
"""
}
def generate_scene(args):
"""Generate Three.js scene code based on arguments."""
# Select template
template = TEMPLATES.get(args.renderer, TEMPLATES["basic"])
# Select light setup
lights = LIGHT_SETUPS.get(args.lighting, LIGHT_SETUPS["basic"])
# Shadow setup
shadow_setup = ""
if args.shadows:
shadow_setup = """renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
"""
# Fill template
code = template.format(
background_color=args.background,
fov=args.fov,
near=args.near,
far=args.far,
camera_x=args.camera[0],
camera_y=args.camera[1],
camera_z=args.camera[2],
antialias=str(args.antialias).lower(),
shadow_setup=shadow_setup,
lights=lights
)
return code
def main():
parser = argparse.ArgumentParser(
description='Generate Three.js scene boilerplate code'
)
# Renderer type
parser.add_argument(
'--renderer',
choices=['basic', 'webgpu'],
default='basic',
help='Renderer type (default: basic)'
)
# Camera settings
parser.add_argument(
'--fov',
type=int,
default=75,
help='Camera field of view (default: 75)'
)
parser.add_argument(
'--near',
type=float,
default=0.1,
help='Camera near plane (default: 0.1)'
)
parser.add_argument(
'--far',
type=int,
default=1000,
help='Camera far plane (default: 1000)'
)
parser.add_argument(
'--camera',
nargs=3,
type=float,
default=[0, 2, 5],
metavar=('X', 'Y', 'Z'),
help='Camera position (default: 0 2 5)'
)
# Scene settings
parser.add_argument(
'--background',
default='0x000000',
help='Background color hex (default: 0x000000)'
)
parser.add_argument(
'--lighting',
choices=['basic', 'shadows', 'physical'],
default='basic',
help='Lighting setup (default: basic)'
)
parser.add_argument(
'--shadows',
action='store_true',
help='Enable shadow rendering'
)
parser.add_argument(
'--antialias',
action='store_true',
default=True,
help='Enable antialiasing (default: True)'
)
# Output
parser.add_argument(
'--output',
'-o',
help='Output file path (default: print to stdout)'
)
args = parser.parse_args()
# Generate code
code = generate_scene(args)
# Output
if args.output:
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(code)
print(f"✅ Generated scene code: {output_path}")
else:
print(code)
if __name__ == '__main__':
main()
Related skills
FAQ
Who is threejs-webgl for?
Developers and software engineers working with threejs-webgl patterns from the skill documentation.
When should I use threejs-webgl?
Comprehensive skill for Three.js 3D web development. Use this skill when building interactive 3D scenes, WebGL/WebGPU applications, product configurators, 3D visualizations, or immersive web experiences. Triggers on tasks involving Three.js, 3D rendering, scenes, cameras, meshes,
Is threejs-webgl safe to install?
Review the Security Audits panel on this page before installing in production.