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

相关推荐
y先森3 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
虾球xz5 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇5 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒5 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员6 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐6 小时前
前端图像处理(一)
前端