首页/文章/threejs

加载3D模型性能优化

2023-10-17
399014 分钟
...

一、 引入

无论是大型虚拟世界还是简单的网站,性能优化都是必要的。

特别是在运用三维模型的情况下,我们需要更加深入的优化。因为三维模型通常包含大量的数据和复杂的几何形状,如果不进行性能优化,浏览器可能会因为负载过重而崩溃。

在本文中,我们将探讨如何在three.js / React-three-fiber中对加载三维模型进行性能优化。我们将介绍一些实践方法和技巧,让你的虚拟世界或网站能够以更好的性能运行。

二、 性能监测工具简介:stats.js \ r3f-perf \spector.js

① 写原生three.js可用stats.js

Stats.js是一个轻量级的性能监测库,可以用于监测WebGL或者其他HTML5应用的帧速率、内存使用情况等性能指标。它可以在浏览器中展示FPS(每秒帧数)、渲染时间、内存占用等性能指标,便于开发者实时监测应用程序的性能状况并进行优化。

image.png

image.png

代码示🌰:

var stats = new Stats(); 
stats.showPanel(1); // 0: fps, 1: ms, 2: mb, 3+: custom
document.body.appendChild(stats.dom);  
function animate() {   
  stats.begin();    // monitored code goes here   
  stats.end();   
  requestAnimationFrame(animate);
}  
requestAnimationFrame(animate);
② 在react-three/fiber可用r3f-perf

r3f-perf是一个React Three Fiber(R3F)的性能分析工具,它可以帮助开发者识别和解决在使用R3F构建WebGL应用程序时可能遇到的性能问题。它提供了实时的性能指标,例如帧率、GPU和CPU使用率、内存使用量等

](https://wtp9t.csb.app/)

代码示🌰:

import { Canvas } from "@react-three/fiber"; 
import { Perf } from "r3f-perf";  
function App() {   
  return (    
    <Canvas>    
      <Perf position="bottom-left" />   
    </Canvas>
  ); 
}
③ 用spector.js深入分析

Spector.js可以捕获所有WebGL调用,并将其记录到一个文件中,以便开发人员可以分析和调试应用程序中的问题,不仅有依赖包,还有浏览器插件,数据详细,适用于性能深入分析。

](https://spector.babylonjs.com/demos/instancedbones/)


三、解决方案

(1) 使用 gltf-pipeline 压缩文件

](https://cesium.com/blog/2018/04/09/draco-compression/)

1. 简单介绍gltf-pipeline

gltf-pipeline 是一个用于转换、优化和验证 GLTF 文件的命令行工具。它可以

  • 校验 GLTF 文件的结构、语法和语义是否正确;
  • 优化 GLTF 文件的大小和加载性能
  • 转换 GLTF 文件到其他格式,如 glb、FBX、OBJ、Collada、Three.js JSON 等。

2. 代码示例

压缩glb文件命令行

gltf-pipeline -i male.glb -o male-processed.glb -d
① 使用原生 three.js 时,需要用 DRACOLoader 解码
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; 
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";  
const loader = new GLTFLoader();  // 创建解码器实例 
const dracoLoader = new DRACOLoader(); // 设置解压库文件路径 
dracoLoader.setDecoderPath(DECODER_PATH); // 加载解码器实例 loader.setDRACOLoader(dracoLoader);  loader.load(MODEL_FILE_PATH, (gltf) => { 
let model = gltf.scene;   // .... 
});

![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
② 使用 react-three/fiber 时,直接使用 useGLTF 加载即可
  • primitive 只可加载一个模型
import { Clone } from "@react-three/drei";  
function Model() { 
  const { scene } = useGLTF(MODEL_FILE_PATH);  
  return <primitive object={scene} />; 
}
  • Clone 可批量加载多个模型
import { Clone, useGLTF } from "@react-three/drei"; 
function Model() {   
  const { nodes } = useGLTF(MODEL_FILE_PATH);   
  return <Clone object={nodes.foo} />;
}

3. 优缺点

优点:
  • 压缩后的文件大小更小,加载速度更快
  • 可以优化模型的渲染性能,减少 GPU 的工作负担,提高帧率和动画流畅度
  • 可以自动优化模型的材质和纹理,使其更加符合 GPU 的渲染特性,提高渲染效率
缺点:
  • 压缩过程可能会损失一些模型细节和质量,导致模型不够精细;
  • 压缩后的文件需要解压缩才能使用,增加了一些额外的步骤和时间

4. 适用场景

① 在移动端等网络环境较差的情况下,压缩模型可以减少加载时间和流量消耗;
② 当模型文件较大,需要优化渲染性能时,可以使用压缩工具进行优化;
③ 在需要快速加载和渲染大量模型的场景中,压缩文件可以提高整体性能。


(2) 使用 instanceMesh 减少 draw call

1. instanceMesh 定义

InstancedMesh 是 Mesh 的一个特殊版本,支持实例化渲染。如果您需要渲染大量具有相同几何和材质但具有不同世界变换的对象,请使用 InstancedMesh。使用 InstancedMesh 将帮助你减少绘制调用的数量(draw calls),从而提高应用程序的整体渲染性能。

2. instanceMesh 原理

InstancedMesh 是一种优化技术,它允许在场景中创建多个具有相同形状和材质的对象,同时只使用一次网格数据。它的原理是通过使用单个网格实例来渲染多个实例,而不是为每个实例创建单独的网格。

在实现上,InstancedMesh 的原理是使用 instancing 技术,通过将变量和矩阵传递到着色器中,来控制每个实例的位置、旋转和缩放等属性。

3.代码示例

① 使用原生 three.js

性能测试例子性能测试代码

不使用instanceMesh
drawcalls为1000

核心代码如下:

const count = 1000;         
const matrix = new THREE.Matrix4();  
for ( let i = 0; i < count; i ++ ) { 	     
  randomizeMatrix( matrix ); 	   
  const mesh = new THREE.Mesh( geometry, material ); 	 
  mesh.applyMatrix4( matrix );  	
  scene.add( mesh );  	
}

使用instanceMesh
drawcalls为1

核心代码如下:

const count = 1000; 
const matrix = new THREE.Matrix4(); 
const mesh = new THREE.InstancedMesh(geometry, material, count);  
for (let i = 0; i < count; i++) {   
  randomizeMatrix(matrix);  
  mesh.setMatrixAt(i, matrix); 
}  
scene.add(mesh);
② 使用 react-three/fiber

实现 instanced 的例子

将 glb/gltf 文件自动转为 gltfjsx 的工具:gltfjsx

(拖拽文件生成 https://gltf.pmnd.rs/ ,但是自动生成的可能不对,需要自己调整一下)

再举个🌰

// 定义InstancesProvider 
import React, { useMemo, useContext, createContext } from 'react' 
import { useGLTF, Merged, } from '@react-three/drei'  
const context = createContext() 
export function InstancesProvider({ children, ...props }) { 
  const { nodes } = useGLTF(MODEL_FILE_PATH)  
  const instances = useMemo(() => ({
    Screw1: nodes['Screw1'], 
    Screw2: nodes['Screw2'] }
                                  ),
                            [nodes])  
  return (    
    <Merged meshes={instances} {...props}>      
      {(instances) => <context.Provider value={instances} children={children} />
      }    
    </Merged>
  ) }  // 定义Model 
export function Model(props) {  
  const instances = useContext(context)  
  return (    
    <group {...props} dispose={null}>   
      <instances.Screw1 position={[-0.42, 0.04, -0.08]} rotation={[-Math.PI / 2, 0, 0]} />       <instances.Screw2 position={[-0.42, 0.04, -0.08]} rotation={[-Math.PI / 2, 0, 0]} />     </group>  
  )
}  // 使用Model 
import { InstancesProvider, Model } from './Model' 
<InstancesProvider>  
  <Model position={[10,0,0]}> 
  <Model position={[-10,0,0]}> 
  <Model position={[-10,10,0]}> 
  </InstancesProvider>

![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

4. 优缺点

优点:
  • 减少了渲染调用的数量,提高了渲染效率
  • 能够轻松地复制多个相同的模型,节省了内存空间
  • 可以通过修改实例矩阵来实现动态变换,比如实现粒子效果。
缺点:
  • 不能直接修改实例的顶点属性,只能通过修改实例矩阵来实现动态变换;
  • 需要对实例矩阵进行矩阵计算,可能会影响性能

5.适用场景及不适用场景

① 适用场景

(在这些场景下,使用 InstancedMesh 可以减少 GPU 的工作量,从而提高渲染性能):

大量相似的物体需要被渲染,例如草、树、石头等;
② 展示大规模的复杂场景,例如城市或者森林等;
③ 大量重复的元素需要被渲染,例如在复杂的建筑中的砖块、玻璃面板等;
④ 需要在不同的地方渲染同一个物体,例如在多个镜头之间切换的时候。

② 不适用场景

(在以下场景下使用 InstancedMesh 有可能会使性能变差):

① 当需要在每个实例之间进行大量不同的计算时,例如不同的动画或者物理模拟;
② 当需要在每个实例之间进行大量不同的着色器计算时,例如每个实例有不同的材质或者纹理;
③ 当需要经常更新实例的位置、旋转或者缩放时。


(3) 其他优化方案

  • 减少模型面数:可以通过优化 3D 模型来减少面数,从而提高性能。可以使用 Blender 等建模软件来进行优化,也可以使用 Three.js 自带的 SimplifyModifier 和 DecimationModifier 来进行简化。
  • 使用纹理贴图:纹理贴图可以减少几何体的面数,同时提高渲染效率。可以使用 UV 映射技术将纹理贴图应用到 3D 模型上。
  • 合并几何体:将多个几何体合并成一个可以减少渲染调用次数,从而提高性能。可以使用 Three.js 自带的 MergeGeometry 和 BufferGeometryUtils 工具类来实现。
  • 使用 LOD(Level of Detail):使用 LOD 技术可以根据距离远近来切换不同的模型细节级别,从而提高性能。
  • 使用 GLTFpack 实现快速压缩和优化 GLTF 文件,提高加载速度。

四、总结

本文先简单介绍了三种性能监测工具:stats.js \ r3f-perf \spector.js,再针对运用原生three.jsreact-three/fiber加载三维模型提供了多种优化性能的方案,其中,主要探讨了使用 gltf-pipeline 压缩文件和
使用 instanceMesh 减少 draw call的原理、实践方法、优缺点及使用场景。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
threejs2024-05-31
three 高性能镜头编辑器
在做 可视化项目的过程中,如果涉及到镜头编辑的话免不了来回改,那这次就来实现一个高性能镜头编辑器,能方便地编辑镜头轨迹并导出数据。移动端 图可能有卡顿,建议用电脑浏览,内容较多,建议收藏后查看 资源 这个编辑器用到了官方的案例: 调试场景用到了下面官方案例里的泳池场景: 开头录屏使用的场景用到了 SketchUp 作者 amogusstrikesback2 的场景: 涉及技术 Renderer CatmullRomCurve3 Gene...
学习
AI2026-09-01
Deep Agents 01:何为 Agent Harness,以及如何开始
1、本篇任务:完成一份多步骤、带证据的技术调研 普通客服 Agent 的问题短、工具少、输出即时。技术调研或编码任务会持续很久,产生计划、搜索结果、文件和中间结论。Deep Agents 在 LangChain/LangGraph 之上预装规划、虚拟文件系统、上下文压缩和子 Agent,适合这类开放任务。 本课让 Agent 比较两种向量数据库,并交付一份可验证报告。 2、什么时候需要 Deep Agent 满足以下两项以上再考虑:任务...
学习
AI2026-09-01
Deep Agents 02:子 Agent、虚拟文件系统与长期记忆
1、本篇任务:让主管只看结论,让子 Agent 处理细节 技术调研会产生几十次搜索和大量文件。如果全部进入主管上下文,真正的目标会被噪音淹没。本课用两个子 Agent: 收集证据, 检查结论;主管负责计划与最终合成。 2、什么时候委派,什么时候直接调用工具 适合委派:子任务有多步;需要专门提示或工具;会产生大量中间结果;只需返回有限结论。不适合:一步查询;主管需要全部中间上下文;协调成本超过任务本身。 3、配置专门子 Agent Pyt...
学习
AI2026-09-01
Deep Agents 03:生产化、Sandbox、权限与上线验收
1、本篇任务:让 Deep Agent 在隔离环境中分析代码 只读研究 Agent 风险有限;编码 Agent 需要读写文件、安装依赖和执行测试。本课不讲如何让模型写更漂亮的代码,只讲执行环境、权限、恢复和上线验收。 2、先做威胁模型 资产包括源代码、用户文件、云凭证、生产网络和发布权限;攻击入口包括用户消息、仓库内容、网页、依赖包、MCP 返回和命令输出。 Prompt injection 不是靠一句 system prompt 解决...
学习
AI2026-09-01
LangChain 01:全景、原理与学习路线
1、本篇学完要得到什么 这一篇只解决三个问题:LangChain 到底负责什么;它与 LangGraph、Deep Agents、LangSmith 是什么关系;后面应按什么顺序学习。 贯穿整套课程的项目是“退款政策与订单助手”。它最终能够:回答知识库中的退款规则;查询当前用户的订单;生成结构化答复;对真正的退款操作进行人工审批;断线后恢复;通过评测后发布。 先记住一句话: 模型负责理解与生成,应用负责数据、权限、状态和副作用。 如果把...
学习
AI2026-09-01
LangChain 02:模型、消息与结构化输出
1、本篇任务:让模型输出成为程序可以依赖的合同 上一课只证明 Agent 能运行。本课暂时不接业务工具,只完成一个“客服分诊器”:输入用户问题,输出意图、紧急程度、是否需要人工和给用户的答复。 本课的核心不是学更多模型参数,而是理解三层合同:消息决定模型看到了什么;schema 决定程序期待什么;业务校验决定结果是否真的可用。 2、消息不是一段字符串,而是一条执行记录 一次工具型对话通常包含四种消息: | 类型 | 由谁产生 | 作用...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录