SKILL.md
唯讀
名稱
threejs-webgl
描述
Three.js 3D 網頁開發的全面技能。在建立互動式 3D 場景、WebGL/WebGPU 應用程式、產品配置器、3D 視覺化或沉浸式網頁體驗時使用此技能。觸發條件為涉及 Three.js、3D 渲染、場景、相機、網格、材質、燈光、動畫、紋理或 WebGL/WebGPU 渲染的任務。
Three.js WebGL/WebGPU 開發
概述
Three.js 是使用 WebGL 和 WebGPU 在網頁瀏覽器中建立 3D 圖形的業界標準 JavaScript 函式庫。此技能提供建立高效能、互動式 3D 體驗的全面指導,包括場景、相機、渲染器、幾何體、材質、燈光、紋理和動畫。
核心概念
場景圖架構
Three.js 使用階層式場景圖,所有 3D 物件以樹狀結構組織:
Scene
├── Camera
├── Lights
│ ├── AmbientLight
│ ├── DirectionalLight
│ └── PointLight
├── Meshes
│ ├── Mesh (Geometry + Material)
│ └── InstancedMesh
└── Groups
必要元件
每個 Three.js 應用程式都需要這些核心元素:
- Scene:所有 3D 物件的容器
- Camera:定義觀看視角
- Renderer:將場景繪製到畫布(WebGL 或 WebGPU)
- Geometry:定義物件的形狀
- Material:定義表面外觀
- Mesh:結合幾何體和材質
快速開始模式
基本場景設定
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, // 長寬比
0.1, // 近裁剪平面
1000 // 遠裁剪平面
);
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);
// 燈光
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);
// 控制
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 動畫迴圈
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
// 處理視窗大小變化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
WebGPU 設定(現代替代方案)
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);
常見模式
1. 建立帶有材質的網格
// 基本網格
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);
// 帶紋理的網格
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. 燈光策略
// 三點燈光設定
function setupThreePointLight(scene) {
// 主光(主要)
const keyLight = new THREE.DirectionalLight(0xffffff, 3);
keyLight.position.set(5, 10, 7.5);
keyLight.castShadow = true;
scene.add(keyLight);
// 補光(柔化陰影)
const fillLight = new THREE.DirectionalLight(0xffffff, 1);
fillLight.position.set(-5, 5, -5);
scene.add(fillLight);
// 輪廓光(邊緣定義)
const rimLight = new THREE.DirectionalLight(0xffffff, 0.5);
rimLight.position.set(0, 5, -10);
scene.add(rimLight);
// 環境光(基礎照明)
const ambient = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambient);
}
// 物理燈光(真實)
const bulbLight = new THREE.PointLight(0xffee88, 1, 100, 2);
bulbLight.power = 1700; // 流明(相當於 100W 燈泡)
bulbLight.castShadow = true;
scene.add(bulbLight);
// 半球光(天空 + 地面)
const hemiLight = new THREE.HemisphereLight(
0xddeeff, // 天空顏色
0x0f0e0d, // 地面顏色
0.02
);
scene.add(hemiLight);
3. 實例化幾何體(效能)
// 用於高效渲染數千個相似物件
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. 載入 3D 模型(glTF)
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
// 設定載入器
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
// 載入模型
gltfLoader.load('model.glb', (gltf) => {
const model = gltf.scene;
// 啟用陰影
model.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add(model);
// 處理動畫
if (gltf.animations.length > 0) {
const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
// 在動畫迴圈中:
// mixer.update(deltaTime);
}
});
5. 陰影設定
// 在渲染器上啟用陰影
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 或 VSMShadowMap
// 設定燈光陰影
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;
// 物件投射/接收陰影
mesh.castShadow = true;
mesh.receiveShadow = true;
6. 光線投射(互動)
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);
整合模式
與 GSAP 動畫整合
import gsap from 'gsap';
// 動畫相機
gsap.to(camera.position, {
x: 5,
y: 3,
z: 10,
duration: 2,
ease: "power2.inOut",
onUpdate: () => {
camera.lookAt(scene.position);
}
});
// 動畫網格屬性
gsap.to(mesh.rotation, {
y: Math.PI * 2,
duration: 3,
repeat: -1,
ease: "none"
});
與 React 整合(參見 react-three-fiber 技能)
// Three.js 與 React Three Fiber 自然整合
// 使用 react-three-fiber 技能取得 React 整合模式
與後處理整合
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, // 強度
0.4, // 半徑
0.85 // 閾值
);
composer.addPass(bloomPass);
// 在動畫迴圈中:
composer.render();
效能最佳化
1. 幾何體重用
// 不好:為每個網格建立新幾何體
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);
}
// 好:重用幾何體
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. 對重複物件使用 InstancedMesh
對於數百/數千個相同物件,使用 InstancedMesh(參見上述模式)。
3. 紋理最佳化
// 壓縮紋理
texture.generateMipmaps = true;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
// 使用二的冪次方尺寸(512、1024、2048)
// 考慮對多個小紋理使用紋理圖集
4. 細節層級(LOD)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // 0-50 單位
lod.addLevel(mediumDetailMesh, 50); // 50-100 單位
lod.addLevel(lowDetailMesh, 100); // 100+ 單位
scene.add(lod);
5. 視錐剔除
Three.js 自動剔除相機視野外的物件。確保物件有正確的包圍球:
mesh.geometry.computeBoundingSphere();
6. 釋放資源
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();
}
最佳實務
1. 使用動畫時鐘以獲得一致的計時
const clock = new THREE.Clock();
function animate() {
const deltaTime = clock.getDelta();
const elapsedTime = clock.getElapsedTime();
// 使用 deltaTime 進行與幀率無關的動畫
mesh.rotation.y += deltaTime * Math.PI * 0.5; // 每秒 90°
renderer.render(scene, camera);
}
2. 相機設定指南
- FOV:大多數應用程式為 45-75°
- 近平面:盡可能遠(避免 z-fighting)
- 遠平面:盡可能近(精確度)
- 長寬比:始終匹配畫布尺寸
3. 材質選擇
- MeshBasicMaterial:無光照、平面顏色(除錯、UI)
- MeshLambertMaterial:便宜的漫反射光照(行動裝置)
- MeshPhongMaterial:鏡面高光(舊標準)
- MeshStandardMaterial:PBR、真實(推薦)
- MeshPhysicalMaterial:進階 PBR(clearcoat、透射)
4. 座標系統
- Three.js 使用右手座標系統
- +Y 向上,+Z 朝向相機,+X 向右
- 旋轉使用弧度(Math.PI = 180°)
5. 場景組織
// 將相關物件分組
const building = new THREE.Group();
building.add(walls, roof, windows);
scene.add(building);
// 使用有意義的名稱
mesh.name = 'player-character';
const found = scene.getObjectByName('player-character');
常見陷阱
1. 調整大小時未更新長寬比
視窗大小改變時,務必更新相機長寬比和投影矩陣。
2. 在動畫迴圈中建立新物件
// 不好:記憶體洩漏
function animate() {
const geometry = new THREE.BoxGeometry(); // 每一幀都建立!
// ...
}
// 好:在迴圈外建立一次
const geometry = new THREE.BoxGeometry();
function animate() {
// 重用幾何體
}
3. 忘記啟用陰影
記得在渲染器、燈光和物件上啟用陰影。
4. Z-Fighting(閃爍)
- 增加近平面距離
- 減少遠平面距離
- 避免重疊的共面表面
- 使用
material.polygonOffset = true搭配material.polygonOffsetFactor
5. 色彩空間問題
// 始終為紋理設定色彩空間
texture.colorSpace = THREE.SRGBColorSpace;
// 設定渲染器輸出編碼
renderer.outputColorSpace = THREE.SRGBColorSpace;
6. 未釋放資源
不再需要時,務必對幾何體、材質、紋理和渲染器呼叫 .dispose()。
資源
此技能包含隨附資源,以加速 Three.js 開發:
references/
api_reference.md:核心類別(Scene、Camera、Renderer 等)的快速 API 參考materials_guide.md:完整的材質類型和屬性optimization_checklist.md:效能最佳化策略
scripts/
setup_scene.py:產生 Three.js 場景設定的樣板程式碼texture_optimizer.py:批次最佳化網頁紋理(調整大小、壓縮)gltf_validator.py:使用前驗證 glTF 模型
assets/
starter_scene/:完整的 HTML/JS 樣板專案shaders/:自訂 GLSL 著色器範例(頂點、片段)hdri/:用於 PBR 照明的環境貼圖draco/:用於壓縮模型的 DRACO 解碼器
進階主題
自訂著色器(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);
}
`
});
渲染目標(渲染到紋理)
const renderTarget = new THREE.WebGLRenderTarget(512, 512);
// 將場景渲染到紋理
renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null);
// 使用紋理
const material = new THREE.MeshBasicMaterial({
map: renderTarget.texture
});
GPU 計算(GPGPU)
使用 GPUComputationRenderer 進行粒子模擬、布料物理等。
何時使用此技能
在以下情況使用此技能:
- 建立互動式 3D 網頁體驗
- 建立產品配置器或視覺化工具
- 實作 WebGL/WebGPU 渲染
- 處理 3D 模型、場景或動畫
- 最佳化 Three.js 效能
- 將 Three.js 與其他函式庫(GSAP、React 等)整合
- 除錯 Three.js 渲染問題
如需 React 整合,請使用 react-three-fiber 技能。
如需動畫,請搭配 gsap-scrolltrigger 技能。
如需 UI 動畫,請使用 motion-framer 技能。






