贴图纹理材质光影对内存性能的影响
- 学习ThreeJS的捷径
- 烘培模型简介
- 如何优化烘培模型
- 贴图处理
- 贴图质量切换
- 为什么光源要限制数量
- 阴影质量的影响
- 阴影本身也可以理解为是一种贴图
学习ThreeJS的捷径
本段内容会写在0篇以外所有的,本人所编写的Threejs教程中
对,学习ThreeJS有捷径
当你有哪个函数不懂的时候,第一时间去翻一翻文档
当你有哪个效果不会做的时候,第一时间去翻一翻所有的案例,也许就能找到你想要的效果
最重要的一点,就是,绝对不要怕问问题,越怕找找别人问题,你的问题就会被拖的越久
如果你确定要走WebGL/ThreeJS的开发者路线的话,以下行为可以让你更快的学习ThreeJS
- 没事就把所有的文档翻一遍,哪怕看不懂,也要留个印象,至少要知道Threejs有什么
- 没事多看看案例效果,当你记忆的案例效果足够多时,下次再遇到相似问题时,你就有可能第一时间来找对应的案例,能更快解决你自己的问题
- 上述案例不只是官网的案例,郭隆邦技术博客,跃焱邵隼,暮志未晚等站点均有不少优质案例,记得一并收藏
http://www.yanhuangxueyuan.com/ 郭隆邦技术博客
https://www.wellyyss.cn/ 跃焱邵隼
http://www.wjceo.com/ 暮志未晚(暮老的站点暂时挂了,请查阅他之前的threejs相关文档)
暮老的csdn首页
这三个站点是我最常逛的站点,推荐各位有事没事逛一下,看看他们的案例和写法思路,绝对没坏处
烘培模型简介
我们以threejs官方开发包的文件举例,在开发包three/examples/models/obj/cerberus中,有这样一个OBJ格式的文件,这个模型也是之前在材质篇中使用到的模型
我们来查看一下文件大小
在前面的灯光阴影材质篇,我们都有讲过,越好看越逼真的效果,都是建模师烘培出来的,用贴图贴出来的,上述模型,就是这样的模型,各位可以看一下这个模型实际贴出来的效果
Threeejs官方使用了这个模型的demo地址
这里的模型,笔者称为烘培模型
烘培模型的特点,一般是,模型效果非常逼真,模型与贴图的文件大小占比有明显的差距,且模型占比较小
如何优化烘培模型
有很多人刚好是遇到了烘培模型,但是用上一篇文章并不能解决卡顿问题,这里我们来介绍如何优化烘培模型
首先我们把模型加载进来,并贴好图
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();
}
})
})
}
因为本人是使用Chrome来编写文章的,所以避免干扰,本次我这里使用了Edge来统计内存,Edge本质上是套皮的Chrome,所以不会对结果有大的影响
稳定下来后,内存降低到了45976K
这个是我们初始的内存状态
贴图处理
我们先大概看一下5张贴图的分辨率
这里,我们对文件做一下区分,在文件夹下新建两个文件夹,1024和2048,拖动所有的图片到2048的文件夹下,并改写代码中的路径
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软件
- 使用photoShop打开贴图
- 文件-> 导出 -> 导出为
- 选择降低分辨率,png格式,同时选择八位色
在左侧我们可以看到图片处理后的大小,779.3kb,原文件位置810kb,大小也发生了改变,然后我们把导出的图片,全部保存到1024文件夹中
全部处理完后,我们看一下导出结果,文件总大小优化了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');//金属度贴图
此时我们再来看一下结果
这里我们可以看到,GPU出现了明显下降,而内存并没有发生大的变化
降低贴图分辨率,可以降低文件大小,增加一定的加载速度,同时可以降低一定的显存占用
对贴图的优化,对分辨率越高的贴图效果越好,比如说你的模型贴图分辨率原先是4096 * 4096,那么优化到2048,进一步优化到1024就会比较明显
但是,注意!优化贴图会让部分细节变得模糊!尽量保持贴图的最低分辨率为1024
上图左侧是使用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>
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<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>
运行效果是完全随机的,所以如果和我这里的效果不同不用太在意
我们先看一下,1个点光源时的内存情况和帧率情况
本人的屏幕是144hz的屏幕,所以这里显示的是144,帧率会随着屏幕刷新率不同而变化,我们只需要重点关注帧率变化即可
紧接着,我们增加到5个点光源,修改addLights() 中的count = 1 到 count = 5即可
可以看到,我们就只是增加了4个点光源,GPU就多了近10万K的消耗,因为本人的设备比较好,帧率暂时还没有出现波动,紧接着我们增加到10个点光源
本人的设备是I7-10750H,显卡RTX 2070Super,测试的结果会比较高,设备差的如果10个点光源卡崩了,就不要再盲目增加点光源了
10个点光源时,显存占用再次提升了10万左右,内存也跟着提升了近一倍,帧率依然保持坚挺,但是我们在打开网页时已经出现了明显卡顿
我们接着增加到15个点光源
我们增加到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);
}
}
可以看到,显存发生了显著增长,我们此时把阴影质量提高到2048
显存已经提高了三倍多,帧率此时已经下降到了78帧,已经不能再跑到144帧了
我们此时减少5个点光源再看看结果
可以看到,阴影质量对显存的影响要远超光源数量对显存的影响
阴影本身也可以理解为是一种贴图
阴影最终会被渲染到材质上,阴影的分辨率越高,我们最终看到的阴影就会越柔和,阴影的实际效果,与你的场景比例有直接关系,
比如说,你是一个小房间,那么,此时,使用高精度阴影的效果就会比较好,
如果你的场景本身比例非常大,比如说园区模型,那么此时无论你怎么调整阴影的精度,最终打出来的效果都会差强人意
最终结论:适当使用光源和阴影,可以降低GPU内存消耗以及GPU使用率