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 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>

结果如下:

在笔者的电脑上只有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 onMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
}
document.addEventListener('mousemove', onMouseMove);

在笔者的电脑上有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)

Three.js使用InstancedMesh实现性能优化的更多相关文章

  1. js查重去重性能优化心得

    概述 今天产品反映有个5000条数据的页面的保存按钮很慢,查看代码看到是因为点击保存按钮之后,进行了查重操作,而查重操作是用2个for循环完成了,时间复杂度是O(n^2).没办法,只能想办法优化一下了 ...

  2. JS日期格式化函数性能优化篇

    最近开发的软件中需要用到日志功能,其中有一个重要功能是显示日期和时间.于是网上搜了一把,搜到大量的日期格式化函数,不过比较了下,感觉代码都不够优雅,而且性能都不给力.对线上一些代码进行了评测,以下是一 ...

  3. js怎么动态加载js文件(JavaScript性能优化篇)

    下面介绍一种JS代码优化的一个小技巧,通过动态加载引入js外部文件来提高网页加载速度 [基本优化] 将所有需要的<script>标签都放在</body>之前,确保脚本执行之前完 ...

  4. [js高手之路]性能优化技巧 - 缓存与函数重载实战

    所谓缓存,通俗点讲就是把已经做过的事情结果先暂时存起来,下次再做同样的事情,不用再重新去做,只要把之前的存的结果拿出来用即可,很明显大大提升了效率.他的应用场景非常广泛.如: 1.缓存ajax结果,大 ...

  5. babel-polyfill使用与性能优化

    文章首发于笔者的个人博客 文章概览 本文主要内容包括:什么是babel-polyfill,如何使用,如何通过按需加载进行性能优化. 本文所有例子可以在 笔者的github 找到. 什么是babel-p ...

  6. js性能优化-事件委托

    js性能优化-事件委托 考虑一个列表,在li的数量非常少的时候,为每一个li添加事件侦听当然不会存在太多性能方面的问题,但是当列表非常的长,长到上百上千甚至上万的时候(当然只是一个解释,实际工作中很少 ...

  7. Web性能优化-合并js与css,减少请求

    Web性能优化已经是老生常谈的话题了, 不过笔者也一直没放在心上,主要的原因还是项目的用户量以及页面中的js,css文件就那几个,感觉没什么优化的.人总要进步的嘛,最近在被angularjs吸引着,也 ...

  8. Web前端性能优化教程07:精简JS 移除重复脚本

    本文是Web前端性能优化系列文章中的第七篇,主要讲述内容:精简Javascript代码,以及移出重复脚本.完整教程可查看:  一.精简javascript 基础知识 精简:从javascript代码中 ...

  9. 我所经历的JS性能优化

    转自http://www.cnblogs.com/koking/archive/2011/10/17/2215665.html 折腾了好几天,纠结了好几天,郁闷了好几天,终于在今天可以释怀了,留下其中 ...

  10. JS性能优化笔记搜索整理

    通过网上查找资料了解关于性能优化方面的内容,现简单整理,仅供大家在优化的过程中参考使用,如有什么问题请及时提出,再做出相应的补充修改. 一. 让代码简洁:一些简略的表达方式也会产生很好的优化 eg:x ...

随机推荐

  1. 虚拟dom中key的作用以及用index作为key引发的问题

  2. 各种远程工具通过ssh连接服务器

    开头 最近遇到一个新的连接方式,不能使用日常的本地通过账号连接,要通过私钥和公钥的连接方式,然后连接到服务器之后才能连接到数据库.因为之前没试过这种连接方式,所以很多工具有不同的连接方式.所以现在就记 ...

  3. 2022-10-09:我们给出了一个(轴对齐的)二维矩形列表 rectangles 。 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐

    2022-10-09:我们给出了一个(轴对齐的)二维矩形列表 rectangles . 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐标 ...

  4. 2021-07-13:恢复二叉搜索树。给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出

    2021-07-13:恢复二叉搜索树.给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换.请在不改变其结构的情况下,恢复这棵树.进阶:使用 O(n) 空间复杂度的解法很容易实现.你能想出 ...

  5. Mysql- DDL/DML/DQL/DCL 数据库基本操作语句(持续更新中)

    Mysql基本语法 前言: 在测试项目中经常需要使用到简单的Mysql语句,但是不知道语句结构是什么,经常在百度查来查去: 以下就是总结Mysql常用的基础操作语句: 只需要执行从创建开始执行示例中的 ...

  6. hasattr()、getattr()、setattr()函数简介

    hasattr(object, name) 判断object对象中是否存在name属性,当然对于python的对象而言,属性包含变量和方法:有则返回True,没有则返回False:需要注意的是name ...

  7. 【GiraKoo】C++中static关键字的作用

    C++中static关键字的作用 在程序中良好的使用static,const,private等关键字,对于代码的健壮性有很大的帮助. 本文介绍的就是C++中static关键字的一些常见用法与区别.适合 ...

  8. 7-8 估值一亿的AI核心代码

    题目描述: 以上图片来自新浪微博. 本题要求你实现一个稍微更值钱一点的 AI 英文问答程序,规则是: 无论用户说什么,首先把对方说的话在一行中原样打印出来: 消除原文中多余空格:把相邻单词间的多个空格 ...

  9. drf——jwt

    jwt原理 使用jwt认证和使用session认证的区别 三段式 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibm ...

  10. ssh,socat端口转发

    ssh隧道 我们将要研究的第一个协议是SSH,因为它已经内置了通过SSH隧道进行端口转发的功能.虽然SSH曾经是与Linux系统相关联的协议,但现在Windows默认安装了OpenSSH客户端,因此您 ...