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

news2025/1/19 2:55:25

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

  • 学习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格式的文件,这个模型也是之前在材质篇中使用到的模型

在这里插入图片描述
我们来查看一下文件大小在这里插入图片描述

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

在这里插入图片描述

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软件

  1. 使用photoShop打开贴图
  2. 文件-> 导出 -> 导出为
  3. 选择降低分辨率,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使用率

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1801949.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

贪心算法学习二

例题一 解法&#xff08;贪⼼&#xff09;&#xff1a; 贪⼼策略&#xff1a; 由于只能交易⼀次&#xff0c;所以对于某⼀个位置 i &#xff0c;要想获得最⼤利润&#xff0c;仅需知道前⾯所有元素的最⼩ 值。然后在最⼩值的位置「买⼊」股票&#xff0c;在当前位置「卖出」…

数据挖掘--引论

数据挖掘--引论 数据挖掘--认识数据 数据挖掘--数据预处理 数据挖掘--数据仓库与联机分析处理 数据挖掘--挖掘频繁模式、关联和相关性&#xff1a;基本概念和方法 数据挖掘--分类 数据挖掘--聚类分析&#xff1a;基本概念和方法 1.2什么是数据挖掘 数据挖掘是从大量数据…

武汉理工大学 云计算与服务计算 期末复习

云计算与的定义 长定义是&#xff1a;“云计算是一种商业计算模型。它将计算任务分布在大量计算机构成的资源池上&#xff0c;使各种应用系统能够根据需要获取计算力、存储空间和信息服务。” 短定义是&#xff1a;“云计算是通过网络按需提供可动态伸缩的廉价计算服务。 云计…

Pytorch语义分割(1)-----加载数据

一、数据标注 &#xff08;1&#xff09;使用labelme来进行分割标注&#xff0c;标注完之后会得到一个json&#xff0c;数据格式如下&#xff1a; 二、获取数据信息 读取json文件来得到标签信息&#xff0c;读取jpg文件获取图像。在语义分割中用到的数据无非就是原始图片&…

Redis-sentinel(哨兵模式)的搭建步骤及相关知识

1、什么是redis-sentinel&#xff0c;和redis主从复制相比&#xff0c;它具有什么优势 1.1、redis主从复制 Redis主从复制是一种用于数据冗余和可伸缩性的机制&#xff0c;它将一台Redis服务器的数据复制到其他Redis服务器。在这种模式下&#xff0c;数据会实时地从一个主节点…

SwiftUI六组合复杂用户界面

代码下载 应用的首页是一个纵向滚动的地标类别列表&#xff0c;每一个类别内部是一个横向滑动列表。随后将构建应用的页面导航&#xff0c;这个过程中可以学习到如果组合各种视图&#xff0c;并让它们适配不同的设备尺寸和设备方向。 下载起步项目并跟着本篇教程一步步实践&a…

wx 生命周期

以下内容你不需要立马完全弄明白&#xff0c;不过以后它会有帮助。 下图说明了页面 Page 实例的生命周期。

记录jenkins pipeline ,git+maven+sonarqube+打包镜像上传到阿里云镜像仓库

1、阶段视图&#xff1a; 2、准备工作 所需工具与插件 jdk&#xff1a;可以存在多版本 maven&#xff1a;可以存在多版本 sonar-scanner 凭证令牌 gitlab&#xff1a;credentialsId sonarqube:配置在sonarqube208服务中 3、jenkinsfile pipeline {agent anystages {stage(从…

GSS7000卫星导航模拟器结合RTKLIB 接收NTRIP网络RTCM数据以输出RS232

本文聚焦&#xff0c;使用GSS7000仿真GNSS NTRIP&#xff0c;利用开源工具RTKLIB 作为NTRIP Client 接受GSS7000仿真的RTCM数据&#xff0c; 并通过STRSVR将收到的RTCM数据通过USB-RS232数据线吐出&#xff0c;并转给DUT&#xff0c;让其获得RTK -FIXED 固定解。 废话不多说&a…

微信小程序 导航navigation-bar

属性类型默认值必填说明最低版本titlestring否导航条标题2.9.0loadingbooleanfalse否是否在导航条显示 loading 加载提示2.9.0front-colorstring否导航条前景颜色值&#xff0c;包括按钮、标题、状态栏的颜色&#xff0c;仅支持 #ffffff 和 #0000002.9.0background-colorstring…

如何提高网站收录?

GSI服务就是专门干这个的&#xff0c;这个服务用的是光算科技自己研发的GPC爬虫池系统。这个系统通过建立一个庞大的站群和复杂的链接结构&#xff0c;来吸引谷歌的爬虫。这样一来&#xff0c;你的网站就能更频繁地被谷歌的爬虫访问&#xff0c;从而提高被收录的机会。 说到效…

Python语言读取图像

import cv2 import numpy as np width 640 # 图像宽度height 480 # 图像高度channels 3 # 颜色通道数imgEmpty np.empty((height, width, channels), np.uint8) # 创建空白数组imgBlack np.zeros((height, width, channels), np.uint8) # 创建黑色图像 RGB0imgWhite …

全自动饲料机械成套设备:养殖好帮手

全自动饲料机械成套设备是一套能够自动完成饲料生产全过程的机械设备。从原料的粉碎、混合、制粒&#xff0c;到成品的包装、储存&#xff0c;再到生产过程的监控与管理&#xff0c;全部实现自动化操作。减轻了人工劳动强度&#xff0c;提高了生产效率&#xff0c;同时也保证了…

指针在C/C++中的魔力:一级指针与二级指针

什么是指针&#xff1f; 指针是一个变量&#xff0c;它的值是另一个变量的地址。在C/C中&#xff0c;指针是一个强大的工具&#xff0c;可以让我们直接操作内存地址。指针的主要用途包括动态内存分配、数组和字符串处理、函数参数传递等。 一级指针 一级指针&#xff08;也称为…

Prometheus+Altermanager实现钉钉告警

PrometheusAltermanager实现钉钉告警 Prometheus和Altermanager的安装这里就不赘述了&#xff0c;我之前的文章有写到 不记得的小伙伴可以去看看Prometheus和Altermanager的安装使用 直接开始上操作 下载钉钉并打开&#xff0c;先创建一个接收告警信息的钉钉群 添加一个自定…

数据结构【二叉树——堆】

二叉树——堆 1.二叉树的概念与性质二叉树的概念特殊的二叉树 2.二叉树的性质3.二叉树的存储结构顺序结构链式结构 4.堆堆的概念堆接口的实现&#xff08;默认为大堆&#xff09;堆的结构堆的初始化堆的销毁栈的插入堆的删除取堆顶数据堆的元素个数堆的判空 完整代码Heap.hHeap…

ArcGIS for js 4.x 加载图层

二维&#xff1a; 1、创建vue项目 npm create vitelatest 2、安装ArcGIS JS API依赖包 npm install arcgis/core 3、引入ArcGIS API for JavaScript模块 <script setup> import "arcgis/core/assets/esri/themes/light/main.css"; import Map from arcgis…

计网期末复习指南(五):运输层(可靠传输原理、TCP协议、UDP协议、端口)

前言&#xff1a;本系列文章旨在通过TCP/IP协议簇自下而上的梳理大致的知识点&#xff0c;从计算机网络体系结构出发到应用层&#xff0c;每一个协议层通过一篇文章进行总结&#xff0c;本系列正在持续更新中... 计网期末复习指南&#xff08;一&#xff09;&#xff1a;计算机…

【Go语言精进之路】构建高效Go程序:零值可用、使用复合字面值作为初值构造器

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 引言一、深入理解并利用零值提升代码质量1.1 深入Go类型零值原理1.2 零值可用性的实践与优势1.2.1 切片(Slice)的零值与动态扩展1.2.2 Map的零值与安全访问1.2.3 函数参数与零值 二、使用复合字面值作为初值构造器2.1 结构体…