优化Three.js性能:提升WebGL渲染效率

LOD技术

废话

当渲染顶点信息时,WebGL 将这些顶点信息发送到 GPU 中,并使用着色器程序来处理这些顶点信息并最终将它们渲染到屏幕上。在渲染过程中,GPU 对每个顶点执行各种操作,如变换、光照计算、纹理映射等,然后将结果输出到屏幕上。这就意味着,顶点信息越多对于GPU的压力越大,threejs官网提供# 多细节层次(LOD,Levels of Detail)用于优化这种情形,主要原理是通过addLevel添加一个层级,每个层级都对应一个几何体或者模型,对应不同的层级显示不同细节的几何体(如图)。随着相机与模型的距离变化,LOD 对象会自动切换到最适合的模型细节级别,以在远处绘制较简单的模型,在近处绘制更详细的模型。

lod需要动态更新相机,可以在每一帧中调用 LOD 对象的 update(camera) 方法,以更新当前相机位置并根据相机距离选择适当的模型细节级别

示意图

代码

代码中分别创建了一个细分数为8和细分数为32的球几何体,在距离为100以上的时候,加载lowDetailMesh模型,低于100的时候加载highDetailMesh模型

typescript 复制代码
const initLod = () => {
    var lowDetailGeometry = new THREE.SphereGeometry(10, 8, 8);
    var highDetailGeometry = new THREE.SphereGeometry(10, 32, 32);
    var lowDetailMesh = new THREE.Mesh(lowDetailGeometry, material);
    var highDetailMesh = new THREE.Mesh(highDetailGeometry, material);
    // 距离50的时候加载lowDetailMesh模型
    lod.addLevel(lowDetailMesh, 100);
    // 距离10的时候加载highDetailMesh模型
    lod.addLevel(highDetailMesh, 50);

}

render方法中计算出相机距离var distance = camera.position.distanceTo(lod.position);,并更新lodlod.update(camera);

ts 复制代码
let render = () => {
    controls.update()
    camera.updateProjectionMatrix()

    // 获取相机距离
    var distance = camera.position.distanceTo(lod.position);
    if (cameraDistanceDom) {
        cameraDistanceDom.innerText = `距离:${Math.floor(distance)}`
    }
    renderer.render(scene, camera)
    stats.update()
    // 更新lod
    lod.update(camera);

}

性能监控

ts 复制代码
let count = 10
for (let x = -count; x < count; x++) {
    for (let z = -count; z < count; z++) {
        var highDetailGeometry = new THREE.SphereGeometry(1, 32, 32);
        var highDetailMesh = new THREE.Mesh(highDetailGeometry, material);
        highDetailMesh.position.set(x * 2, 0, z * 2)
        scene.add(highDetailMesh)
    }
}

以上代码直接创建了大概400个细分数为32的球体,性能监控28fps,降了接近一半,接下来改造一下,每一层一个等级,并换一个颜色,不管位置是什么,fps一直上不去,下面我们对代码进行改变,创建3个组,分别对应三个级别,每个级别内部的球体分段数不同,注意观察动图的左上角,从近距离的fps27到远距离fps60

ts 复制代码
 let count = 16
for (let level = 1; level < 4; level++) {
    const group = new THREE.Group()
    for (let x = -count; x < count; x++) {
        // 每行换一个颜色的材质
        let material = new THREE.MeshLambertMaterial({
            wireframe: true,
            color: Math.random() * 0xffffff
        })
        // 根据不同的数量计算不同的细分数
        for (let z = -count; z < count; z++) {
            const pos = new THREE.Vector3(x * 4, 0, z * 4)

            var geometry = new THREE.SphereGeometry(1, level * 10, level * 10);
            var mesh = new THREE.Mesh(geometry, material);
            mesh.position.copy(pos);
            group.add(mesh)
            // scene.add(mesh)

        }
    }
    lod.addLevel(group, 200 - level * 50)

}

方案总结

通过计算相机的距离,加载不同等级的几何体(模型)或者贴图,从而降低gpu渲染压力

阶段代码

gitee性能优化lod

空气墙

废话

在threejs中检测物体碰撞一般使用 # 光线投射Raycaster 对物体之间进行检测,Raycasting 是一种从摄像机位置沿着特定方向发射光线,并检查光线是否与场景中的物体相交的技术。在这种情况下,我们可以使用 Raycaster 对物体之间进行碰撞检测。被检测模型越复杂,检测的循环次数越多。主要优化的内容就是将复杂的图形简单化,或者直接用同等大小的空气墙进行检测。

示意图

比如这个人物模型,在进行fps游戏的时候,渲染展示的模型可能有根根手指头,眉眼,皮肤等信息,但是子弹通过时候进行的检测并不需要那么细节,这时候就需要3d设计师提供同等规格的空气墙进行检测,比如胳膊可以做成一个立方体,头可以做成一个椭球体。

下面我们用一个例子来测试一下,首先加载一个体积稍微大一点的地球模型

代码

ts 复制代码
loadGltf('./model/scene.gltf').then((res: any)=>{
    scene.add(res.scene)
})

单纯加载地图,fps值是60,表示性能可以的。那么接下增加个2d文字,众所周知,css2drender渲染的内容是和3d内容不在同一个图层,所以接下来要用射线检测,判断地球是否将文字遮挡,如果是遮挡状态不显示文字,非遮挡状态显示文字。

常规检测

ts 复制代码
loadGltf('./model/scene.gltf').then((res: any) => {
        const earth = res.scene
        earthGroup.add(earth)
      
        controls.addEventListener('change', () => {
            textGroup.traverse((text) => {
                const pos = new THREE.Vector3();
                text.getWorldPosition(pos)
                const hide = pointRay(camera.position, pos, earthGroup.children);
                console.log('hide', hide);
                text.visible = !hide
            })



        })
    })

pointRay方法第三个参数就是被检测目标,目前是用地图模型,相对比较卡顿,fps接近28,

空气墙检测

接下来给地图创建一个空气墙,相对没那么精细,我这里用的分段数是16,如果性能还下不去,可以再低一点

ts 复制代码
const box3 = new THREE.Box3()
box3.setFromObject(earth);
const size = new THREE.Vector3()
const center = new THREE.Vector3();
// 获取地球模型尺寸
box3.getSize(size);
// 获取地球中心点
box3.getCenter(center)
// 通过地球模型尺寸创建一个同等大小的球体
var geometry = new THREE.SphereGeometry(size.y / 2, 16, 16);
let material = new THREE.MeshLambertMaterial({
    wireframe: true,
    color: '#ff0000'
})
var mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(center)
scene.add(mesh)

改造一下刚才的检测方法const hide = pointRay(camera.position, pos, [mesh]);,将制作的空气墙作为检测目标,明显能够感受到fps值有所提升

方案总结

相对球形和立方体我们可以自己做一个模拟的空气墙,如果模型比较复杂,而且有条件的话,可以让3d设计师提供不同规格的模型空气墙,也可以在不同性能的设备都做兼容。

阶段代码

空气墙优化方案

无残留删除元素

我这里根据一些其他网站整理的一个无残留删除元素的工具类 ResourceTracker切割logo/js/ResourceTracker.ts

这用于跟踪和管理 Three.js 中的资源对象,避免内存泄露,和冗余的内存占用,track(resource)这个方法用于添加要跟踪的资源对象,dispose方法对已跟踪的资源进行释放,untrack取消队列,调用dispose时不会释放已经取消队列的元素。具体可以参考 # THREE.JS------让你的logo切割出高级感

具体使用方式

ts 复制代码
// 初始化类
const logoLineResMgr = new ResourceTracker();
const track = logoLineResMgr.track.bind(logoLineResMgr)
ts 复制代码
// 创建新的元素
const logoLineGroup = track(new THREE.Group());
ts 复制代码
用户销毁
logoLineResMgr.dispose();

完整代码地址

gitee

历史文章

# threejs进阶 通过json数据创建立体地图并实现下钻及返回 # threejs基础------判断物体遮挡方案

# threejs开发可视化数字城市效果

# threejs渲染高级感可视化涡轮模型

# 写一个高德地图巡航功能的小DEMO

# threejs 打造 world.ipanda.com 同款3D首页

# three.js------物理引擎

# three.js------镜头跟踪

# threejs 笔记 03 ------ 轨道控制器

相关推荐
FungLeo14 分钟前
node 后端和浏览器前端,有关 RSA 非对称加密的完整实践, 前后端匹配的代码演示
前端·非对称加密·rsa 加密·node 后端
不灭锦鲤24 分钟前
xss-labs靶场第11-14关基础详解
前端·xss
不是吧这都有重名1 小时前
利用systemd启动部署在服务器上的web应用
运维·服务器·前端
霸王蟹1 小时前
React中巧妙使用异步组件Suspense优化页面性能。
前端·笔记·学习·react.js·前端框架
Maỿbe1 小时前
利用html制作简历网页和求职信息网页
前端·html
森叶1 小时前
Electron 主进程中使用Worker来创建不同间隔的定时器实现过程
前端·javascript·electron
霸王蟹1 小时前
React 19 中的useRef得到了进一步加强。
前端·javascript·笔记·学习·react.js·ts
霸王蟹1 小时前
React 19版本refs也支持清理函数了。
前端·javascript·笔记·react.js·前端框架·ts
川石课堂软件测试2 小时前
涨薪技术|0到1学会性能测试第65课-SQL捕获阻塞事件
数据库·sql·功能测试·oracle·性能优化·单元测试·tomcat
繁依Fanyi2 小时前
ColorAid —— 一个面向设计师的色盲模拟工具开发记
开发语言·前端·vue.js·编辑器·codebuddy首席试玩官