0


Three.js性能优化和实践建议

Three.js 是一个功能强大的 3D 引擎,当场景足够大的时候,就会出现卡顿的现象,首先要保证电脑的性能够用,然后看看下面方法,帮助你提高应用的运行效率。

1. 使用

  1. stats.js

监视性能

在进行任何优化之前,首先要监视应用的性能。

  1. stats.js

是一个简单而有效的工具,可以帮助你实时监视帧率(FPS)、每帧渲染所需时间(MS)和内存使用情况(MB)。

**安装和使用

  1. stats.js

**

首先,通过 npm 安装

  1. stats.js

  1. npm install --save stats.js

然后,可以在 Three.js 项目中使用它:

  1. import Stats from 'stats.js';
  2. const stats = new Stats();
  3. stats.showPanel(0); // 显示面板 0: fps, 1: ms, 2: mb, 3+: custom
  4. document.body.appendChild(stats.dom);
  5. const tick = () => {
  6. stats.begin();
  7. // 监视的代码放在这里
  8. stats.end();
  9. requestAnimationFrame(tick);
  10. };
  11. requestAnimationFrame(tick);

FPS:在最后一秒内渲染的帧数。数值越高越好。

MS:渲染一帧所需的毫秒数。数值越低越好。

MB:分配的内存大小(以兆字节为单位)。需要在 Chrome 中使用

  1. --enable-precise-memory-info

启动。

CUSTOM:用户自定义面板支持。

2. 优化几何体和材质

复杂的几何体和高分辨率的材质会显著影响渲染性能。以下是一些优化建议:

降低几何体细节

使用

  1. THREE.LOD

(Level of Detail)类来根据摄像机距离动态切换几何体细节。

  1. import * as THREE from 'three';
  2. // 创建场景和相机
  3. const scene = new THREE.Scene();
  4. const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  5. camera.position.z = 10;
  6. // 创建渲染器
  7. const renderer = new THREE.WebGLRenderer({
  8. antialias: true,
  9. powerPreference: 'high-performance'
  10. });
  11. renderer.setSize(window.innerWidth, window.innerHeight);
  12. renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  13. document.body.appendChild(renderer.domElement);
  14. // 创建不同细节级别的几何体
  15. const highDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 32, 32, 32);
  16. const mediumDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 16, 16, 16);
  17. const lowDetailGeometry = new THREE.BoxGeometry(1, 1, 1, 8, 8, 8);
  18. const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
  19. // 使用 LOD 动态切换几何体
  20. const lod = new THREE.LOD();
  21. lod.addLevel(new THREE.Mesh(highDetailGeometry, material), 0);
  22. lod.addLevel(new THREE.Mesh(mediumDetailGeometry, material), 5);
  23. lod.addLevel(new THREE.Mesh(lowDetailGeometry, material), 10);
  24. scene.add(lod);
  25. // 动画循环
  26. const animate = function () {
  27. requestAnimationFrame(animate);
  28. // 旋转 LOD
  29. lod.rotation.x += 0.01;
  30. lod.rotation.y += 0.01;
  31. // 更新摄像机位置
  32. camera.position.x = Math.sin(Date.now() * 0.001) * 20;
  33. camera.position.z = Math.cos(Date.now() * 0.001) * 20;
  34. camera.lookAt(scene.position);
  35. // 渲染场景和相机
  36. renderer.render(scene, camera);
  37. };
  38. animate();

使用压缩纹理

使用压缩纹理格式(如 DDS、KTX2)来减少内存占用和加载时间。这里以

  1. KTX2

为例。

首先,安装

  1. three/examples/jsm/loaders/KTX2Loader.js

  1. Basisu

解码器,然后,在你的项目中使用

  1. KTX2Loader

加载压缩纹理:

  1. import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
  2. import { MeshStandardMaterial } from 'three';
  3. // 创建 KTX2Loader
  4. const ktx2Loader = new KTX2Loader()
  5. .setTranscoderPath('path/to/basisu/transcoder/') // 设置 Basisu 解码器路径
  6. .detectSupport(renderer);
  7. // 加载 KTX2 压缩纹理
  8. ktx2Loader.load('path/to/texture.ktx2', (texture) => {
  9. const material = new MeshStandardMaterial({ map: texture });
  10. const geometry = new THREE.BoxGeometry(1, 1, 1);
  11. const mesh = new THREE.Mesh(geometry, material);
  12. scene.add(mesh);
  13. });

合并几何体

(1)使用Blender将模型合并一下

(2)将多个几何体合并为一个几何体,以减少绘制调用(draw call)的次数。使用将使用

  1. BufferGeometryUtils

合并几何体。

  1. import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
  2. // 创建多个几何体
  3. const geometries = [];
  4. for (let i = 0; i < 50; i++) {
  5. const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
  6. geometry.translate(
  7. (Math.random() - 0.5) * 10,
  8. (Math.random() - 0.5) * 10,
  9. (Math.random() - 0.5) * 10
  10. );
  11. geometries.push(geometry);
  12. }
  13. // 合并几何体
  14. const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
  15. const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
  16. const mesh = new THREE.Mesh(mergedGeometry, material);
  17. scene.add(mesh);
  18. // 动画循环
  19. const animate = function () {
  20. requestAnimationFrame(animate);
  21. // 旋转合并后的几何体
  22. mesh.rotation.x += 0.01;
  23. mesh.rotation.y += 0.01;
  24. // 渲染场景和相机
  25. renderer.render(scene, camera);
  26. };
  27. animate();

3. 优化灯光和阴影

灯光和阴影计算开销较大,特别是多光源和动态阴影。以下是一些优化建议:

  • 减少光源数量:尽量减少场景中的光源数量,选择性能开销较小的光源如 AmbientLight 和 DirectionalLight。
  • 优化阴影贴图:降低阴影贴图的分辨率,并限制阴影相机的视野范围,以减少计算开销。
  • // 优化阴影贴图directionalLight.shadow.mapSize.width = 1024; // 默认值是 512directionalLight.shadow.mapSize.height = 1024; // 默认值是 512​// 限制阴影相机的视野范围directionalLight.shadow.camera.top = 3;directionalLight.shadow.camera.right = 6;directionalLight.shadow.camera.left = -6;directionalLight.shadow.camera.bottom = -3;directionalLight.shadow.camera.near = 0.1;directionalLight.shadow.camera.far = 10;​// 可选:使用相机助手查看阴影相机的范围const cameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera);scene.add(cameraHelper);
  • 静态光照贴图:对于静态场景,可以预先计算光照和阴影,生成光照贴图。这里我们使用 Lightmap,一个 Three.js 的扩展,可以帮助实现静态光照贴图。

首先,安装

  1. three-lightmap

  1. npm install three-lightmap

然后,在你的项目中使用

  1. three-lightmap

来生成静态光照贴图:

  1. import { Lightmap } from 'three-lightmap';
  2. // 创建静态光照贴图
  3. const lightmap = new Lightmap(scene, renderer, {
  4. mapSize: 1024,
  5. samples: 4,
  6. bake: true,
  7. exposure: 0.7,
  8. softEdges: 0.01,
  9. aoOnly: false,
  10. aoStrength: 0.6
  11. });
  12. // 在几何体上启用静态光照贴图
  13. cube.material.lightMap = lightmap.generate(cube.geometry);
  14. plane.material.lightMap = lightmap.generate(plane.geometry);
  15. // 运行一次性光照贴图烘焙过程
  16. lightmap.bake();

4. 纹理贴图

纹理贴图非常消耗 GPU 内存,以下是一些优化建议:

  • 调整尺寸:调整纹理贴图的分辨率可以通过图像编辑工具(如 Photoshop、GIMP)或编程工具(如 Sharp for Node.js)来实现。在加载纹理时,可以使用 Three.js 内置的 THREE.TextureLoader 来加载已经调整好尺寸的纹理。
  • 使用正确格式:确保使用合适的文件格式(如 .jpg 或 .png)。可以使用在线工具如 TinyPNG 来压缩纹理文件,减小文件大小,同时保持较高的视觉质量。
  • 保持分辨率为 2 的幂次方:确保纹理尺寸为 2 的幂次方(如 256x256, 512x512,1024x1024) 如果纹理的尺寸不是 2 的幂次方,Three.js 会自动调整它们,但这会影响性能。

5. 使用对象池

在动画或游戏应用中,经常需要频繁创建和销毁对象。使用对象池可以有效减少内存分配和垃圾回收频繁的开销。

  1. class ObjectPool {
  2. constructor(createFunc, size) {
  3. this.createFunc = createFunc;
  4. this.pool = [];
  5. for (let i = 0; i < size; i++) {
  6. this.pool.push(this.createFunc());
  7. }
  8. }
  9. get() {
  10. return this.pool.length ? this.pool.pop() : this.createFunc();
  11. }
  12. release(obj) {
  13. this.pool.push(obj);
  14. }
  15. }

6. 渲染器优化

以下是一些针对渲染器的优化建议:

限制像素比:一些设备有非常高的像素比,但渲染的像素越多,消耗的性能越大。将渲染器的像素比限制为 2:

  1. renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

配置偏好:指定

  1. powerPreference

属性来提示用户代理适当的 GPU 配置:

  1. const renderer = new THREE.WebGLRenderer({ powerPreference: 'high-performance' });

抗锯齿:只有在有明显锯齿且不会显著影响性能时才启用抗锯齿。

  1. // 创建渲染器时启用抗锯齿
  2. const renderer = new THREE.WebGLRenderer({
  3. canvas: document.querySelector('#canvas'),
  4. antialias: true, // 启用抗锯齿
  5. powerPreference: 'high-performance' // 提示浏览器选择高性能的 GPU
  6. });

7. 相机优化

通过缩小相机的视野范围(FOV)以及调整相机的

  1. near

  1. far

属性,可以显著减少渲染的对象数量,从而提高渲染性能。下面是具体的实现代码和逻辑说明。

缩小相机的视野范围

通过减少相机的视野角度(FOV),可以让屏幕中显示的对象更少,从而减少需要渲染的三角形数量。

调整相机的近端面和远端面

调整相机的

  1. near

  1. far

属性,可以确保只渲染特定范围内的对象,避免渲染不必要的远距离对象。

  1. // 调整相机的视野角度和近端面、远端面
  2. const fov = 50; // 缩小视野角度(默认值通常为75)
  3. const aspect = window.innerWidth / window.innerHeight;
  4. const near = 1; // 将 near 属性从 0.1 增大到 1
  5. const far = 50; // 将 far 属性从 100 缩小到 50
  6. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  7. camera.position.z = 10;

8. 清除不必要的对象

当场景中不再需要某个对象时,及时清除它:

  1. // 创建示例对象
  2. const geometry = new THREE.BoxGeometry();
  3. const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
  4. const cube = new THREE.Mesh(geometry, material);
  5. scene.add(cube);
  6. // 在某个时刻移除对象
  7. function removeObject(object) {
  8. // 移除对象
  9. scene.remove(object);
  10. // 释放几何体资源
  11. if (object.geometry) {
  12. object.geometry.dispose();
  13. }
  14. // 释放材质资源
  15. if (object.material) {
  16. if (Array.isArray(object.material)) {
  17. // 如果材质是数组,遍历并释放每个材质
  18. object.material.forEach((material) => {
  19. material.dispose();
  20. });
  21. } else {
  22. // 单一材质,直接释放
  23. object.material.dispose();
  24. }
  25. }
  26. // 释放纹理资源
  27. if (object.material.map) {
  28. object.material.map.dispose();
  29. }
  30. }
  31. // 在某个时刻调用函数移除对象
  32. removeObject(cube);

9. 后期处理和着色器优化

限制后期处理通道

每个后期处理过程都会增加渲染负担,尽量减少不必要的后期处理步骤。

着色器优化

  • 指定精度:强制材质中着色器的精度:
  1. const shaderMaterial = new THREE.ShaderMaterial({ precision: 'lowp' });
  • 保持代码简单:尽量保持着色器代码简单,避免复杂的逻辑和多层嵌套。
  • 使用贴图纹理:尽量使用纹理来代替复杂的计算,例如噪声生成。
  • 使用 defines:对于不会改变的值,使用 defines 而不是 uniform
  1. const shaderMaterial = new THREE.ShaderMaterial({
  2. defines: { uDisplacementStrength: 1.5 },
  3. });

性能优化任重而道远,有更好的方法可以分享出来呀。

标签: vue

本文转载自: https://blog.csdn.net/mad970906/article/details/143592680
版权归原作者 阿铎前端 所有, 如有侵权,请联系我们删除。

“Three.js性能优化和实践建议”的评论:

还没有评论