Three.js usa InstancedMesh para lograr la optimización del rendimiento

1. Introducción

Existe tal escenario: es necesario renderizar un puente. El puente tiene muchos pilares. Excepto por las diferentes posiciones y ángulos de inclinación de los pilares del puente, todo lo demás es igual. Debido a la gran cantidad de pilares del puente, es relativamente lento para dibujar usando three.js. ¿Cómo optimizarlo? Tenga en cuenta que será necesario seleccionar un determinado pilar del puente más adelante.

2. Concepto

2.1 Fusionar geometría

El tutorial oficial de three.js menciona la optimización de una gran cantidad de objetos: manual de three.js (tresjs.org) , utilizando geometrías fusionadas

¿Por qué la combinación de geometría optimiza el rendimiento al dibujar una gran cantidad de objetos?

Esto lleva a un concepto: llamada de empate (llamada de empate)

Una llamada de dibujo se refiere al proceso en el que el motor de renderizado envía comandos de dibujo a la GPU. Cada llamada de dibujo le dice a la GPU que dibuje uno o más objetos o geometrías tridimensionales.

En la representación de gráficos, la cantidad de llamadas de dibujo tiene un gran impacto en el rendimiento. Menos llamadas de dibujo generalmente significan un mayor rendimiento, porque la GPU necesita cambiar contextos y estados al procesar llamadas de dibujo, lo que causará una cierta sobrecarga.

En three.js, dado que dibujar una geometría requiere una llamada de dibujo, dibujar muchas geometrías consume rendimiento, por lo que fusionar varias geometrías en una geometría puede reducir las llamadas de dibujo, optimizando así el rendimiento del dibujo.

Existe un problema importante al fusionar geometrías: una de las geometrías no se puede seleccionar individualmente.

Dado que se fusionan varias geometrías en una sola, ya no es posible seleccionar una geometría original, es decir, no se puede seleccionar una sola geometría.

Teniendo en cuenta la necesidad posterior de poder seleccionar los pilares del puente, se abandonó este plan.

2.2 Malla instanciada

La documentación oficial de la API de three.js explica esto:

InstancedMesh, una versión especial de Mesh con soporte de renderizado instanciado . Puede utilizar InstancedMesh para representar una gran cantidad de objetos con la misma geometría y materiales, pero diferentes transformaciones del mundo. El uso de InstancedMesh lo ayudará a reducir la cantidad de llamadas de sorteo, mejorando así el rendimiento general de renderizado de su aplicación.

Excepto por las diferentes posiciones y ángulos de inclinación, las columnas del puente son todas iguales, lo que cumple con los requisitos de InstancedMesh. Al mismo tiempo, InstancedMesh le permite seleccionar un solo objeto. Puede consultar este ejemplo oficial: ejemplos de three.js (tresjs.org )

Con respecto a InstancedMesh, se puede encontrar una explicación más detallada en la documentación oficial: InstancedMesh – three.js docs (tresjs.org)

En resumen, el autor elige InstancedMesh para optimizar la representación de las columnas del puente. Este artículo describe el uso de InstancedMesh en three.js para optimizar el rendimiento al dibujar una gran cantidad de geometrías.

3. Situación inicial

En el caso inicial se utilizan múltiples geometrías para cargar las columnas del puente, que en realidad son múltiples cilindros, el número es 10980

El código de muestra es el siguiente:

 
 
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,
body,
canvas {
height: 100%;
width: 100%;
margin: 0;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three/build/three.module.js",
"three/addons/": "https://unpkg.com/three/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js'
const scene = new THREE.Scene();
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(1, 1);
let mesh;
const color = new THREE.Color();
const white = new THREE.Color().setHex(0xffffff);
// 创建性能监视器
let stats = new Stats();
// 将监视器添加到页面中
document.body.appendChild(stats.domElement)
const canvas = document.querySelector('#canvas');
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100000);
camera.position.z = 5;
camera.position.y = 60;
camera.position.x = -1500;
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#canvas'),
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight, false)
const controls = new OrbitControls(camera, renderer.domElement);
function animate() {
// 更新帧数
stats.update()
if (scene.children.length > 0) {
raycaster.setFromCamera(mouse, camera);
const intersections = raycaster.intersectObject(scene, true);
if (intersections.length > 0) {
// 获取第一个相交的物体
const intersectedObject = intersections[0].object;
// 更新物体的颜色
intersectedObject.material.color.set(0xff0000); // 设置为红色
}
}
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
let count = 0
let matrixList = []
fetch("./数据.json").then(res => res.json()).then(res => {
const name = Object.keys(res)
for (let index = 0; index < 60; index++) {
name.filter(item => item.includes("直立桩基")).forEach(item => {
res[item].forEach(element => {
const geometry = new THREE.CylinderGeometry(element.diameter / 2000, element.diameter / 2000, (element.height - element.depth) / 1000, 32);
const material = new THREE.MeshBasicMaterial({ color: 0xffffff });
const cylinder = new THREE.Mesh(geometry, material);
const originalHeight = cylinder.geometry.parameters.height;
cylinder.geometry.translate(0, -originalHeight / 2, 0);
cylinder.position.set(element.x / 1000 * Math.random(), (element.z + element.height) / 1000, element.y / 1000)
scene.add(cylinder);
count++
});
})
}
console.log(count)
})
function onMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
}
document.addEventListener('mousemove', onMouseMove);
</script>
</body>
</html>

El resultado es el siguiente:

imagen-20230727171954824

En mi computadora, solo hay 20 FPS y la función de selección (seleccionar un solo pilar) es normal.

4. Optimización de instancia de malla

InstanceMesh puede entenderse conceptualmente como un grupo de geometrías. Puede encontrar la geometría en este grupo de InstanceMesh simplemente basándose en la identificación de la instancia. Por lo tanto, el método principal para usar InstanceMesh es determinar qué geometría seleccionar en función de la InstanceMesh y la identificación de la instancia. .Para realizar cambios de posición, establecer colores, etc.

Para obtener instrucciones más detalladas sobre cómo utilizar InstanceMesh, consulte la documentación oficial y los ejemplos:

El autor modificó el código anterior para usar InstanceMesh. El código principal es el siguiente:

 
 
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js'
const scene = new THREE.Scene();
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(1, 1);
let mesh;
const color = new THREE.Color();
const white = new THREE.Color().setHex(0xffffff);
// 创建性能监视器
let stats = new Stats();
// 将监视器添加到页面中
document.body.appendChild(stats.domElement)
const canvas = document.querySelector('#canvas');
const camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100000);
camera.position.z = 5;
camera.position.y = 60;
camera.position.x = -1500;
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#canvas'),
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight, false)
const controls = new OrbitControls(camera, renderer.domElement);
function animate() {
// 更新帧数
stats.update()
if (mesh) {
raycaster.setFromCamera(mouse, camera);
const intersection = raycaster.intersectObject(mesh);
if (intersection.length > 0) {
const instanceId = intersection[0].instanceId;
console.log(instanceId)
mesh.setColorAt(instanceId, new THREE.Color(0xff0000));
mesh.instanceColor.needsUpdate = true;
}
}
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
let count = 0
let matrixList = []
fetch("./数据.json").then(res => res.json()).then(res => {
const name = Object.keys(res)
for (let index = 0; index < 60; index++) {
name.filter(item => item.includes("直立桩基")).forEach(item => {
res[item].forEach(element => {
count++
matrixList.push(new THREE.Matrix4().makeTranslation(element.x / 1000 * Math.random(), (element.z + element.height) / 1000, element.y / 1000))
});
})
}
console.log(count)
const element = {
diameter: 1200,
depth: 72000
}
const geometry = new THREE.CylinderGeometry(element.diameter / 2000, element.diameter / 2000, element.depth / 1000, 32);
const material = new THREE.MeshBasicMaterial({ color: 0xffffff });
mesh = new THREE.InstancedMesh(geometry, material, count);
for (let i = 0; i < count; i++) {
mesh.setColorAt(i, color);
mesh.setMatrixAt(i, matrixList[i]);
}
scene.add(mesh);
})
function onMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
}
document.addEventListener('mousemove', onMouseMove);

imagen-20230727173402116

Hay 60 FPS en la computadora del autor y la función de selección (seleccionar un solo pilar) es normal

Supongo que te gusta

Origin blog.csdn.net/2301_78834737/article/details/132004611
Recomendado
Clasificación