threejs-graphics-optimizer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

THREE.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

Version:1.0
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
Design philosophy: Optimize for mobile, scale up for desktop (not vice versa).

桌面端:高性能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):
  1. MeshBasicMaterial - No lighting, flat shading
  2. MeshLambertMaterial - Simple diffuse lighting
  3. MeshPhongMaterial - Specular highlights
  4. MeshStandardMaterial - PBR (expensive)
  5. 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
    })
性能排名(从快到慢):
  1. MeshBasicMaterial - 无光照、平面着色
  2. MeshLambertMaterial - 简单漫反射光照
  3. MeshPhongMaterial - 镜面高光
  4. MeshStandardMaterial - PBR(开销大)
  5. 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 buffer

javascript
// 发光效果使用加法混合(比透明更高效)
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

常见性能杀手

  1. Too many draw calls → Use InstancedMesh
  2. High-resolution textures → Resize to 1K or 2K
  3. Too many lights → Limit to 2-3
  4. Transparent objects → Use sparingly, render last
  5. Post-processing on mobile → Disable or simplify
  6. Memory leaks → Always dispose geometries/materials/textures
  7. Unnecessary re-renders → Use conditional rendering
  8. High pixel ratio on mobile → Cap at 1.5x

  1. 过多绘制调用 → 使用InstancedMesh
  2. 高分辨率纹理 → 缩小至1K或2K
  3. 灯光数量过多 → 限制为2-3盏
  4. 透明对象 → 谨慎使用,最后渲染
  5. 移动端后处理 → 禁用或简化
  6. 内存泄漏 → 务必释放几何体/材质/纹理
  7. 不必要的重复渲染 → 使用条件渲染
  8. 移动端高像素比 → 限制为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分析

  1. Open DevTools → Performance tab
  2. Record 5-10 seconds of rendering
  3. Look for:
    • Long frames (>16ms)
    • GPU bottlenecks
    • Memory leaks
  1. 打开DevTools → 性能面板
  2. 录制5-10秒的渲染过程
  3. 查找:
    • 长帧(>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 : 500

javascript
// 用于测试的功能开关
const ENABLE_SHADOWS = !isMobile()
const ENABLE_BLOOM = !isMobile()
const MAX_PARTICLE_COUNT = isMobile() ? 100 : 500

Resources

资源