Appearance
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 启用后的推荐顺序(安全优先)
- Draco 压缩
- 合并几何体
- 实例化渲染
- 适度减面
- 纹理降分辨率
- 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 的模型优化本质是工程化问题:你不是在优化某一个模型,而是在构建一套可持续的“资产准入机制”。当体检、策略与优化链路稳定后,性能讨论会从“感觉卡不卡”变成“指标是否超标、策略是否触发”,团队协作成本会明显下降。