优化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 ------ 轨道控制器

相关推荐
吕彬-前端30 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱33 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai42 分钟前
uniapp
前端·javascript·vue.js·uni-app
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
萌面小侠Plus2 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205872 小时前
web端手机录音
前端