threejs-graphics-optimizer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTHREE.js Graphics Optimizer
THREE.js 图形优化器
Version: 1.0
Focus: Performance optimization for THREE.js and graphics applications
Purpose: Build smooth 60fps graphics experiences across all devices including mobile
Focus: Performance optimization for THREE.js and graphics applications
Purpose: Build smooth 60fps graphics experiences across all devices including mobile
Version:1.0
Focus:THREE.js与图形应用的性能优化
Purpose:在包括移动端在内的所有设备上构建流畅的60fps图形体验
Focus:THREE.js与图形应用的性能优化
Purpose:在包括移动端在内的所有设备上构建流畅的60fps图形体验
Philosophy: Performance-First Graphics
理念:性能优先的图形开发
The 16ms Budget
16ms 预算
Target: 60 FPS = 16.67ms per frame
Frame budget breakdown:
- JavaScript logic: ~5-8ms
- Rendering (GPU): ~8-10ms
- Browser overhead: ~2ms
If you exceed 16ms: Frames drop, stuttering occurs.
目标:60 FPS = 每帧16.67ms
帧预算拆分:
- JavaScript逻辑:约5-8ms
- 渲染(GPU):约8-10ms
- 浏览器开销:约2ms
如果超过16ms:丢帧、卡顿现象出现。
Mobile vs Desktop Reality
移动端与桌面端的实际差异
Desktop: Powerful GPU, lots of VRAM, high pixel ratios
Mobile: Constrained GPU, limited VRAM, battery concerns, thermal throttling
Mobile: Constrained GPU, limited VRAM, battery concerns, thermal throttling
Design philosophy: Optimize for mobile, scale up for desktop (not vice versa).
桌面端:高性能GPU、充足显存、高像素比
移动端:GPU性能受限、显存有限、电池续航顾虑、发热降频
移动端:GPU性能受限、显存有限、电池续航顾虑、发热降频
设计理念:优先针对移动端优化,再向上适配桌面端(而非反过来)。
Part 1: Core Optimization Principles
第一部分:核心优化原则
1. Minimize Draw Calls
1. 减少绘制调用
The Problem: Each object = one draw call. 1000 objects = 1000 calls = slow.
Solution: Geometry Merging
javascript
// ❌ Bad: 100 draw calls for 100 cubes
for (let i = 0; i < 100; i++) {
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const cube = new THREE.Mesh(geometry, material)
cube.position.set(i * 2, 0, 0)
scene.add(cube)
}
// ✅ Good: 1 draw call via InstancedMesh
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const instancedMesh = new THREE.InstancedMesh(geometry, material, 100)
for (let i = 0; i < 100; i++) {
const matrix = new THREE.Matrix4()
matrix.setPosition(i * 2, 0, 0)
instancedMesh.setMatrixAt(i, matrix)
}
instancedMesh.instanceMatrix.needsUpdate = true
scene.add(instancedMesh)When to use:
- Many similar objects (particles, trees, enemies)
- Static or semi-static positioning
- Shared material/geometry
问题:每个对象对应一次绘制调用。1000个对象=1000次调用=性能缓慢。
解决方案:几何体合并
javascript
// ❌ 不佳:100个立方体对应100次绘制调用
for (let i = 0; i < 100; i++) {
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const cube = new THREE.Mesh(geometry, material)
cube.position.set(i * 2, 0, 0)
scene.add(cube)
}
// ✅ 推荐:通过InstancedMesh实现1次绘制调用
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const instancedMesh = new THREE.InstancedMesh(geometry, material, 100)
for (let i = 0; i < 100; i++) {
const matrix = new THREE.Matrix4()
matrix.setPosition(i * 2, 0, 0)
instancedMesh.setMatrixAt(i, matrix)
}
instancedMesh.instanceMatrix.needsUpdate = true
scene.add(instancedMesh)适用场景:
- 大量相似对象(粒子、树木、敌人)
- 静态或半静态定位
- 共享材质/几何体
2. Level of Detail (LOD)
2. 细节层次(LOD)
Render simpler geometry when objects are far away:
javascript
const lod = new THREE.LOD()
// High detail (near camera)
const highDetailGeo = new THREE.IcosahedronGeometry(1, 3) // Many faces
const highDetailMesh = new THREE.Mesh(
highDetailGeo,
new THREE.MeshStandardMaterial({ color: 0x00d9ff })
)
lod.addLevel(highDetailMesh, 0) // Distance 0-10
// Medium detail
const medDetailGeo = new THREE.IcosahedronGeometry(1, 1)
const medDetailMesh = new THREE.Mesh(
medDetailGeo,
new THREE.MeshBasicMaterial({ color: 0x00d9ff })
)
lod.addLevel(medDetailMesh, 10) // Distance 10-50
// Low detail (far from camera)
const lowDetailGeo = new THREE.IcosahedronGeometry(1, 0)
const lowDetailMesh = new THREE.Mesh(
lowDetailGeo,
new THREE.MeshBasicMaterial({ color: 0x00d9ff })
)
lod.addLevel(lowDetailMesh, 50) // Distance 50+
scene.add(lod)
// Update LOD in render loop
function animate() {
lod.update(camera)
renderer.render(scene, camera)
}当对象距离相机较远时,渲染更简单的几何体:
javascript
const lod = new THREE.LOD()
// 高细节(靠近相机)
const highDetailGeo = new THREE.IcosahedronGeometry(1, 3) // 大量面
const highDetailMesh = new THREE.Mesh(
highDetailGeo,
new THREE.MeshStandardMaterial({ color: 0x00d9ff })
)
lod.addLevel(highDetailMesh, 0) // 距离0-10
// 中等细节
const medDetailGeo = new THREE.IcosahedronGeometry(1, 1)
const medDetailMesh = new THREE.Mesh(
medDetailGeo,
new THREE.MeshBasicMaterial({ color: 0x00d9ff })
)
lod.addLevel(medDetailMesh, 10) // 距离10-50
// 低细节(远离相机)
const lowDetailGeo = new THREE.IcosahedronGeometry(1, 0)
const lowDetailMesh = new THREE.Mesh(
lowDetailGeo,
new THREE.MeshBasicMaterial({ color: 0x00d9ff })
)
lod.addLevel(lowDetailMesh, 50) // 距离50+
scene.add(lod)
// 在渲染循环中更新LOD
function animate() {
lod.update(camera)
renderer.render(scene, camera)
}3. Frustum Culling (Automatic)
3. 视锥体剔除(自动)
THREE.js automatically skips objects outside camera view. Help it:
javascript
// ❌ Bad: Unnecessarily large bounding volumes
mesh.geometry.computeBoundingSphere()
mesh.geometry.boundingSphere.radius = 1000 // Too large!
// ✅ Good: Accurate bounding volumes
mesh.geometry.computeBoundingSphere() // Uses actual geometry size
mesh.geometry.computeBoundingBox()THREE.js会自动跳过相机视野外的对象。请配合以下操作:
javascript
// ❌ 不佳:不必要的大包围盒
mesh.geometry.computeBoundingSphere()
mesh.geometry.boundingSphere.radius = 1000 // 过大!
// ✅ 推荐:精确的包围盒
mesh.geometry.computeBoundingSphere() // 使用几何体实际尺寸
mesh.geometry.computeBoundingBox()4. Texture Optimization
4. 纹理优化
Texture size matters:
- 4K texture (4096x4096): 64MB VRAM (uncompressed)
- 2K texture (2048x2048): 16MB VRAM
- 1K texture (1024x1024): 4MB VRAM
Rules:
- Use smallest textures that look good
- Power-of-two dimensions (512, 1024, 2048)
- Compress textures (use basis/KTX2 format)
javascript
const textureLoader = new THREE.TextureLoader()
// ❌ Bad: Loading 4K texture for small object
const texture = textureLoader.load('texture-4k.jpg')
// ✅ Good: Appropriate size for use case
const texture = textureLoader.load('texture-1k.jpg')
// ✅ Better: Set appropriate filtering
texture.minFilter = THREE.LinearFilter // No mipmaps (saves VRAM)
texture.anisotropy = renderer.capabilities.getMaxAnisotropy()
// ✅ Best: Dispose when done
function cleanup() {
texture.dispose()
}纹理尺寸至关重要:
- 4K纹理(4096x4096):64MB显存(未压缩)
- 2K纹理(2048x2048):16MB显存
- 1K纹理(1024x1024):4MB显存
规则:
- 使用满足视觉需求的最小纹理
- 采用2的幂次尺寸(512、1024、2048)
- 压缩纹理(使用basis/KTX2格式)
javascript
const textureLoader = new THREE.TextureLoader()
// ❌ 不佳:为小对象加载4K纹理
const texture = textureLoader.load('texture-4k.jpg')
// ✅ 推荐:根据场景使用合适尺寸
const texture = textureLoader.load('texture-1k.jpg')
// ✅ 更佳:设置合适的过滤方式
texture.minFilter = THREE.LinearFilter // 不使用mipmap(节省显存)
texture.anisotropy = renderer.capabilities.getMaxAnisotropy()
// ✅ 最优:使用完毕后释放
function cleanup() {
texture.dispose()
}Part 2: Mobile-Specific Optimization
第二部分:移动端专属优化
Mobile Detection & Adaptation
移动端检测与适配
javascript
/**
* Detect mobile device.
* @returns {boolean}
*/
export function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile/i.test(navigator.userAgent)
|| window.innerWidth < 768
}
/**
* Get optimal pixel ratio for device.
* @returns {number}
*/
export function getOptimalPixelRatio() {
const mobile = isMobile()
const deviceRatio = window.devicePixelRatio
// Cap pixel ratio on mobile to save performance
return mobile
? Math.min(deviceRatio, 1.5) // Max 1.5x on mobile
: Math.min(deviceRatio, 2) // Max 2x on desktop
}
// Apply to renderer
renderer.setPixelRatio(getOptimalPixelRatio())javascript
/**
* 检测移动设备。
* @returns {boolean}
*/
export function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile/i.test(navigator.userAgent)
|| window.innerWidth < 768
}
/**
* 获取设备的最佳像素比。
* @returns {number}
*/
export function getOptimalPixelRatio() {
const mobile = isMobile()
const deviceRatio = window.devicePixelRatio
// 在移动端限制像素比以节省性能
return mobile
? Math.min(deviceRatio, 1.5) // 移动端最大1.5x
: Math.min(deviceRatio, 2) // 桌面端最大2x
}
// 应用到渲染器
renderer.setPixelRatio(getOptimalPixelRatio())Mobile Performance Settings
移动端性能设置
javascript
/**
* Configure renderer for mobile performance.
*/
function setupMobileOptimizations(renderer, scene, camera) {
const mobile = isMobile()
if (mobile) {
// Disable expensive features
renderer.shadowMap.enabled = false
renderer.antialias = false
// Lower pixel ratio
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5))
// Simpler tone mapping
renderer.toneMapping = THREE.NoToneMapping
// Remove fog (expensive pixel shader)
scene.fog = null
// Reduce lights (expensive)
// Keep only 1-2 lights max on mobile
console.log('[Mobile] Performance optimizations applied')
} else {
// Desktop: enable high-quality features
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
renderer.antialias = true
renderer.toneMapping = THREE.ACESFilmicToneMapping
console.log('[Desktop] High-quality features enabled')
}
}javascript
/**
* 为移动端配置渲染器以优化性能。
*/
function setupMobileOptimizations(renderer, scene, camera) {
const mobile = isMobile()
if (mobile) {
// 禁用高开销特性
renderer.shadowMap.enabled = false
renderer.antialias = false
// 降低像素比
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5))
// 使用更简单的色调映射
renderer.toneMapping = THREE.NoToneMapping
// 移除雾化(高开销像素着色器)
scene.fog = null
// 减少灯光数量(高开销)
// 移动端最多保留1-2盏灯
console.log('[移动端] 已应用性能优化')
} else {
// 桌面端:启用高质量特性
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
renderer.antialias = true
renderer.toneMapping = THREE.ACESFilmicToneMapping
console.log('[桌面端] 已启用高质量特性')
}
}Fallback Pattern
降级方案
javascript
/**
* Create geometry with fallback for low-end devices.
*/
export function createOptimizedGeometry(options = {}) {
const { size = 1, mobile = false } = options
if (mobile) {
// Simple geometry for mobile
return new THREE.SphereGeometry(size, 8, 8) // Low poly
} else {
// Detailed geometry for desktop
return new THREE.IcosahedronGeometry(size, 2) // High poly
}
}
// Usage
const mobile = isMobile()
const geometry = createOptimizedGeometry({ size: 1, mobile })
const material = new THREE.MeshBasicMaterial({ color: 0x00d9ff })
const mesh = new THREE.Mesh(geometry, material)javascript
/**
* 为低端设备创建带降级选项的几何体。
*/
export function createOptimizedGeometry(options = {}) {
const { size = 1, mobile = false } = options
if (mobile) {
// 移动端使用简单几何体
return new THREE.SphereGeometry(size, 8, 8) // 低多边形
} else {
// 桌面端使用精细几何体
return new THREE.IcosahedronGeometry(size, 2) // 高多边形
}
}
// 使用示例
const mobile = isMobile()
const geometry = createOptimizedGeometry({ size: 1, mobile })
const material = new THREE.MeshBasicMaterial({ color: 0x00d9ff })
const mesh = new THREE.Mesh(geometry, material)Part 3: Render Loop Optimization
第三部分:渲染循环优化
Efficient Animation Loop
高效动画循环
javascript
class SceneManager {
constructor() {
this.clock = new THREE.Clock()
this.animationId = null
this.lastFrameTime = 0
this.fps = 60
this.frameInterval = 1000 / this.fps
}
/**
* Main render loop with delta time.
*/
animate() {
this.animationId = requestAnimationFrame(() => this.animate())
const now = performance.now()
const delta = now - this.lastFrameTime
// Throttle to target FPS if needed
if (delta < this.frameInterval) return
this.lastFrameTime = now - (delta % this.frameInterval)
// Update logic with delta
const deltaSeconds = this.clock.getDelta()
this.update(deltaSeconds)
// Render
this.renderer.render(this.scene, this.camera)
}
/**
* Update scene objects.
* @param {number} delta - Time since last frame (seconds)
*/
update(delta) {
// Update animations, physics, etc.
this.animatedObjects.forEach(obj => {
if (obj.update) obj.update(delta)
})
}
/**
* Cleanup and stop animation.
*/
dispose() {
if (this.animationId) {
cancelAnimationFrame(this.animationId)
}
}
}javascript
class SceneManager {
constructor() {
this.clock = new THREE.Clock()
this.animationId = null
this.lastFrameTime = 0
this.fps = 60
this.frameInterval = 1000 / this.fps
}
/**
* 带增量时间的主渲染循环。
*/
animate() {
this.animationId = requestAnimationFrame(() => this.animate())
const now = performance.now()
const delta = now - this.lastFrameTime
// 必要时限制帧率
if (delta < this.frameInterval) return
this.lastFrameTime = now - (delta % this.frameInterval)
// 使用增量时间更新逻辑
const deltaSeconds = this.clock.getDelta()
this.update(deltaSeconds)
// 渲染
this.renderer.render(this.scene, this.camera)
}
/**
* 更新场景对象。
* @param {number} delta - 距上一帧的时间(秒)
*/
update(delta) {
// 更新动画、物理效果等
this.animatedObjects.forEach(obj => {
if (obj.update) obj.update(delta)
})
}
/**
* 清理并停止动画。
*/
dispose() {
if (this.animationId) {
cancelAnimationFrame(this.animationId)
}
}
}Conditional Rendering
条件渲染
javascript
/**
* Only render when something changed (for static scenes).
*/
class ConditionalRenderer {
constructor(renderer, scene, camera) {
this.renderer = renderer
this.scene = scene
this.camera = camera
this.needsRender = true
}
/**
* Mark scene as needing re-render.
*/
invalidate() {
this.needsRender = true
}
/**
* Render only if needed.
*/
render() {
if (this.needsRender) {
this.renderer.render(this.scene, this.camera)
this.needsRender = false
}
}
/**
* Use with controls.
*/
connectControls(controls) {
controls.addEventListener('change', () => this.invalidate())
}
}
// Usage
const conditionalRenderer = new ConditionalRenderer(renderer, scene, camera)
conditionalRenderer.connectControls(controls)
function animate() {
requestAnimationFrame(animate)
controls.update()
conditionalRenderer.render() // Only renders if camera moved
}javascript
/**
* 仅在场景变化时渲染(适用于静态场景)。
*/
class ConditionalRenderer {
constructor(renderer, scene, camera) {
this.renderer = renderer
this.scene = scene
this.camera = camera
this.needsRender = true
}
/**
* 标记场景需要重新渲染。
*/
invalidate() {
this.needsRender = true
}
/**
* 仅在需要时渲染。
*/
render() {
if (this.needsRender) {
this.renderer.render(this.scene, this.camera)
this.needsRender = false
}
}
/**
* 与控制器关联。
*/
connectControls(controls) {
controls.addEventListener('change', () => this.invalidate())
}
}
// 使用示例
const conditionalRenderer = new ConditionalRenderer(renderer, scene, camera)
conditionalRenderer.connectControls(controls)
function animate() {
requestAnimationFrame(animate)
controls.update()
conditionalRenderer.render() // 仅在相机移动时渲染
}Part 4: Memory Management
第四部分:内存管理
Dispose Pattern
释放模式
javascript
/**
* Properly dispose THREE.js resources.
*/
export function disposeObject(object) {
if (!object) return
// Traverse and dispose children
object.traverse((child) => {
// Dispose geometry
if (child.geometry) {
child.geometry.dispose()
}
// Dispose materials
if (child.material) {
if (Array.isArray(child.material)) {
child.material.forEach(material => disposeMaterial(material))
} else {
disposeMaterial(child.material)
}
}
// Dispose textures
if (child.texture) {
child.texture.dispose()
}
})
// Remove from parent
if (object.parent) {
object.parent.remove(object)
}
}
/**
* Dispose material and its textures.
*/
function disposeMaterial(material) {
material.dispose()
// Dispose textures
Object.keys(material).forEach(key => {
const value = material[key]
if (value && typeof value === 'object' && 'minFilter' in value) {
value.dispose() // It's a texture
}
})
}javascript
/**
* 正确释放THREE.js资源。
*/
export function disposeObject(object) {
if (!object) return
// 遍历并释放子对象
object.traverse((child) => {
// 释放几何体
if (child.geometry) {
child.geometry.dispose()
}
// 释放材质
if (child.material) {
if (Array.isArray(child.material)) {
child.material.forEach(material => disposeMaterial(material))
} else {
disposeMaterial(child.material)
}
}
// 释放纹理
if (child.texture) {
child.texture.dispose()
}
})
// 从父节点移除
if (object.parent) {
object.parent.remove(object)
}
}
/**
* 释放材质及其纹理。
*/
function disposeMaterial(material) {
material.dispose()
// 释放纹理
Object.keys(material).forEach(key => {
const value = material[key]
if (value && typeof value === 'object' && 'minFilter' in value) {
value.dispose() // 这是纹理
}
})
}Memory Leak Prevention
内存泄漏预防
javascript
class SafeSceneManager {
constructor() {
this.scene = new THREE.Scene()
this.renderer = new THREE.WebGLRenderer()
this.objects = new Set()
}
/**
* Add object and track it.
*/
add(object) {
this.scene.add(object)
this.objects.add(object)
}
/**
* Remove and dispose object.
*/
remove(object) {
this.scene.remove(object)
this.objects.delete(object)
disposeObject(object)
}
/**
* Cleanup all resources.
*/
dispose() {
// Dispose all tracked objects
this.objects.forEach(obj => disposeObject(obj))
this.objects.clear()
// Dispose renderer
this.renderer.dispose()
// Clear scene
this.scene.clear()
}
}javascript
class SafeSceneManager {
constructor() {
this.scene = new THREE.Scene()
this.renderer = new THREE.WebGLRenderer()
this.objects = new Set()
}
/**
* 添加对象并跟踪。
*/
add(object) {
this.scene.add(object)
this.objects.add(object)
}
/**
* 移除并释放对象。
*/
remove(object) {
this.scene.remove(object)
this.objects.delete(object)
disposeObject(object)
}
/**
* 清理所有资源。
*/
dispose() {
// 释放所有跟踪的对象
this.objects.forEach(obj => disposeObject(obj))
this.objects.clear()
// 释放渲染器
this.renderer.dispose()
// 清空场景
this.scene.clear()
}
}Part 5: Material Optimization
第五部分:材质优化
Material Sharing
材质共享
javascript
// ❌ Bad: New material for each object
for (let i = 0; i < 100; i++) {
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
}
// ✅ Good: Share single material
const sharedMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 })
for (let i = 0; i < 100; i++) {
const mesh = new THREE.Mesh(geometry, sharedMaterial)
scene.add(mesh)
}javascript
// ❌ 不佳:为每个对象创建新材质
for (let i = 0; i < 100; i++) {
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
}
// ✅ 推荐:共享单个材质
const sharedMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 })
for (let i = 0; i < 100; i++) {
const mesh = new THREE.Mesh(geometry, sharedMaterial)
scene.add(mesh)
}Cheaper Material Types
轻量化材质类型
Performance ranking (fastest to slowest):
- MeshBasicMaterial - No lighting, flat shading
- MeshLambertMaterial - Simple diffuse lighting
- MeshPhongMaterial - Specular highlights
- MeshStandardMaterial - PBR (expensive)
- MeshPhysicalMaterial - Advanced PBR (very expensive)
javascript
// Mobile: Use cheaper materials
const material = isMobile()
? new THREE.MeshBasicMaterial({ color: 0x00d9ff })
: new THREE.MeshStandardMaterial({
color: 0x00d9ff,
roughness: 0.5,
metalness: 0.1
})性能排名(从快到慢):
- MeshBasicMaterial - 无光照、平面着色
- MeshLambertMaterial - 简单漫反射光照
- MeshPhongMaterial - 镜面高光
- MeshStandardMaterial - PBR(开销大)
- MeshPhysicalMaterial - 高级PBR(开销极大)
javascript
// 移动端:使用轻量化材质
const material = isMobile()
? new THREE.MeshBasicMaterial({ color: 0x00d9ff })
: new THREE.MeshStandardMaterial({
color: 0x00d9ff,
roughness: 0.5,
metalness: 0.1
})Blending Modes
混合模式
javascript
// Additive blending for glows (cheaper than transparent)
material.blending = THREE.AdditiveBlending
material.transparent = true
material.depthWrite = false // Don't write to depth bufferjavascript
// 发光效果使用加法混合(比透明更高效)
material.blending = THREE.AdditiveBlending
material.transparent = true
material.depthWrite = false // 不写入深度缓冲Part 6: Post-Processing Optimization
第六部分:后处理优化
Selective 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'
function setupPostProcessing(renderer, scene, camera, mobile) {
const composer = new EffectComposer(renderer)
// Always add render pass
composer.addPass(new RenderPass(scene, camera))
// Bloom only on desktop
if (!mobile) {
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // strength
0.4, // radius
0.85 // threshold
)
composer.addPass(bloomPass)
}
return composer
}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'
function setupPostProcessing(renderer, scene, camera, mobile) {
const composer = new EffectComposer(renderer)
// 始终添加渲染通道
composer.addPass(new RenderPass(scene, camera))
// 仅在桌面端启用 bloom
if (!mobile) {
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // 强度
0.4, // 半径
0.85 // 阈值
)
composer.addPass(bloomPass)
}
return composer
}Part 7: General Graphics Best Practices
第七部分:通用图形开发最佳实践
1. Object Pooling
1. 对象池化
javascript
/**
* Object pool to reuse objects instead of creating/destroying.
*/
class ObjectPool {
constructor(createFn, resetFn) {
this.pool = []
this.createFn = createFn
this.resetFn = resetFn
}
/**
* Get object from pool or create new one.
*/
acquire() {
if (this.pool.length > 0) {
return this.pool.pop()
}
return this.createFn()
}
/**
* Return object to pool.
*/
release(obj) {
this.resetFn(obj)
this.pool.push(obj)
}
}
// Usage: Particle pool
const particlePool = new ObjectPool(
// Create function
() => {
const geometry = new THREE.SphereGeometry(0.1)
const material = new THREE.MeshBasicMaterial({ color: 0xffffff })
return new THREE.Mesh(geometry, material)
},
// Reset function
(particle) => {
particle.position.set(0, 0, 0)
particle.visible = false
}
)
// Spawn particle
const particle = particlePool.acquire()
particle.position.set(Math.random(), Math.random(), Math.random())
particle.visible = true
scene.add(particle)
// Later: Return to pool
scene.remove(particle)
particlePool.release(particle)javascript
/**
* 对象池:复用对象而非频繁创建/销毁。
*/
class ObjectPool {
constructor(createFn, resetFn) {
this.pool = []
this.createFn = createFn
this.resetFn = resetFn
}
/**
* 从对象池获取对象,若无则创建新对象。
*/
acquire() {
if (this.pool.length > 0) {
return this.pool.pop()
}
return this.createFn()
}
/**
* 将对象放回对象池。
*/
release(obj) {
this.resetFn(obj)
this.pool.push(obj)
}
}
// 使用示例:粒子对象池
const particlePool = new ObjectPool(
// 创建函数
() => {
const geometry = new THREE.SphereGeometry(0.1)
const material = new THREE.MeshBasicMaterial({ color: 0xffffff })
return new THREE.Mesh(geometry, material)
},
// 重置函数
(particle) => {
particle.position.set(0, 0, 0)
particle.visible = false
}
)
// 生成粒子
const particle = particlePool.acquire()
particle.position.set(Math.random(), Math.random(), Math.random())
particle.visible = true
scene.add(particle)
// 后续:放回对象池
scene.remove(particle)
particlePool.release(particle)2. Visibility Culling
2. 可见性剔除
javascript
/**
* Hide objects far from camera.
*/
function updateVisibility(camera, objects, maxDistance = 50) {
const cameraPos = camera.position
objects.forEach(obj => {
const distance = obj.position.distanceTo(cameraPos)
obj.visible = distance < maxDistance
})
}javascript
/**
* 隐藏距离相机过远的对象。
*/
function updateVisibility(camera, objects, maxDistance = 50) {
const cameraPos = camera.position
objects.forEach(obj => {
const distance = obj.position.distanceTo(cameraPos)
obj.visible = distance < maxDistance
})
}3. Lazy Loading
3. 懒加载
javascript
/**
* Load textures on demand.
*/
class LazyTextureLoader {
constructor() {
this.loader = new THREE.TextureLoader()
this.cache = new Map()
}
async load(url) {
// Check cache
if (this.cache.has(url)) {
return this.cache.get(url)
}
// Load texture
return new Promise((resolve, reject) => {
this.loader.load(
url,
(texture) => {
this.cache.set(url, texture)
resolve(texture)
},
undefined,
reject
)
})
}
}javascript
/**
* 按需加载纹理。
*/
class LazyTextureLoader {
constructor() {
this.loader = new THREE.TextureLoader()
this.cache = new Map()
}
async load(url) {
// 检查缓存
if (this.cache.has(url)) {
return this.cache.get(url)
}
// 加载纹理
return new Promise((resolve, reject) => {
this.loader.load(
url,
(texture) => {
this.cache.set(url, texture)
resolve(texture)
},
undefined,
reject
)
})
}
}Part 8: Performance Monitoring
第八部分:性能监控
FPS Counter
FPS计数器
javascript
/**
* Simple FPS monitor.
*/
class FPSMonitor {
constructor() {
this.frames = 0
this.lastTime = performance.now()
this.fps = 60
}
update() {
this.frames++
const now = performance.now()
if (now >= this.lastTime + 1000) {
this.fps = Math.round((this.frames * 1000) / (now - this.lastTime))
this.frames = 0
this.lastTime = now
// Warn if FPS drops
if (this.fps < 30) {
console.warn(`Low FPS: ${this.fps}`)
}
}
}
getFPS() {
return this.fps
}
}
// Usage
const fpsMonitor = new FPSMonitor()
function animate() {
requestAnimationFrame(animate)
fpsMonitor.update()
renderer.render(scene, camera)
}javascript
/**
* 简单的FPS监控器。
*/
class FPSMonitor {
constructor() {
this.frames = 0
this.lastTime = performance.now()
this.fps = 60
}
update() {
this.frames++
const now = performance.now()
if (now >= this.lastTime + 1000) {
this.fps = Math.round((this.frames * 1000) / (now - this.lastTime))
this.frames = 0
this.lastTime = now
// 帧率过低时发出警告
if (this.fps < 30) {
console.warn(`低FPS: ${this.fps}`)
}
}
}
getFPS() {
return this.fps
}
}
// 使用示例
const fpsMonitor = new FPSMonitor()
function animate() {
requestAnimationFrame(animate)
fpsMonitor.update()
renderer.render(scene, camera)
}GPU Memory Monitoring
GPU内存监控
javascript
/**
* Monitor GPU memory usage.
*/
function logMemoryUsage(renderer) {
const info = renderer.info
console.log('GPU Memory:', {
geometries: info.memory.geometries,
textures: info.memory.textures,
programs: info.programs.length,
drawCalls: info.render.calls,
triangles: info.render.triangles
})
}
// Call periodically
setInterval(() => logMemoryUsage(renderer), 5000)javascript
/**
* 监控GPU内存使用情况。
*/
function logMemoryUsage(renderer) {
const info = renderer.info
console.log('GPU内存:', {
geometries: info.memory.geometries,
textures: info.memory.textures,
programs: info.programs.length,
drawCalls: info.render.calls,
triangles: info.render.triangles
})
}
// 定期调用
setInterval(() => logMemoryUsage(renderer), 5000)Critical Optimization Checklist
关键优化检查清单
Before Optimizing
优化前
- Profile first (Chrome DevTools Performance tab)
- Identify bottleneck (CPU or GPU?)
- Set target FPS (usually 60fps = 16ms/frame)
- 先进行性能分析(Chrome DevTools性能面板)
- 定位瓶颈(CPU还是GPU?)
- 设置目标帧率(通常60fps=每帧16ms)
Geometry
几何体
- Use InstancedMesh for repeated objects
- Implement LOD for distant objects
- Merge static geometries
- Use BufferGeometry (not Geometry)
- Dispose unused geometries
- 对重复对象使用InstancedMesh
- 为远距离对象实现LOD
- 合并静态几何体
- 使用BufferGeometry(而非Geometry)
- 释放未使用的几何体
Textures
纹理
- Use smallest texture size needed
- Power-of-two dimensions
- Compress textures (basis/KTX2)
- Set minFilter = LinearFilter if no mipmaps
- Dispose unused textures
- 使用满足需求的最小纹理尺寸
- 采用2的幂次尺寸
- 压缩纹理(basis/KTX2)
- 若不使用mipmap,设置minFilter=LinearFilter
- 释放未使用的纹理
Materials
材质
- Share materials across objects
- Use cheaper material types on mobile
- Limit transparent objects
- Use additive blending for glows
- Dispose unused materials
- 在多个对象间共享材质
- 在移动端使用轻量化材质
- 限制透明对象数量
- 发光效果使用加法混合
- 释放未使用的材质
Lighting
光照
- Limit lights (1-2 on mobile, 3-5 on desktop)
- Disable shadows on mobile
- Use baked lighting where possible
- Prefer directional/point over spot lights
- 限制灯光数量(移动端1-2盏,桌面端3-5盏)
- 移动端禁用阴影
- 尽可能使用烘焙光照
- 优先使用平行光/点光,而非聚光灯
Rendering
渲染
- Cap pixel ratio (1.5x mobile, 2x desktop)
- Disable antialiasing on mobile
- Use conditional rendering for static scenes
- Implement frustum culling
- Limit post-processing on mobile
- 限制像素比(移动端1.5x,桌面端2x)
- 移动端禁用抗锯齿
- 静态场景使用条件渲染
- 实现视锥体剔除
- 移动端限制后处理
Mobile-Specific
移动端专属
- Detect mobile devices
- Reduce geometry complexity
- Disable expensive features
- Lower pixel ratio
- Test on real devices (not just desktop browser)
- 检测移动设备
- 降低几何体复杂度
- 禁用高开销特性
- 降低像素比
- 在真实设备上测试(而非仅桌面浏览器)
Common Performance Killers
常见性能杀手
- Too many draw calls → Use InstancedMesh
- High-resolution textures → Resize to 1K or 2K
- Too many lights → Limit to 2-3
- Transparent objects → Use sparingly, render last
- Post-processing on mobile → Disable or simplify
- Memory leaks → Always dispose geometries/materials/textures
- Unnecessary re-renders → Use conditional rendering
- High pixel ratio on mobile → Cap at 1.5x
- 过多绘制调用 → 使用InstancedMesh
- 高分辨率纹理 → 缩小至1K或2K
- 灯光数量过多 → 限制为2-3盏
- 透明对象 → 谨慎使用,最后渲染
- 移动端后处理 → 禁用或简化
- 内存泄漏 → 务必释放几何体/材质/纹理
- 不必要的重复渲染 → 使用条件渲染
- 移动端高像素比 → 限制为1.5x
Performance Testing Workflow
性能测试流程
1. Test on Target Devices
1. 在目标设备上测试
javascript
// Detect and log device info
console.log('Device Info:', {
userAgent: navigator.userAgent,
pixelRatio: window.devicePixelRatio,
screen: `${window.screen.width}x${window.screen.height}`,
gpu: renderer.capabilities.getMaxAnisotropy()
})javascript
// 检测并记录设备信息
console.log('设备信息:', {
userAgent: navigator.userAgent,
pixelRatio: window.devicePixelRatio,
screen: `${window.screen.width}x${window.screen.height}`,
gpu: renderer.capabilities.getMaxAnisotropy()
})2. Profile with Chrome DevTools
2. 使用Chrome DevTools分析
- Open DevTools → Performance tab
- Record 5-10 seconds of rendering
- Look for:
- Long frames (>16ms)
- GPU bottlenecks
- Memory leaks
- 打开DevTools → 性能面板
- 录制5-10秒的渲染过程
- 查找:
- 长帧(>16ms)
- GPU瓶颈
- 内存泄漏
3. A/B Test Optimizations
3. A/B测试优化效果
javascript
// Feature flag for testing
const ENABLE_SHADOWS = !isMobile()
const ENABLE_BLOOM = !isMobile()
const MAX_PARTICLE_COUNT = isMobile() ? 100 : 500javascript
// 用于测试的功能开关
const ENABLE_SHADOWS = !isMobile()
const ENABLE_BLOOM = !isMobile()
const MAX_PARTICLE_COUNT = isMobile() ? 100 : 500Resources
资源
- THREE.js Docs: https://threejs.org/docs/
- THREE.js Performance Tips: https://discoverthreejs.com/tips-and-tricks/
- WebGL Fundamentals: https://webglfundamentals.org/
- GPU Performance: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices
- THREE.js文档: https://threejs.org/docs/
- THREE.js性能技巧: https://discoverthreejs.com/tips-and-tricks/
- WebGL基础: https://webglfundamentals.org/
- GPU性能: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices