【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)

作者 : admin 本文共7822个字,预计阅读时间需要20分钟 发布时间: 2024-06-16 共1人阅读

贴图纹理材质光影对内存性能的影响

  • 学习ThreeJS的捷径
  • 烘培模型简介
  • 如何优化烘培模型
    • 贴图处理
    • 贴图质量切换
  • 为什么光源要限制数量
    • 阴影质量的影响
    • 阴影本身也可以理解为是一种贴图

学习ThreeJS的捷径

本段内容会写在0篇以外所有的,本人所编写的Threejs教程中

对,学习ThreeJS有捷径
当你有哪个函数不懂的时候,第一时间去翻一翻文档
当你有哪个效果不会做的时候,第一时间去翻一翻所有的案例,也许就能找到你想要的效果
最重要的一点,就是,绝对不要怕问问题,越怕找找别人问题,你的问题就会被拖的越久

如果你确定要走WebGL/ThreeJS的开发者路线的话,以下行为可以让你更快的学习ThreeJS

  1. 没事就把所有的文档翻一遍,哪怕看不懂,也要留个印象,至少要知道Threejs有什么
  2. 没事多看看案例效果,当你记忆的案例效果足够多时,下次再遇到相似问题时,你就有可能第一时间来找对应的案例,能更快解决你自己的问题
  3. 上述案例不只是官网的案例,郭隆邦技术博客,跃焱邵隼,暮志未晚等站点均有不少优质案例,记得一并收藏
    http://www.yanhuangxueyuan.com/ 郭隆邦技术博客
    https://www.wellyyss.cn/ 跃焱邵隼
    http://www.wjceo.com/ 暮志未晚(暮老的站点暂时挂了,请查阅他之前的threejs相关文档)
    暮老的csdn首页
    这三个站点是我最常逛的站点,推荐各位有事没事逛一下,看看他们的案例和写法思路,绝对没坏处

烘培模型简介

我们以threejs官方开发包的文件举例,在开发包three/examples/models/obj/cerberus中,有这样一个OBJ格式的文件,这个模型也是之前在材质篇中使用到的模型

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图
我们来查看一下文件大小【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(1)

在前面的灯光阴影材质篇,我们都有讲过,越好看越逼真的效果,都是建模师烘培出来的,用贴图贴出来的,上述模型,就是这样的模型,各位可以看一下这个模型实际贴出来的效果

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(2)

Threeejs官方使用了这个模型的demo地址

这里的模型,笔者称为烘培模型

烘培模型的特点,一般是,模型效果非常逼真,模型与贴图的文件大小占比有明显的差距,且模型占比较小

如何优化烘培模型

有很多人刚好是遇到了烘培模型,但是用上一篇文章并不能解决卡顿问题,这里我们来介绍如何优化烘培模型

首先我们把模型加载进来,并贴好图
【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(3)

    function addMesh(){
        let loader = new OBJLoader();
        let textureLoader = new THREE.TextureLoader();//创建纹理加载器
        loader.load('./model/Cerberus.obj',obj=>{
            scene.add(obj);

            let map = textureLoader.load('./model/Cerberus_A.jpg'); //颜色贴图
            let normalMap = textureLoader.load('./model/Cerberus_N.jpg'); //法线贴图
            let roughnessMap = textureLoader.load('./model/Cerberus_R.jpg'); //粗糙度贴图
            let metalnessMap = textureLoader.load('./model/Cerberus_RM.jpg');//金属度贴图

            //处理贴图的常规操作
            map.wrapT = map.wrapS = THREE.RepeatWrapping;
            normalMap.wrapT = normalMap.wrapS = THREE.RepeatWrapping;
            roughnessMap.wrapT = roughnessMap.wrapS = THREE.RepeatWrapping;
            metalnessMap.wrapT = metalnessMap.wrapS = THREE.RepeatWrapping;

            //根据上面的所有贴图创建材质
            let material = new THREE.MeshStandardMaterial({
                transparent:true,
                side:THREE.DoubleSide,
                map:map,
                roughnessMap:roughnessMap,
                metalnessMap:metalnessMap,
                normalMap:normalMap
            });

            obj.traverse(object=>{
                if(object.isMesh){
                    let oldMaterial = object.material;
                    object.material = material;
                    oldMaterial.dispose();
                }
            })
        })
    }

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(4)
因为本人是使用Chrome来编写文章的,所以避免干扰,本次我这里使用了Edge来统计内存,Edge本质上是套皮的Chrome,所以不会对结果有大的影响

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(5)
稳定下来后,内存降低到了45976K

这个是我们初始的内存状态

贴图处理

我们先大概看一下5张贴图的分辨率
【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(6)
这里,我们对文件做一下区分,在文件夹下新建两个文件夹,1024和2048,拖动所有的图片到2048的文件夹下,并改写代码中的路径

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(7)

            let map = textureLoader.load('./model/2048/Cerberus_A.jpg'); //颜色贴图
            let normalMap = textureLoader.load('./model/2048/Cerberus_N.jpg'); //法线贴图
            let roughnessMap = textureLoader.load('./model/2048/Cerberus_R.jpg'); //粗糙度贴图
            let metalnessMap = textureLoader.load('./model/2048/Cerberus_RM.jpg');//金属度贴图

这里的操作,需要你的电脑上有PhotoShop软件

  1. 使用photoShop打开贴图
  2. 文件-> 导出 -> 导出为
  3. 选择降低分辨率,png格式,同时选择八位色

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(8)

在左侧我们可以看到图片处理后的大小,779.3kb,原文件位置810kb,大小也发生了改变,然后我们把导出的图片,全部保存到1024文件夹中

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(9)
全部处理完后,我们看一下导出结果,文件总大小优化了1.3 M左右,后续加载速度也会有一定的提升

这里我们把代码修改为加载1024的图片,注意,格式已经从jpg变化为了png


            // let map = textureLoader.load('./model/2048/Cerberus_A.jpg'); //颜色贴图
            // let normalMap = textureLoader.load('./model/2048/Cerberus_N.jpg'); //法线贴图
            // let roughnessMap = textureLoader.load('./model/2048/Cerberus_R.jpg'); //粗糙度贴图
            // let metalnessMap = textureLoader.load('./model/2048/Cerberus_RM.jpg');//金属度贴图

            let map = textureLoader.load('./model/1024/Cerberus_A.png'); //颜色贴图
            let normalMap = textureLoader.load('./model/1024/Cerberus_N.png'); //法线贴图
            let roughnessMap = textureLoader.load('./model/1024/Cerberus_R.png'); //粗糙度贴图
            let metalnessMap = textureLoader.load('./model/2048/Cerberus_RM.png');//金属度贴图

此时我们再来看一下结果

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(10)

这里我们可以看到,GPU出现了明显下降,而内存并没有发生大的变化

降低贴图分辨率,可以降低文件大小,增加一定的加载速度,同时可以降低一定的显存占用

对贴图的优化,对分辨率越高的贴图效果越好,比如说你的模型贴图分辨率原先是4096 * 4096,那么优化到2048,进一步优化到1024就会比较明显

但是,注意!优化贴图会让部分细节变得模糊!尽量保持贴图的最低分辨率为1024

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(11)
上图左侧是使用1024贴图的,右侧是使用2048贴图的,我们在拉近的时候,可以看到明显的细节上的区别,所以优化贴图的技巧慎用!

贴图质量切换

如果你不能确定用户的设备水平,你可以追加这样一个设置,让用户自行选择分辨率

            let map,normalMap,roughnessMap,metalnessMap;

            let effect = "high"

            if(effect === "high"){
                map = textureLoader.load('./model/2048/Cerberus_A.jpg'); //颜色贴图
                normalMap = textureLoader.load('./model/2048/Cerberus_N.jpg'); //法线贴图
                roughnessMap = textureLoader.load('./model/2048/Cerberus_R.jpg'); //粗糙度贴图
                metalnessMap = textureLoader.load('./model/2048/Cerberus_RM.jpg');//金属度贴图
            }else{
                map = textureLoader.load('./model/1024/Cerberus_A.png'); //颜色贴图
                normalMap = textureLoader.load('./model/1024/Cerberus_N.png'); //法线贴图
                roughnessMap = textureLoader.load('./model/1024/Cerberus_R.png'); //粗糙度贴图
                metalnessMap = textureLoader.load('./model/2048/Cerberus_RM.png');//金属度贴图
            }

这样配置了之后,只需要我们修改 effect 为 “low”,就可以使用低分辨率的贴图,来保证用户能更好的跑起来效果,如果用户的设备配置足够,可以直接选择high来运行

小技巧:如果你的项目中涉及到分辨率的切换,可以使用gltf格式来加载模型,手动加载指定位置的贴图,前面讲过,gltf格式是由 gltf + bin + 贴图文件构成,这里我们就可以让建模师导出并处理多套贴图,放到对应的文件路径

为什么光源要限制数量

我们先写这样一个案例

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
*{
margin: 0;
padding: 0;
border: 0;
}
body{
width:100vw;
height: 100vh;
overflow: hidden;
}
</style>
</head>
<body>


<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../three/build/three.module.js",
"three/addons/": "../three/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "../three/build/three.module.js";
import {OrbitControls} from "../three/examples/jsm/controls/OrbitControls.js";
import Stats from "../three/examples/jsm/libs/stats.module.js";
window.addEventListener('load',e=>{
init();
addRoom();
addMesh();
addLights();
render();
})
let scene,renderer,camera;
let orbit;
let stats = new Stats();
function init(){
document.body.appendChild(stats.dom);
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({
antialias:true
});
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,2000);
camera.position.set(100,100,100);
orbit = new OrbitControls(camera,renderer.domElement);
orbit.enableDamping = true;
scene.add(new THREE.GridHelper(10,10));
}
function addRoom() {
let geometry = new THREE.BoxGeometry(100,100,100);
let material = new THREE.MeshStandardMaterial({
color:"#ffffff",
side:THREE.BackSide
});
let mesh = new THREE.Mesh(geometry,material);
mesh.receiveShadow = true;
scene.add(mesh);
}
function addMesh() {
let count = 100;
let geometry = new THREE.BoxGeometry(5,5,5);
for(let i = 0;i< count;i++){
let material = new THREE.MeshStandardMaterial({
color:0xffffff * Math.random()
});
let mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);
mesh.position.x = Math.random() * 100 - 50;
mesh.position.y = Math.random() * 100 - 50;
mesh.position.z = Math.random() * 100 - 50;
mesh.receiveShadow = true;
mesh.castShadow = true;
}
}
function addLights() {
let count = 1;
for(let i = 0;i< count;i++){
let pointLight = new THREE.PointLight(0xffffff * Math.random(), 1,1000,0.01);
pointLight.position.x = Math.random() * 100 - 50;
pointLight.position.y = Math.random() * 100 - 50;
pointLight.position.z = Math.random() * 100 - 50;
pointLight.castShadow = true;
scene.add(pointLight);
}
}
function render() {
stats.update();
renderer.render(scene,camera);
orbit.update();
requestAnimationFrame(render);
}
</script>
</body>
</html>

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(12)
运行效果是完全随机的,所以如果和我这里的效果不同不用太在意

我们先看一下,1个点光源时的内存情况和帧率情况
【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(13)
【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(14)
本人的屏幕是144hz的屏幕,所以这里显示的是144,帧率会随着屏幕刷新率不同而变化,我们只需要重点关注帧率变化即可

紧接着,我们增加到5个点光源,修改addLights() 中的count = 1 到 count = 5即可

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(15)

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(16)
可以看到,我们就只是增加了4个点光源,GPU就多了近10万K的消耗,因为本人的设备比较好,帧率暂时还没有出现波动,紧接着我们增加到10个点光源

本人的设备是I7-10750H,显卡RTX 2070Super,测试的结果会比较高,设备差的如果10个点光源卡崩了,就不要再盲目增加点光源了

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(17)
10个点光源时,显存占用再次提升了10万左右,内存也跟着提升了近一倍,帧率依然保持坚挺,但是我们在打开网页时已经出现了明显卡顿

我们接着增加到15个点光源
【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(18)

我们增加到20个。。。但是场景变的一片漆黑,这个问题我们先挖个坑,后面再解决,只需要知道场景中点光源是有上限的即可

从几次的测试结果来看,GPU进程的内存,随着光源数量的增多而增加,GPU占用率随着光源数量增多而增多,也就是说,场景中的光源越多,对GPU的内存消耗就越大,GPU使用率越高

阴影质量的影响

我们在10个点光源的基础上,修改一下光源的阴影质量

    function addLights() {
let count = 10;
for(let i = 0;i< count;i++){
let pointLight = new THREE.PointLight(0xffffff * Math.random(), 1,1000,0.01);
pointLight.position.x = Math.random() * 100 - 50;
pointLight.position.y = Math.random() * 100 - 50;
pointLight.position.z = Math.random() * 100 - 50;
pointLight.castShadow = true;
pointLight.shadow.mapSize.set(1024,1024);
scene.add(pointLight);
}
}

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(19)

可以看到,显存发生了显著增长,我们此时把阴影质量提高到2048

【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(20)
显存已经提高了三倍多,帧率此时已经下降到了78帧,已经不能再跑到144帧了

我们此时减少5个点光源再看看结果
【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)插图(21)

可以看到,阴影质量对显存的影响要远超光源数量对显存的影响

阴影本身也可以理解为是一种贴图

阴影最终会被渲染到材质上,阴影的分辨率越高,我们最终看到的阴影就会越柔和,阴影的实际效果,与你的场景比例有直接关系,

比如说,你是一个小房间,那么,此时,使用高精度阴影的效果就会比较好,

如果你的场景本身比例非常大,比如说园区模型,那么此时无论你怎么调整阴影的精度,最终打出来的效果都会差强人意

最终结论:适当使用光源和阴影,可以降低GPU内存消耗以及GPU使用率

本站无任何商业行为
个人在线分享 » 【Threejs进阶教程-优化篇】3. 卡顿与内存管理(二)
E-->