首页 > 其他分享 >Three.js使用InstancedMesh实现性能优化

Three.js使用InstancedMesh实现性能优化

时间:2023-07-28 10:55:46浏览次数:45  
标签:几何体 const three Three InstancedMesh new js THREE

1. 引言

有这么一种场景:需要渲染一座桥,桥有很多桥柱,桥柱除了位置与倾斜角度不完全相同外,其他均相同,由于桥柱数量很大,使用three.js绘制较为卡顿,如何优化?注意,要求后续能选中某个桥柱

2. 概念

2.1 合并几何体

three.js官方教程里提到,大量对象的优化 - three.js manual (threejs.org),使用合并几何体

为什么合并几何体能优化绘制大量对象时的性能呢?

这得引出一个概念:绘制调用(draw call)

绘制调用(draw call)是指渲染引擎向GPU发送绘制命令的过程,每个绘制调用都会告诉GPU绘制一个或多个三维物体或几何体

在图形渲染中,绘制调用的数量对性能有很大影响,较少的绘制调用通常意味着更高的性能,因为GPU在处理绘制调用时需要切换上下文和状态,这会导致一定的开销

在three.js中,由于绘制一个几何体需要调用一次draw call,绘制很多几何体就很消耗性能,所以合并多个几何体为一个几何体能减少draw call,从而实现绘制性能优化

合并几何体会有一个突出的问题:无法单独选择其中某个几何体

由于多个几何体合并为一个几何体,所以已经无法选择原来的某个几何体,即无法拾取单个几何体

考虑到后续需要能选中桥柱,这个方案舍弃

2.2 InstancedMesh

three.js官方API文档是这样解释:

实例化网格(InstancedMesh),一种具有实例化渲染支持的特殊版本的Mesh。你可以使用 InstancedMesh 来渲染大量具有相同几何体与材质、但具有不同世界变换的物体。 使用 InstancedMesh 将帮助你减少 draw call 的数量,从而提升你应用程序的整体渲染性能

桥柱除了位置与倾斜角度不完全相同外,其他均相同,符合InstancedMesh的要求,同时InstancedMesh是可以选择单个物体的,可以参考这个官方示例:three.js examples (threejs.org)

关于InstancedMesh,更为详细的解释可参考官方文档:InstancedMesh – three.js docs (threejs.org)

综上,笔者选用InstancedMesh来进行桥柱渲染优化,本文记述在three.js中使用InstancedMesh来实现绘制大量几何体的性能优化

3. 初始情况

初始情况下使用多个几何体来加载桥柱,其实就是多个圆柱体,数量为10980

示例代码如下:

<!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 onm ouseMove(event) {
      event.preventDefault();
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
    }
    document.addEventListener('mousemove', onm ouseMove);
  </script>
</body>

</html>

结果如下:

image-20230727171954824

在笔者的电脑上只有20FPS,拾取功能(选择单个柱子)正常

4. InstanceMesh优化

InstanceMesh在概念上可以理解为这是一组几何体,只需根据instance id即可在这一组InstanceMesh上找到这个几何体,所以InstanceMesh的使用方法主要就是根据InstanceMesh和instance id来确定选择的是那个几何体,从而进行位置变换、设置颜色等

更为详细的InstanceMesh使用方法可参考官方文档和示例:

笔者将上述代码修改为使用InstanceMesh的代码,主体代码如下:

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 onm ouseMove(event) {
    event.preventDefault();
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
}
document.addEventListener('mousemove', onm ouseMove);

image-20230727173402116

在笔者的电脑上有60FPS,拾取功能(选择单个柱子)正常

5. 参考

[1] 大量对象的优化 - three.js manual (threejs.org)

[2] three.js 性能优化的几种方法 - 可爱的黑精灵 - 博客园 (cnblogs.com)

[3] InstancedMesh – three.js docs (threejs.org)

[4] three.js/examples/webgl_instancing_raycast.html at master · mrdoob/three.js (github.com)

[5] three.js examples (threejs.org)

标签:几何体,const,three,Three,InstancedMesh,new,js,THREE
From: https://www.cnblogs.com/jiujiubashiyi/p/17585661.html

相关文章

  • Ubuntu apt 安装 nodejs
    安装nodejs在windows下,都是到Nodejs官方网站上下载压缩包。然后在环境变量中配置Nodejs的环境变量,但是到了Ubuntu下这种情况可能会改变,因为有强大的APT包管理器。所以我们要做的就是两件事情,第一找到资源仓库,第二安装所需要的资源。Step1Nodejs的APT仓库#先安装Python的软件......
  • JS直接将页面的内容作为excel下载
     做个笔记,后续自己可以看看,将页面的一个Table直接输出为excel文件,亲测有用。 //下载excelfunctiondownloadExcel(){varuri='data:application/vnd.ms-excel;base64,';vartemplate=`<htmlxmlns:o="urn:schemas-microsoft-com:o......
  • 引入外部文件(图片、js等)出现403 forbidden的问题
    引入外部文件(图片、js等)出现403forbidden的问题报403错误则是访问被拒绝浏览器的防盗链机制当你的项目和需要访问的地址不在同一个域内,这时浏览器的防盗链机制就发挥作用了。其中防盗链是利用HTTPheader中的referer来实现的。当浏览器向服务器发送请求时会带上referer,......
  • Java 对json排序
    Java对JSON排序在日常的开发中,我们经常需要将JSON数据进行排序,以满足业务需求或者提高查询效率。本文将介绍如何使用Java对JSON数据进行排序,并提供示例代码帮助理解。什么是JSON?JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式,常用于前后端数据传输。它以......
  • Still waiting to schedule task ‘nodejs-html5’ is offline
    运行任务时等待节点在线的解决方案在进行分布式任务调度时,我们可能会遇到一个问题:当一个节点不在线时,我们如何等待节点上线再执行任务?本文将介绍如何使用Node.js编写代码实现此功能。问题描述假设我们有一个任务调度程序,使用Node.js编写。在这个任务调度程序中,我们有一个名为nod......
  • 使用@JsonFormat引起的时间比正常时间慢8小时解决方法
    转:使用@JsonFormat引起的时间比正常时间慢8小时解决方法 @JsonFormat,默认情况下timeZone为GMT(即标准时区),所以会造成输出少8小时。改为北京时间,方式如下:@JsonFormat(pattern="yyyy-MM-ddHH:mm:ss",timezone="GMT+8")      ......
  • 16. 最接近的三数之和(threeSumClosest)
    给你一个长度为n的整数数组nums和一个目标值target。请你从nums中选出三个整数,使它们的和与target最接近。返回这三个数的和。假定每组输入只存在恰好一个解。 示例1:输入:nums=[-1,2,1,-4],target=1输出:2解释:与target最接近的和是2(-1+2+1=2)。示......
  • jsx语法
    JSX语法JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScriptXML,因为看起就是一段XML语法;它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);......
  • n stable报错curl: (7) Failed to connect to nodejs.org port 443: Connection refus
    nstable报错curl:(7)Failedtoconnecttonodejs.orgport443:ConnectionrefusedError:failedtodownloadversionindex(https://nodejs.org/dist/index.tab)使用淘宝源exportN_NODE_MIRROR=https://npm.taobao.org/mirrors/nodenstable......
  • 直播商城源码,js判断上传图片格式类型、尺寸大小
    直播商城源码,js判断上传图片格式类型、尺寸大小//判断图片类型varf=document.getElementById("File1").value;if(f==""){ alert("请上传图片");returnfalse;}else{if(!/\.(gif|jpg|jpeg|png|GIF|JPG|PNG)$/.test(f)){alert("图片类型必须是.gif,jpeg,jpg,png中的一种")retu......