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渲染压力
阶段代码
空气墙
废话
在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();
完整代码地址
历史文章
# threejs进阶 通过json数据创建立体地图并实现下钻及返回 # threejs基础------判断物体遮挡方案