assets/starter_scene/index.html
<!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>
assets/starter_scene/main.js
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();
assets/starter_scene/README.md
# 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
```bash
# Serve with any static server
python -m http.server 8000
# Or
npx serve
```
Open `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 file
```
## Customization
### Change Background Color
```javascript
// In main.js
scene.background = new THREE.Color(0x1a1a2e); // Change hex color
```
### Add Your Own Objects
```javascript
// 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
```javascript
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
scene.add(gltf.scene);
});
```
### Adjust Performance
```javascript
// Reduce pixel ratio for better performance
renderer.setPixelRatio(1);
// Disable shadows
renderer.shadowMap.enabled = false;
// Simplify materials
material.roughness = 1.0; // More matte = faster
```
## Built-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
- [Three.js Documentation](https://threejs.org/docs/)
- [Three.js Examples](https://threejs.org/examples/)
- [Three.js Fundamentals](https://threejsfundamentals.org/)
## License
This starter template is provided as-is for use in any project.
references/api_reference.md
# Three.js API Quick Reference
## Core Classes
### Scene
```javascript
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
```javascript
new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(x, y, z);
camera.lookAt(target);
camera.updateProjectionMatrix(); // Call after changing properties
```
**Common FOVs:**
- 45° - Natural perspective
- 50° - Default for many apps
- 75° - Wide, immersive feel
#### OrthographicCamera
```javascript
new THREE.OrthographicCamera(left, right, top, bottom, near, far);
// Useful for 2D/isometric views
```
### Renderer
#### WebGLRenderer
```javascript
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
```javascript
const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setAnimationLoop(animate);
```
## Geometry
### Built-in Geometries
```javascript
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)
```javascript
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([...]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.computeVertexNormals();
geometry.computeBoundingSphere();
```
### Geometry Operations
```javascript
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
```javascript
{
color: 0xff0000,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide, // FrontSide, BackSide, DoubleSide
depthWrite: true,
depthTest: true,
wireframe: false
}
```
### Material Types
```javascript
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
```javascript
new THREE.AmbientLight(0xffffff, 0.5);
// Illuminates all objects equally
```
### DirectionalLight
```javascript
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 7.5);
light.castShadow = true;
// Parallel rays (like sunlight)
```
### PointLight
```javascript
const light = new THREE.PointLight(0xffffff, 1, distance, decay);
light.castShadow = true;
// Radiates in all directions
```
### SpotLight
```javascript
const light = new THREE.SpotLight(0xffffff, 1, distance, angle, penumbra, decay);
light.target.position.set(x, y, z);
light.castShadow = true;
```
### HemisphereLight
```javascript
new THREE.HemisphereLight(skyColor, groundColor, intensity);
// Sky and ground hemisphere lighting
```
### Light Properties
```javascript
light.intensity = 1.0;
light.color.set(0xff0000);
light.power = 1700; // Lumens (for PointLight)
light.visible = false;
```
## Textures
### TextureLoader
```javascript
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
```javascript
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
```javascript
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
```javascript
mesh.translateX(distance);
mesh.translateY(distance);
mesh.translateZ(distance);
mesh.rotateX(angle);
mesh.rotateY(angle);
mesh.rotateZ(angle);
mesh.lookAt(targetVector);
```
### Matrix Operations
```javascript
mesh.updateMatrix();
mesh.updateMatrixWorld(force);
mesh.applyMatrix4(matrix);
```
## Groups
```javascript
const group = new THREE.Group();
group.add(mesh1, mesh2, mesh3);
group.position.set(x, y, z);
scene.add(group);
```
## Animation
### AnimationMixer
```javascript
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
```javascript
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
```javascript
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
scene.add(gltf.scene);
}, onProgress, onError);
```
### DRACOLoader
```javascript
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
gltfLoader.setDRACOLoader(dracoLoader);
```
### OBJLoader, FBXLoader
```javascript
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
```
## Controls
### OrbitControls
```javascript
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 enabled
```
### MapControls, TrackballControls, FlyControls
```javascript
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
```javascript
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
```javascript
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
```javascript
const q = new THREE.Quaternion();
q.setFromEuler(euler);
q.setFromAxisAngle(axis, angle);
mesh.quaternion.copy(q);
```
### Clock
```javascript
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
```javascript
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
```javascript
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
```javascript
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.BackSide`
- `THREE.DoubleSide`
### Blending Modes
- `THREE.NormalBlending` (default)
- `THREE.AdditiveBlending`
- `THREE.SubtractiveBlending`
- `THREE.MultiplyBlending`
### Shadow Map Types
- `THREE.BasicShadowMap`
- `THREE.PCFShadowMap`
- `THREE.PCFSoftShadowMap`
- `THREE.VSMShadowMap`
### Tone Mapping
- `THREE.NoToneMapping`
- `THREE.LinearToneMapping`
- `THREE.ReinhardToneMapping`
- `THREE.CineonToneMapping`
- `THREE.ACESFilmicToneMapping`
### Color Spaces
- `THREE.SRGBColorSpace`
- `THREE.LinearSRGBColorSpace`
## Performance Tips
```javascript
// 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**
references/materials_guide.md
# 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 → MeshStandardMaterial
```
## Material 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
```javascript
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
```javascript
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
```javascript
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
```javascript
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
```javascript
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
```javascript
{
roughness: 0.0,
metalness: 0.0,
transmission: 1.0,
thickness: 1.0,
ior: 1.5
}
```
### Car Paint
```javascript
{
roughness: 0.4,
metalness: 0.8,
clearcoat: 1.0,
clearcoatRoughness: 0.1,
color: 0xff0000
}
```
### Fabric (Velvet, Satin)
```javascript
{
roughness: 0.8,
metalness: 0.0,
sheen: 1.0,
sheenRoughness: 0.5,
sheenColor: new THREE.Color(0xffffff)
}
```
### Soap Bubble
```javascript
{
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
```javascript
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)
```javascript
{
// 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
```javascript
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
```javascript
// 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
```javascript
material.needsUpdate = false; // After initial setup
renderer.shadowMap.autoUpdate = false; // If shadows don't change
```
### 5. Use InstancedMesh with Materials
For identical objects with the same material.
## Custom Shaders (ShaderMaterial)
**Use for:** Complete custom control, special effects, optimizations
```javascript
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:
```javascript
material.dispose();
// Dispose textures too
if (material.map) material.map.dispose();
if (material.normalMap) material.normalMap.dispose();
if (material.roughnessMap) material.roughnessMap.dispose();
// ... etc
```
## Troubleshooting
### 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
```javascript
material.transparent = true;
material.opacity = 0.5;
material.depthWrite = false; // For glass-like materials
```
### Texture Not Showing
- Check texture.colorSpace (SRGB for diffuse)
- Ensure geometry has UV coordinates
- Verify texture loaded successfully
### Z-Fighting / Flickering
- Adjust material.polygonOffset
```javascript
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.
references/optimization_checklist.md
# Three.js Performance Optimization Checklist
## Quick Wins (High Impact, Low Effort)
### ✅ Geometry Optimization
- [ ] **Reuse geometries** across multiple meshes
```javascript
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)
```javascript
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**
```javascript
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**
```javascript
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**:
```javascript
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**:
```javascript
texture.generateMipmaps = true;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
```
- [ ] **Dispose unused textures**:
```javascript
texture.dispose();
```
### ✅ Rendering Optimization
- [ ] **Set pixel ratio appropriately**:
```javascript
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:
```javascript
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**:
```javascript
light.shadow.mapSize.width = 1024; // Lower for mobile
light.shadow.mapSize.height = 1024;
```
- [ ] **Optimize shadow camera frustum**:
```javascript
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:
```javascript
renderer.shadowMap.autoUpdate = false;
renderer.shadowMap.needsUpdate = true; // Only when changed
```
## Medium Effort Optimizations
### 🔧 Culling & Visibility
- [ ] **Enable frustum culling** (enabled by default):
```javascript
mesh.frustumCulled = true;
```
- [ ] **Compute bounding spheres** for custom geometries:
```javascript
geometry.computeBoundingSphere();
```
- [ ] **Hide offscreen objects**:
```javascript
if (distanceToCamera > threshold) {
mesh.visible = false;
}
```
- [ ] **Use layers** for selective rendering:
```javascript
mesh.layers.set(1);
camera.layers.enable(1);
```
### 🔧 Level of Detail (LOD)
- [ ] **Implement LOD** for complex objects:
```javascript
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**:
```javascript
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:
```javascript
const delta = clock.getDelta();
mixer.update(delta);
```
- [ ] **Pause animations** when offscreen:
```javascript
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**:
```javascript
const renderTarget = new THREE.WebGLRenderTarget(
window.innerWidth * 0.5,
window.innerHeight * 0.5
);
```
## Advanced Optimizations
### ⚙️ Memory Management
- [ ] **Dispose all resources** when removing from scene:
```javascript
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:
```javascript
renderTarget.dispose();
```
- [ ] **Monitor memory usage**:
```javascript
console.log(renderer.info.memory);
console.log(renderer.info.render);
```
### ⚙️ Matrix Optimization
- [ ] **Disable auto-update** for static objects:
```javascript
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();
```
- [ ] **Update world matrix** manually when needed:
```javascript
mesh.matrixWorldNeedsUpdate = true;
```
### ⚙️ Custom Shaders
- [ ] **Use low precision** where possible:
```glsl
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**:
```javascript
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**:
```javascript
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:
```javascript
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// Stop animation loop
} else {
// Resume animation loop
}
});
```
## Profiling & Debugging
### 🔍 Performance Monitoring
- [ ] **Use Stats.js**:
```javascript
import Stats from 'three/examples/jsm/libs/stats.module.js';
const stats = new Stats();
document.body.appendChild(stats.dom);
```
- [ ] **Monitor renderer info**:
```javascript
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!)
```
scripts/setup_scene.py
#!/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()
SKILL.md
---
name: threejs-webgl
description: 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.
---
# 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:
```javascript
Scene
├── Camera
├── Lights
│ ├── AmbientLight
│ ├── DirectionalLight
│ └── PointLight
├── Meshes
│ ├── Mesh (Geometry + Material)
│ └── InstancedMesh
└── Groups
```
### Essential 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
```javascript
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)
```javascript
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
```javascript
// 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
```javascript
// 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)
```javascript
// 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)
```javascript
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
```javascript
// 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)
```javascript
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
```javascript
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)
```javascript
// Three.js integrates naturally with React Three Fiber
// Use the react-three-fiber skill for React integration patterns
```
### With Post-Processing
```javascript
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
```javascript
// 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
```javascript
// 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 textures
```
### 4. Level of Detail (LOD)
```javascript
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:
```javascript
mesh.geometry.computeBoundingSphere();
```
### 6. Dispose Resources
```javascript
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
```javascript
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
```javascript
// 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
```javascript
// 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 = true` with `material.polygonOffsetFactor`
### 5. Color Space Issues
```javascript
// 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 properties
- `optimization_checklist.md`: Performance optimization strategies
### scripts/
- `setup_scene.py`: Generate boilerplate Three.js scene setup code
- `texture_optimizer.py`: Batch optimize textures for web (resize, compress)
- `gltf_validator.py`: Validate glTF models before use
### assets/
- `starter_scene/`: Complete HTML/JS boilerplate project
- `shaders/`: Custom GLSL shader examples (vertex, fragment)
- `hdri/`: Environment maps for PBR lighting
- `draco/`: DRACO decoder for compressed models
## Advanced Topics
### Custom Shaders (GLSL)
```javascript
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)
```javascript
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.