threejs
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseThree.js / React Three Fiber Development Skill
Three.js / React Three Fiber 开发技巧
Engine Detection
引擎检测
Look for: with , , , , ,
package.jsonthree@react-three/fiber@react-three/drei.glb.gltf.hdr查找包含以下内容的:、、、、、
package.jsonthree@react-three/fiber@react-three/drei.glb.gltf.hdrProject Structure (Vanilla Three.js)
项目结构(原生Three.js)
src/
main.ts # Entry point, renderer setup
scene/
SceneManager.ts # Scene lifecycle
LevelLoader.ts
entities/
Player.ts
Enemy.ts
systems/
InputSystem.ts
PhysicsSystem.ts
AudioSystem.ts
rendering/
MaterialLibrary.ts
PostProcessing.ts
ShaderChunks/
utils/
ObjectPool.ts
MathUtils.ts
types/
GameTypes.ts
public/
models/
textures/
audio/src/
main.ts # 入口文件,渲染器设置
scene/
SceneManager.ts # 场景生命周期
LevelLoader.ts
entities/
Player.ts
Enemy.ts
systems/
InputSystem.ts
PhysicsSystem.ts
AudioSystem.ts
rendering/
MaterialLibrary.ts
PostProcessing.ts
ShaderChunks/
utils/
ObjectPool.ts
MathUtils.ts
types/
GameTypes.ts
public/
models/
textures/
audio/Project Structure (React Three Fiber)
项目结构(React Three Fiber)
src/
App.tsx
components/
canvas/
GameCanvas.tsx # Canvas + providers
Scene.tsx # Main scene composition
entities/
Player.tsx
Enemy.tsx
environment/
Terrain.tsx
Skybox.tsx
Lighting.tsx
ui/
HUD.tsx
MainMenu.tsx
effects/
PostProcessing.tsx
Particles.tsx
hooks/
useGameLoop.ts
useInput.ts
usePhysics.ts
stores/
gameStore.ts # Zustand store
types/
game.ts
utils/
pool.ts
public/
models/
textures/src/
App.tsx
components/
canvas/
GameCanvas.tsx # Canvas + 提供者
Scene.tsx # 主场景组合
entities/
Player.tsx
Enemy.tsx
environment/
Terrain.tsx
Skybox.tsx
Lighting.tsx
ui/
HUD.tsx
MainMenu.tsx
effects/
PostProcessing.tsx
Particles.tsx
hooks/
useGameLoop.ts
useInput.ts
usePhysics.ts
stores/
gameStore.ts # Zustand状态库
types/
game.ts
utils/
pool.ts
public/
models/
textures/Scene Graph & Disposal
场景图与资源释放
Proper resource management is critical. Three.js does NOT garbage collect GPU resources:
typescript
// Creating resources
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// MUST dispose when removing
scene.remove(mesh);
geometry.dispose();
material.dispose();
if (material.map) material.map.dispose();
// Dispose ALL textures: map, normalMap, roughnessMap, etc.
// Helper for deep disposal
function disposeObject(obj: THREE.Object3D): void {
obj.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.geometry.dispose();
if (Array.isArray(child.material)) {
child.material.forEach(disposeMaterial);
} else {
disposeMaterial(child.material);
}
}
});
obj.removeFromParent();
}
function disposeMaterial(mat: THREE.Material): void {
for (const value of Object.values(mat)) {
if (value instanceof THREE.Texture) {
value.dispose();
}
}
mat.dispose();
}合理的资源管理至关重要。Three.js不会自动回收GPU资源:
typescript
// 创建资源
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// 移除时必须释放资源
scene.remove(mesh);
geometry.dispose();
material.dispose();
if (material.map) material.map.dispose();
// 释放所有纹理:map、normalMap、roughnessMap等
// 深度释放的辅助函数
function disposeObject(obj: THREE.Object3D): void {
obj.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.geometry.dispose();
if (Array.isArray(child.material)) {
child.material.forEach(disposeMaterial);
} else {
disposeMaterial(child.material);
}
}
});
obj.removeFromParent();
}
function disposeMaterial(mat: THREE.Material): void {
for (const value of Object.values(mat)) {
if (value instanceof THREE.Texture) {
value.dispose();
}
}
mat.dispose();
}Game Loop (Vanilla Three.js)
游戏循环(原生Three.js)
typescript
class Game {
private renderer: THREE.WebGLRenderer;
private scene: THREE.Scene;
private camera: THREE.PerspectiveCamera;
private clock = new THREE.Clock();
private systems: GameSystem[] = [];
constructor(canvas: HTMLCanvasElement) {
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
window.addEventListener('resize', this.onResize);
this.animate();
}
private animate = (): void => {
requestAnimationFrame(this.animate);
const delta = this.clock.getDelta();
const elapsed = this.clock.getElapsedTime();
for (const system of this.systems) {
system.update(delta, elapsed);
}
this.renderer.render(this.scene, this.camera);
};
private onResize = (): void => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
};
dispose(): void {
window.removeEventListener('resize', this.onResize);
this.renderer.dispose();
// Dispose all scene objects
disposeObject(this.scene);
}
}typescript
class Game {
private renderer: THREE.WebGLRenderer;
private scene: THREE.Scene;
private camera: THREE.PerspectiveCamera;
private clock = new THREE.Clock();
private systems: GameSystem[] = [];
constructor(canvas: HTMLCanvasElement) {
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
window.addEventListener('resize', this.onResize);
this.animate();
}
private animate = (): void => {
requestAnimationFrame(this.animate);
const delta = this.clock.getDelta();
const elapsed = this.clock.getElapsedTime();
for (const system of this.systems) {
system.update(delta, elapsed);
}
this.renderer.render(this.scene, this.camera);
};
private onResize = (): void => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
};
dispose(): void {
window.removeEventListener('resize', this.onResize);
this.renderer.dispose();
// 释放所有场景对象
disposeObject(this.scene);
}
}React Three Fiber Patterns
React Three Fiber 模式
tsx
// GameCanvas.tsx - Entry point
import { Canvas } from '@react-three/fiber';
import { Physics } from '@react-three/rapier';
export function GameCanvas() {
return (
<Canvas
camera={{ position: [0, 5, 10], fov: 60 }}
shadows
gl={{ antialias: true }}
>
<Physics>
<Scene />
</Physics>
</Canvas>
);
}tsx
// Player.tsx - Entity component
import { useFrame } from '@react-three/fiber';
import { useRef } from 'react';
import { useInput } from '../hooks/useInput';
export function Player() {
const meshRef = useRef<THREE.Mesh>(null!);
const input = useInput();
useFrame((state, delta) => {
// Runs every frame inside the render loop
meshRef.current.position.x += input.horizontal * 5 * delta;
meshRef.current.position.z += input.vertical * 5 * delta;
});
return (
<mesh ref={meshRef} castShadow>
<boxGeometry args={[1, 2, 1]} />
<meshStandardMaterial color="blue" />
</mesh>
);
}tsx
// GameCanvas.tsx - 入口文件
import { Canvas } from '@react-three/fiber';
import { Physics } from '@react-three/rapier';
export function GameCanvas() {
return (
<Canvas
camera={{ position: [0, 5, 10], fov: 60 }}
shadows
gl={{ antialias: true }}
>
<Physics>
<Scene />
</Physics>
</Canvas>
);
}tsx
// Player.tsx - 实体组件
import { useFrame } from '@react-three/fiber';
import { useRef } from 'react';
import { useInput } from '../hooks/useInput';
export function Player() {
const meshRef = useRef<THREE.Mesh>(null!);
const input = useInput();
useFrame((state, delta) => {
// 在渲染循环的每一帧执行
meshRef.current.position.x += input.horizontal * 5 * delta;
meshRef.current.position.z += input.vertical * 5 * delta;
});
return (
<mesh ref={meshRef} castShadow>
<boxGeometry args={[1, 2, 1]} />
<meshStandardMaterial color="blue" />
</mesh>
);
}Zustand for Game State
使用Zustand管理游戏状态
typescript
// gameStore.ts
import { create } from 'zustand';
interface GameState {
health: number;
score: number;
isPaused: boolean;
takeDamage: (amount: number) => void;
addScore: (points: number) => void;
togglePause: () => void;
}
export const useGameStore = create<GameState>((set) => ({
health: 100,
score: 0,
isPaused: false,
takeDamage: (amount) =>
set((state) => ({ health: Math.max(0, state.health - amount) })),
addScore: (points) =>
set((state) => ({ score: state.score + points })),
togglePause: () =>
set((state) => ({ isPaused: !state.isPaused })),
}));typescript
// gameStore.ts
import { create } from 'zustand';
interface GameState {
health: number;
score: number;
isPaused: boolean;
takeDamage: (amount: number) => void;
addScore: (points: number) => void;
togglePause: () => void;
}
export const useGameStore = create<GameState>((set) => ({
health: 100,
score: 0,
isPaused: false,
takeDamage: (amount) =>
set((state) => ({ health: Math.max(0, state.health - amount) })),
addScore: (points) =>
set((state) => ({ score: state.score + points })),
togglePause: () =>
set((state) => ({ isPaused: !state.isPaused })),
}));Object Pooling
对象池
typescript
class ObjectPool<T extends THREE.Object3D> {
private available: T[] = [];
private active = new Set<T>();
constructor(
private factory: () => T,
initialSize: number,
) {
for (let i = 0; i < initialSize; i++) {
this.available.push(factory());
}
}
acquire(): T {
const obj = this.available.pop() ?? this.factory();
obj.visible = true;
this.active.add(obj);
return obj;
}
release(obj: T): void {
obj.visible = false;
this.active.delete(obj);
this.available.push(obj);
}
get activeCount(): number {
return this.active.size;
}
}typescript
class ObjectPool<T extends THREE.Object3D> {
private available: T[] = [];
private active = new Set<T>();
constructor(
private factory: () => T,
initialSize: number,
) {
for (let i = 0; i < initialSize; i++) {
this.available.push(factory());
}
}
acquire(): T {
const obj = this.available.pop() ?? this.factory();
obj.visible = true;
this.active.add(obj);
return obj;
}
release(obj: T): void {
obj.visible = false;
this.active.delete(obj);
this.available.push(obj);
}
get activeCount(): number {
return this.active.size;
}
}Performance Optimization
性能优化
typescript
// Use instancing for many identical objects
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
matrix.setPosition(positions[i]);
instancedMesh.setMatrixAt(i, matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true;
// LOD (Level of Detail)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // 0-50 units
lod.addLevel(mediumDetailMesh, 50); // 50-150 units
lod.addLevel(lowDetailMesh, 150); // 150+ units
// Texture optimization
const loader = new THREE.TextureLoader();
const texture = loader.load('texture.jpg');
texture.minFilter = THREE.LinearMipMapLinearFilter;
texture.generateMipmaps = true;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();typescript
// 对大量相同对象使用实例化
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
matrix.setPosition(positions[i]);
instancedMesh.setMatrixAt(i, matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true;
// LOD(细节层次)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // 0-50单位距离
lod.addLevel(mediumDetailMesh, 50); // 50-150单位距离
lod.addLevel(lowDetailMesh, 150); // 150单位距离以上
// 纹理优化
const loader = new THREE.TextureLoader();
const texture = loader.load('texture.jpg');
texture.minFilter = THREE.LinearMipMapLinearFilter;
texture.generateMipmaps = true;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();Key Rules
核心规则
- Always dispose GPU resources - geometry, material, texture, render targets
- Use delta time in useFrame/animate - Frame-rate independent updates
- Limit pixel ratio - prevents GPU overload
Math.min(window.devicePixelRatio, 2) - Use instancing for repeated geometry - Massively reduces draw calls
- Remove event listeners on cleanup - Prevent memory leaks
- Use refs for Three.js objects in R3F - Not React state
- Keep React state out of the render loop - Use Zustand or refs for per-frame data
- Use drei helpers - OrbitControls, Environment, useGLTF, etc.
- Compress textures - KTX2/Basis Universal for GPU compression
- Profile with Stats.js and renderer.info - Monitor draw calls, triangles, textures
- 始终释放GPU资源 - 几何体、材质、纹理、渲染目标
- 在useFrame/animate中使用delta时间 - 实现帧率无关的更新
- 限制像素比 - 使用避免GPU过载
Math.min(window.devicePixelRatio, 2) - 对重复几何体使用实例化 - 大幅减少绘制调用
- 在清理时移除事件监听器 - 防止内存泄漏
- 在R3F中使用refs引用Three.js对象 - 不要用React状态
- 不要在渲染循环中使用React状态 - 对每帧数据使用Zustand或refs
- 使用drei辅助工具 - OrbitControls、Environment、useGLTF等
- 压缩纹理 - 使用KTX2/Basis Universal进行GPU压缩
- 使用Stats.js和renderer.info进行性能分析 - 监控绘制调用、三角形数量、纹理数量
Common Anti-Patterns
常见反模式
- Creating new materials/geometries every frame
- Using inside
setState- causes React re-rendersuseFrame - Not disposing loaded GLTF models when removed
- Using parent without disposing children
THREE.Group - Large textures without power-of-2 dimensions or compression
- 每帧创建新的材质/几何体
- 在内使用
useFrame- 会导致React重新渲染setState - 移除已加载的GLTF模型时不释放资源
- 使用作为父节点但不释放子节点
THREE.Group - 使用未采用2的幂尺寸或未压缩的大纹理