Three.js

Three.js 性能优化

1.渲染,抗锯齿优化

1
2
3
4
5
6
7
8
 const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true, //是否启用抗锯齿
});

需求场景:根据用户设备判断cpu性能,gpu性能,如果性能比较差,就把抗锯齿关掉,如果性能好就把抗锯齿打开,就和游戏中的高画质,低画质一样,属于画质切换。

抗锯齿其实底层就是一套数学算法,开启它后会增加性能开销。
1
2
3
4
5
6
7
8
9
10
11
12
13
Three.js 中,顶点着色器中的变量可以使用 uniform、attribute 和 varying。这些不同类型的变量用于不同的目的:

Uniform:
用于从 JavaScript 代码传递全局变量到着色器,这些变量在一个绘制调用期间对所有顶点(顶点着色器)或片元(片元着色器)都保持不变。
在顶点着色器中使用 uniform 变量时,通常用于传递变换矩阵、时间、光源参数等。

Attribute:
用于从 JavaScript 代码传递每个顶点独特的数据到顶点着色器,例如顶点位置、法向量、纹理坐标等。
在片元着色器中不能直接使用 attribute,只能在顶点着色器中使用。

Varying:
用于在顶点着色器和片元着色器之间传递数据。顶点着色器计算出 varying 变量的值,并将其插值后传递给片元着色器。
在顶点着色器中定义 varying 变量,并在片元着色器中使用。

2.性能指标检测

  • 1.用户的设备性能标准 2.监测帧率 –stats.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function estimatePerformance() {
// 获取 CPU 核心数
const cores = navigator.hardwareConcurrency || 1;

// 测量一个操作的执行时间
const start = performance.now();
for (let i = 0; i < 1e7; i++) {} // 简单的计算任务
const end = performance.now();
const duration = end - start;

// 根据核心数和执行时间估计性能
if (cores >= 8 && duration < 50) {
console.log("高性能设备");
} else if (cores >= 4 && duration < 100) {
console.log("中等性能设备");
} else {
console.log("低性能设备");
}
}

estimatePerformance();

3.画面 resize 优化,开启节流

1
2
3
4
5
6
7
8
9
10
11
window.addEventListener("resize", () => {
console.log("resize");
// 更新宽高比
camera.aspect = window.innerWidth / window.innerHeight;
// 更新相机的投影矩阵
camera.updateProjectionMatrix();

renderer.setSize(window.innerWidth, window.innerHeight);
// 设置像素比
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});

4.创建点云可以关闭深度测试

  • 损失渲染精度
  1. 获得好的性能
1
2
3
4
5
6
this.material = new THREE.PointsMaterial({
size: 50,
map: new THREE.TextureLoader().load(smoke_png),
transparent: true,
depthWrite: false,
});

5.尽量使用 clone

  • threejs 一切皆对象。适合: 相似度比较高的物体。
  • 在 Three.js 中,clone 方法用于创建一个对象的深拷贝。这个方法可以应用于几乎所有的 Three.js 对象,包括几何体、材质、网格、场景等。通过 clone 方法,你可以复制一个对象及其所有属性,而不会影响原始对象。
1
2
3
4
Three.js 中使用 clone 方法可以对性能产生积极影响,主要原因如下:
1. 减少计算开销:克隆对象时,Three.js 会复制现有对象的所有属性和状态,而不需要重新计算几何体、材质或其他复杂属性。这减少了 CPU 的计算开销。
2. 共享资源:克隆的对象可以共享一些资源(如纹理、材质等),而不需要为每个对象创建新的资源。这减少了内存使用和加载时间。
3. 简化代码:使用 clone 方法可以简化代码,使得创建多个相似对象变得更加容易和高效。这有助于减少代码的复杂性和潜在的错误。

6.不需要的对象要销毁

  • 不销毁的坏处?
  1. 占用内存,可能会有内存泄露
  2. 有额外的渲染开销
  • 在 Three.js 中,dispose 方法用于释放对象占用的内存资源。对于几何体、材质、纹理等对象,调用 dispose 方法可以确保这些资源被正确地释放,从而避免内存泄漏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import * as THREE from "three";
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js";

class TextManager {
constructor(font) {
this.font = font;
this.texts = [];
this.init();
}

init() {
[
{ text: "Hello", color: "#ffffff" },
{ text: "Three.js", color: "#ffffff" },
].forEach((item) => {
this.createText(item);
});
}

createText(data) {
const geometry = new TextGeometry(data.text, {
font: this.font,
size: 20,
height: 2,
});

const material = new THREE.ShaderMaterial({
vertexShader: `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
`,
});

const mesh = new THREE.Mesh(geometry, material);
this.texts.push(mesh);
}

dispose() {
this.texts.forEach((mesh) => {
mesh.geometry.dispose();
mesh.material.dispose();
});
this.texts = [];
}
}

// 示例使用
const fontLoader = new THREE.FontLoader();
fontLoader.load("path/to/font.json", function (font) {
const textManager = new TextManager(font);

// 在需要释放资源时调用 dispose 方法
textManager.dispose();
});

7.尽量使用 BufferGeometry

  • 使用 BufferGeometry 对性能更好。BufferGeometry 是 Three.js 中的一种几何体类型,它比传统的 Geometry 更高效,特别是在处理大量顶点数据时。BufferGeometry 直接使用 WebGL 的缓冲区来存储顶点数据,从而减少了内存占用和 CPU 开销。
    1. 直接使用 WebGL 缓冲区BufferGeometry 直接使用 WebGL 的缓冲区来存储顶点数据。这意味着数据可以直接传递给 GPU(利用显存),而不需要在 CPU 和 GPU 之间进行额外的转换和处理。
    2. 内存布局更紧凑BufferGeometry 使用类型化数组(如 Float32Array)来存储顶点数据,这种内存布局更紧凑,减少了内存占用和缓存未命中。
    3. 减少 CPU 开销:由于 BufferGeometry 的数据结构更简单,Three.js 在处理几何体时需要的 CPU 计算量更少。这使得渲染过程更加高效,特别是在处理大量顶点数据时。
    4. 更灵活的顶点属性BufferGeometry 允许你定义自定义的顶点属性(如颜色、法线、UV 坐标等),并且可以更灵活地控制这些属性的更新和使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import * as THREE from "three";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader.js";
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js";

class TextManager {
constructor(font) {
this.font = font;
this.texts = [];
this.init();
}

init() {
[
{ text: "Hello", color: "#ffffff" },
{ text: "Three.js", color: "#ffffff" },
].forEach((item) => {
this.createText(item);
});
}

createText(data) {
const geometry = new TextGeometry(data.text, {
font: this.font,
size: 20,
height: 2,
});

// 将 TextGeometry 转换为 BufferGeometry
const bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry);

const material = new THREE.ShaderMaterial({
vertexShader: `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
`,
});

const mesh = new THREE.Mesh(bufferGeometry, material);
this.texts.push(mesh);
}

dispose() {
this.texts.forEach((mesh) => {
mesh.geometry.dispose();
mesh.material.dispose();
});
this.texts = [];
}
}

// 示例使用
const fontLoader = new FontLoader();
fontLoader.load("path/to/font.json", function (font) {
const textManager = new TextManager(font);

// 在需要释放资源时调用 dispose 方法
textManager.dispose();
});

8.图片纹理优化

  • 在 Three.js 中,可以通过使用压缩纹理来减少内存占用和提高渲染性能。常见的压缩纹理格式包括 DDSKTXPVR。这些格式可以显著减少纹理的内存占用,并且在 GPU 上的解压缩速度非常快。
  • 纹理压缩格式有多种,每种格式都有其独特的优点和适用场景。以下是一些常见的纹理压缩格式及其区别:
1
2
3
4
5
6
7
8
9
10
1. DDS (DirectDraw Surface) - 优点: - 支持多种压缩格式(如 DXT1、DXT5)。 -
广泛支持,特别是在 DirectX 环境中。 - 支持 mipmap 和立方体贴图。 - 缺点: -
文件较大。 - 不支持所有平台,特别是在 WebGL 环境中。 2. KTX (Khronos Texture)
---这种最常用,且普遍 - 优点: - 支持多种压缩格式(如 ETC1、ETC2、ASTC)。 -
适用于 OpenGL 和 WebGL。 - 支持 mipmap 和立方体贴图。 - 缺点: -
需要额外的工具来生成 KTX 文件。 3. PVR (PowerVR Texture) - 优点: -
专为移动设备设计,特别是 PowerVR GPU。 - 高压缩率,适合存储空间有限的设备。 -
支持 mipmap 和立方体贴图。 - 缺点: - 仅在支持 PowerVR GPU 的设备上表现最佳。 4.
Basis Universal - 优点: - 支持多种平台和 GPU。 - 高压缩率,适合网络传输。 -
可以在运行时解码为多种 GPU 纹理格式。 - 缺点: - 需要额外的库来解码。

9.在需要的时候进行 render

  • threejs,项目通常在使用 render 的函数中,是一直在执行的,就是说 gpu 是一直在占用的。导致他一直在占用电脑性能
1
2
3
4
5
6
7
8
9
10
11
12
//每一帧render
const start = () => {
//每一帧的间隔时间,以秒为单位
// console.log(clock.getDelta());
const delta = clock.getDelta();

city.start(delta);

renderer.render(scene, camera);
requestAnimationFrame(start);
};
start();
  • 怎么解决?

在需要的时候,设置一个开关

  • 使用场景
  1. 挂机时间远远大于需要动态渲染的时间
  2. 挂机的时候,状态可以是静止
  3. 本来是一直运动的,但是长时间不管,静止也 ok
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let renderEnabled = false;
let timeOut: number;

const animate = () => {
requestAnimationFrame(animate);
// 设置一个变量 减少没用的执行
if (renderEnabled) {
// 这里是你自己业务上需要的code
controls.update(clock.getDelta());
renderer.render(scene, camera);
}
stats.update();
};

// 需要渲染的时候调用一下即可
const timeRender = (time: number = 3000) => {
renderEnabled = true;
clearTimeout(timeOut);
timeOut = setTimeout(() => {
renderEnabled = false;
}, time);
};

// 启动动画循环
animate();

10.gltf 模型压缩

  • gltf-pipeline 是一个用于处理和优化 glTF 文件的工具库。它可以用于压缩、转换和优化 glTF 3D 模型,以提高加载性能和减少文件大小。
  • npm install -g gltf-pipeline
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//在node端运行

const fs = require('fs');
const path = require('path');
const gltfPipeline = require('gltf-pipeline');

const inputFilePath = 'path/to/your/input.gltf';
const outputFilePath = 'path/to/your/output.gltf';

const gltf = fs.readFileSync(inputFilePath);

gltfPipeline.processGltf(gltf, { dracoOptions: { compressionLevel: 10 } })
.then((results) => {
fs.writeFileSync(outputFilePath, Buffer.from(results.gltf));
console.log('Optimization complete.');
})
.catch((error) => {
console.error('Error during optimization:', error);
});
  • gltf-pipeline 的底层是 Draco(zhua kou)
  • Draco 是 Google 开发的一种开源库,用于压缩和解压缩 3D 图形数据。它可以显著减少 3D 模型的文件大小,从而提高加载速度和减少带宽使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('path/to/draco/gltf/'); // 设置 Draco 解码器路径

const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);

loader.load('path/to/your/model.gltf', (gltf) => {
scene.add(gltf.scene);
animate();
}, undefined, (error) => {
console.error('An error happened', error);
});

camera.position.z = 5;

const animate = () => {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};

// 启动动画循环
animate();

11.模型压缩原理

  • 为了减少模型的顶点和面数,同时使用法线贴图来保持视觉效果,可以使用 gltf-pipelineDraco 压缩工具来优化 glTF 文件。
1
2
3
4
5
1. 使用 gltf-pipeline 和 Draco 压缩工具优化 glTF 文件。
1. 减少体积
2. 减少计算量
2.Three.js 中加载和渲染优化后的 glTF 文件。
3. 在模型上应用法线贴图。

12.降低材质精度

  • 为了降低材质精度并尽量共享材质,可以在创建材质时设置材质的精度,并在多个网格之间共享同一个材质实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import * as THREE from 'three';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';

class TextManager {
private scene: THREE.Scene;
private font: THREE.Font;
private sharedMaterial: THREE.MeshBasicMaterial;

constructor(scene: THREE.Scene, font: THREE.Font) {
this.scene = scene;
this.font = font;
this.sharedMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
precision: 'lowp' // 设置材质精度为低精度
});
}

createText(data: { text: string, position: THREE.Vector3, rotate: number }) {
const geometry = new TextGeometry(data.text, {
font: this.font,
size: 20,
height: 2,
});

const mesh = new THREE.Mesh(geometry, this.sharedMaterial); // 使用共享材质

mesh.position.copy(data.position);
mesh.rotateY(data.rotate);

this.scene.add(mesh);
}
}

// 示例使用
const fontLoader = new THREE.FontLoader();
fontLoader.load('path/to/font.json', (font) => {
const scene = new THREE.Scene();
const textManager = new TextManager(scene, font);

textManager.createText({ text: 'Hello, World!', position: new THREE.Vector3(0, 0, 0), rotate: 0 });
textManager.createText({ text: 'Another Text', position: new THREE.Vector3(1, 1, 1), rotate: Math.PI / 4 });

// 继续添加其他文本...
});

13.使用 LOD 技术优化

  • LOD(Level of Detail)技术是一种在 3D 图形中根据物体与相机的距离动态调整物体细节层次的技术。通过使用 LOD,可以在远距离时使用低细节模型,在近距离时使用高细节模型,从而提高渲染性能。
  • 利用压缩工具对同一种模型,导出 3 种精度的模型(顶点数量不一样)
    1. 高精
    2. 中等
    3. low
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("path/to/draco/gltf/"); // 设置 Draco 解码器路径

const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);

const lod = new THREE.LOD();

loader.load(
"path/to/your/high-detail-model.gltf",
(gltf) => {
const highDetailMesh = gltf.scene.children[0];
lod.addLevel(highDetailMesh, 0); // 0 表示在最近距离使用高细节模型
},
undefined,
(error) => {
console.error("An error happened", error);
}
);

loader.load(
"path/to/your/medium-detail-model.gltf",
(gltf) => {
const mediumDetailMesh = gltf.scene.children[0];
lod.addLevel(mediumDetailMesh, 50); // 50 表示在中等距离使用中等细节模型
},
undefined,
(error) => {
console.error("An error happened", error);
}
);

loader.load(
"path/to/your/low-detail-model.gltf",
(gltf) => {
const lowDetailMesh = gltf.scene.children[0];
lod.addLevel(lowDetailMesh, 100); // 100 表示在远距离使用低细节模型
},
undefined,
(error) => {
console.error("An error happened", error);
}
);

scene.add(lod);

camera.position.z = 5;

const animate = () => {
requestAnimationFrame(animate);
lod.update(camera); // 更新 LOD
renderer.render(scene, camera);
};

// 启动动画循环
animate();

14.使用 indexdb 存取模型

  • CDN 加速,强制缓存
  • 本地的加载模型,304 已经代表进行了协商缓存
  • 使用 indexdb 进行缓存

IndexedDB 和 localStorage 都是浏览器提供的客户端存储解决方案,但它们在功能和使用场景上有显著的区别。

1
2
3
4
5
6
IndexedDB
1. 数据存储类型:IndexedDB 是一个低级API,用于存储大量结构化数据(包括文件/二进制数据)。它支持事务、索引和键范围查询。
2. 容量:IndexedDB 的存储容量通常比 localStorage 大得多,具体限制取决于浏览器和设备。
3. 异步操作:IndexedDB 的操作是异步的,这意味着它不会阻塞主线程,适合处理大量数据。
4. 数据类型:支持存储复杂的数据类型,包括对象、数组、文件等。
5. 使用场景:适用于需要存储大量数据、复杂查询和事务处理的应用,如离线应用、PWA(渐进式Web应用)等。
1
2
3
4
5
6
localStorage
1. 数据存储类型:localStorage 是一个简单的键值对存储,所有数据都以字符串形式存储。
2. 容量:localStorage 的存储容量通常较小(约5MB),具体限制取决于浏览器。
3. 同步操作:localStorage 的操作是同步的,这意味着它会阻塞主线程,不适合处理大量数据。
4. 数据类型:只能存储字符串,需要手动序列化和反序列化复杂数据类型(如JSON)。
5. 使用场景:适用于存储少量简单数据,如用户设置、会话信息等。
  • Three.js 可以与 IndexedDB 一起使用来存储和检索 3D 模型。这可以提高应用的性能,特别是在需要频繁加载相同模型的情况下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

const dbName = 'threejs-models';
const storeName = 'models';

const openDB = () => {
return new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(dbName, 1);

request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
db.createObjectStore(storeName, { keyPath: 'url' });
};

request.onsuccess = (event) => {
resolve((event.target as IDBOpenDBRequest).result);
};

request.onerror = (event) => {
reject((event.target as IDBOpenDBRequest).error);
};
});
};

const storeModel = async (url: string, arrayBuffer: ArrayBuffer) => {
const db = await openDB();
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
store.put({ url, model: arrayBuffer });
};

const loadModel = async (url: string) => {
const db = await openDB();
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
return new Promise<ArrayBuffer | null>((resolve, reject) => {
const request = store.get(url);
request.onsuccess = () => {
resolve(request.result ? request.result.model : null);
};
request.onerror = () => {
reject(request.error);
};
});
};

const loader = new GLTFLoader();
const modelUrl = 'path/to/your/model.gltf';

// 加载模型并存储到 IndexedDB
loader.load(modelUrl, async (gltf) => {
const arrayBuffer = await fetch(modelUrl).then(res => res.arrayBuffer());
await storeModel(modelUrl, arrayBuffer);
console.log('Model stored in IndexedDB');
});

// 从 IndexedDB 中加载模型
loadModel(modelUrl).then((arrayBuffer) => {
if (arrayBuffer) {
const blob = new Blob([arrayBuffer], { type: 'model/gltf-binary' });
const url = URL.createObjectURL(blob);
loader.load(url, (gltf) => {
scene.add(gltf.scene);
console.log('Model loaded from IndexedDB');
});
} else {
console.log('Model not found in IndexedDB');
}
});

15.离屏渲染

  • 什么是离屏渲染

    1.three 加载的模型一切皆对象,那么 three 经过一些列计算后,把模型渲染到屏幕上。那么离屏渲染就是不用渲染到屏幕上,因为渲染到屏幕(绘制)上一定会有一个过程叫 ➡ 光栅化

光栅化的作用,就是将 3d 模型进行各种变化,通过 2d 渲染到屏幕上

那么离屏 它就没有光栅化

那么到底什么是离屏渲染呢、

比如 b 页面是淘宝的双十一页面,那么进入双十一页面就必须先进入淘宝首页,那么我们可以在进入淘宝首页的时候就加载双十一页面,(只是加载,不渲染)这个就是离屏渲染。

再比如:视频导出功能,浏览器可以录制 canvas 的渲染结果,浏览器有 api 可以把它录制下来,然后转成视频;我要录制一个 10s 的过程,大概是 600 帧,(1s60 帧,10s600 帧),这个时候就会从 canvas 截取 600 个画面,(600 个图),然后进行拼接成视频,剪映的导出就是离屏渲染。背后就是 canvas 在运行,输出画面。但是并没有进行绘制,因为绘制就涉及到了光栅化。还有一点就是导出时用户不需要看到画面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import * as THREE from 'three';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';

class TextManager {
private scene: THREE.Scene;
private font: THREE.Font;
private renderer: THREE.WebGLRenderer;
private renderTarget: THREE.WebGLRenderTarget;

constructor(scene: THREE.Scene, font: THREE.Font, renderer: THREE.WebGLRenderer) {
this.scene = scene;
this.font = font;
this.renderer = renderer;
this.renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight);
}

createText(data: { text: string, position: THREE.Vector3, rotate: number }) {
const geometry = new TextGeometry(data.text, {
font: this.font,
size: 20,
height: 2,
});

const material = new THREE.ShaderMaterial({
vertexShader: `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0,1.0,1.0,1.0);
}
`,
});

const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(data.position);
mesh.rotateY(data.rotate);

// 创建一个临时场景用于离屏渲染
const tempScene = new THREE.Scene();
tempScene.add(mesh);

// 使用 WebGLRenderTarget 进行离屏渲染
this.renderer.setRenderTarget(this.renderTarget);
this.renderer.render(tempScene, this.scene.children[0] as THREE.Camera);
this.renderer.setRenderTarget(null);

//切换页面了 将渲染结果添加到主场景中
this.scene.add(mesh);
}
}

16.场景阴影

  • 阴影在需要时打开
1
2
3
4
5
6
if (data.castShadow) {
mesh.castShadow = true;
}
if (data.receiveShadow) {
mesh.receiveShadow = true;
}

17.降低渲染帧率

  • requestAnimationFrame 它可以获取到屏幕的刷新率,settimeout 不准确

  • 正常用户 80% 60 帧

    根据用户设备性能,做判断。

1
2
3
4
5
6
7
8
9
10
11
const start = () => {
controls.update();

city.start(clock.getDelta());
// 渲染场景
renderer.render(scene, camera)

requestAnimationFrame(start)
}

start();

18.按需导入 Threejs 依赖,减少编译之后的包体积

  • Tree shaking 优化

比如 1m 优化到 600k

1
2
3
import { WebGLRenderer, Scene, PerspectiveCamera, Color, Clock } from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { City } from "./City"; // 假设 City 是你自定义的模块

19.视锥体剔除

  • 视锥体剔除(Frustum Culling)是一种优化技术,用于在渲染场景时剔除那些不在相机视锥体内的物体,从而减少渲染负担。Three.js 内置了视锥体剔除功能,但你需要确保物体的 frustumCulled 属性设置为 true(默认值)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { City } from './City'; // 假设 City 是你自定义的模块

// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置像素比
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// 设置场景颜色
renderer.setClearColor(new THREE.Color(0x000000), 1);
document.body.appendChild(renderer.domElement);

// 创建场景
const scene = new THREE.Scene();

// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1, 5);

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);

// 实例化城市模型
const city = new City(scene, camera, controls);
const clock = new THREE.Clock();

// 确保所有物体启用视锥体剔除
scene.traverse((object) => {
if (object instanceof THREE.Mesh) {
object.frustumCulled = true;
}
});

const start = () => {
controls.update();
city.start(clock.getDelta());
// 渲染场景
renderer.render(scene, camera);
requestAnimationFrame(start);
};

start();

window.addEventListener('resize', () => {
// 更新宽高比
camera.aspect = window.innerWidth / window.innerHeight;
// 更新相机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器的尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
});
使用搜索:谷歌必应百度