Skip to content

Three.js 模型优化综合方案:体检指标 + 自动优化策略引擎

很多 3D 项目的性能问题并不是“优化没做”,而是“优化做晚了”:模型都加载进来了,页面已经开始掉帧,再去补救就会陷入反复试错。

更好的方式是把优化前置:模型加载/解析完成后先体检,超标则自动触发优化策略,通过统一规则把性能风险挡在渲染之前。

配套阅读:Three.js 性能面板必备指标(用于统一口径与阈值):/thoughts/threejs/threejs-performance-panel-core-metrics-analysis

一、核心思路:优化前置,让性能左移

Three.js 在 GLTFLoader 加载完成后,你可以拿到足够多的统计信息,用来判断:

  • 这个模型大概率会不会卡
  • 压力主要来自 GPU 算力、CPU DrawCall 还是显存
  • 是否需要进入自动优化流程

常用体检指标包括:

  • 三角面数:mesh.geometry.attributes.position.count / 3
  • 顶点数:与几何体顶点相关
  • 纹理数量与最大纹理尺寸:决定显存与带宽压力
  • 材质数量与 DrawCall:决定 CPU 提交与状态切换压力

二、落地流程:加载后体检 → 决策 → 优化 → 再渲染

mermaid
flowchart LR
  L[模型加载/解析] --> C[体检统计<br/>Triangles/Textures/DC/VRAM]
  C --> D{是否超标}
  D -- 否 --> R[直接渲染]
  D -- 是 --> P[策略引擎<br/>按规则挑选动作]
  P --> O[执行优化<br/>按配置启用]
  O --> R

这套流程可以做到:不盲目加载、可控降级、把“卡顿/闪退”变成可观测、可策略化的问题。

三、优化一定会影响模型吗:先把两类优化分清楚

优化基本分两类,是否影响外观是第一条分界线:

类型是否影响外观优先级典型动作适用情况
安全优化基本不影响Draco、合并几何体、实例化启用时优先选用
有损优化会影响减面、降纹理、精简贴图通道、LOD需要兜底与可配置开关

做策略引擎时,建议保证:默认不强制执行任何优化动作;当项目开启优化时,优先选择安全优化;有损优化必须可控、可回退、可按设备分层启用。

四、安全优化(优先用):尽量不改外观,先把性能风险压下去

4.1 网格压缩:Draco

  • 动了什么:顶点数据的存储与编码方式
  • 不动什么:顶点位置、面数、形状与外观
  • 主要收益:文件体积通常可减少 60% ~ 90%,降低下载与解析压力
  • 适用情况:模型体积大、网络与解析开销明显时

4.2 合并几何体(Merge Geometry)

  • 动了什么:将多个小几何体合成更少的几何体
  • 不动什么:模型外观、尺寸、UV、纹理
  • 主要收益:显著降低 DrawCall,让帧率更稳定
  • 适用情况:场景里碎片化 mesh 多、材质相对可收敛时

4.3 实例化渲染(InstancedMesh)

  • 动了什么:渲染方式(把重复物体合并为一次绘制)
  • 不动什么:模型本身的几何与材质语义
  • 主要收益:大量重复物体时性能提升非常显著
  • 适用情况:重复摆件、树、灯、螺丝等“同形多实例”

五、有损优化(不得已才用):必须可控、可降级、可回退

5.1 模型减面(Simplify / Decimate)

  • 动了什么:三角面数量减少
  • 不动什么:纹理、比例(一般可以保持)
  • 影响:
    • 轻度减面:肉眼几乎看不出
    • 重度减面:细节丢失、边缘变圆、模型发糊
  • 主要收益:降低 GPU 算力压力

5.2 纹理降分辨率(4K → 2K → 1K → 512)

  • 动了什么:贴图清晰度
  • 不动什么:模型形状
  • 影响:细节/文字变糊是最常见的负反馈
  • 主要收益:显存压力下降最明显,往往也是移动端兜底手段

5.3 精简贴图通道(PBR 贴图精简)

  • 动了什么:去掉金属、粗糙度、法线等贴图通道
  • 不动什么:模型形状与基础颜色
  • 影响:质感下降,容易出现“塑料感”
  • 主要收益:减少纹理数量与显存占用

5.4 LOD 层级(远距离低配,近距离高配)

  • 动了什么:不同距离显示不同细节等级的模型
  • 不动什么:近距离观感可以保持
  • 影响:远距离精度下降
  • 主要收益:整体 GPU 压力可控,适合大场景与可视距离较远的项目

六、策略引擎:把“经验”固化成可配置规则

6.1 体检指标与触发条件(示例)

触发阈值建议与性能面板指标保持一致,并按设备档位做默认值分层(桌面/移动端阈值不同)。下面给出一套可直接落地的“默认值”,项目可按业务类型与目标机型调整:

指标建议触发(桌面)建议触发(移动)优先动作
三角面数> 30 万> 15 万减面 / LOD
最大纹理尺寸> 2048> 2048降纹理 / KTX2
纹理数量> 10> 8精简通道 / 复用 / 降纹理
DrawCall> 300> 150合并几何体 / 实例化

6.2 启用后的推荐顺序(安全优先)

  1. Draco 压缩
  2. 合并几何体
  3. 实例化渲染
  4. 适度减面
  5. 纹理降分辨率
  6. LOD

6.3 伪代码:决策与执行

ts
type ModelStats = {
  triangles: number
  drawCalls: number
  textureCount: number
  maxTextureSize: number
  deviceTier?: 'desktop' | 'mobile'
}

type OptimizeAction =
  | 'draco'
  | 'mergeGeometry'
  | 'instancing'
  | 'simplify'
  | 'downscaleTextures'
  | 'lod'

function planActions(stats: ModelStats): OptimizeAction[] {
  const actions: OptimizeAction[] = []

  const isMobile = stats.deviceTier === 'mobile'
  const drawCallLimit = isMobile ? 150 : 300
  const trianglesLimit = isMobile ? 150_000 : 300_000
  const textureCountLimit = isMobile ? 8 : 10
  const maxTextureSizeLimit = 2048

  actions.push('draco')
  if (stats.drawCalls > drawCallLimit) actions.push('mergeGeometry')
  if (stats.textureCount > textureCountLimit || stats.maxTextureSize > maxTextureSizeLimit) actions.push('downscaleTextures')
  if (stats.triangles > trianglesLimit) actions.push('simplify')

  return actions
}

七、最终目标:别让用户“加载完再卡”

这套方案落地的关键不在于某个具体优化技巧,而在于建立一条可复用的链路:

  • 体检口径统一
  • 触发条件清晰
  • 优化动作可配置、可降级、可回退
  • 用规则把经验变成团队共识

架构思考

Three.js 的模型优化本质是工程化问题:你不是在优化某一个模型,而是在构建一套可持续的“资产准入机制”。当体检、策略与优化链路稳定后,性能讨论会从“感觉卡不卡”变成“指标是否超标、策略是否触发”,团队协作成本会明显下降。