温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何使用Unity 2018深度优化渲染管线

发布时间:2021-07-24 13:42:26 来源:亿速云 阅读:264 作者:chen 栏目:大数据

这篇文章主要介绍“如何使用Unity 2018深度优化渲染管线”,在日常操作中,相信很多人在如何使用Unity 2018深度优化渲染管线问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用Unity 2018深度优化渲染管线”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

这段时间一直没有出文章,其实除了学校课程繁忙这些因素,更是把许多时间都放在了整理和优化旧代码的工作上,其中很大一部分包括对内存管理的“苛求”,乃至使用一些新特性对渲染管线的在CPU执行的代码进行深度优化。

首先来介绍一下Unity现有的3种编译方法,Mono VM, IL2CPP, Burst。

Mono虚拟机是传统而古老的实现方案,其跨平台的优势使得早年的Unity一直依赖于它,然而Mono本身性能极差,代码执行效率只有Native C++的一半左右,这也使得早年的Unity扣上了“做PC不行”“做不了大作”“引擎性能差”的帽子,而现在除了Editor中为了开发效率仍在使用Mono VM,PC,主机,手机等平台对其的需求越来越低。

IL2CPP是通过IL作为中间层,将代码编译成Native CPP的办法,在2018.3正式版发布以后,IL2CPP的编译优化有了长足进展,一些古怪的小问题也逐渐减少,官方也大力推荐在高端平台代替Mono使用,这将会是我们目前使用的主要的编译方法。但是这种编译方法也并非没有坑, 相反它的坑要比Mono VM还要多。因为经过IL层的翻译,一些C#高级用法在翻译的过程中极有可能产生额外的性能消耗,所以在开发时我们应该用CPP的思维去考虑开发,毕竟游戏开发与传统.Net开发还是有巨大差距的,当然也不是要求所有的开发者都像我一样代码里指针乱飞,把C#当C++写,但是最起码编程习惯方面的问题还是要留意一下的,该牺牲开发舒适度就要牺牲一下下(笑)。

Burst Compiler是一个专用于科学运算的编译器,之所以说是“专用于科学运算”,是因为除此之外它基本没有其他功能,本身不支持托管类型,所以代码中不能有任何访问托管类型的代码,既然托管类型不支持,那么抽象,多态等面向对象就更别想了,可以说就是最原始的C语言编程,Burst的设计是专门用来处理科学运算的,比如处理矩阵,向量等提供了诸如SIMD之类的优化黑科技。官方一直在神化Burst,称其运行性能能远超C++,但是其实在我看来,Burst只在处理个别数据处理类方法时可以派上用场,在处理其他普通代码时并不会比C++快多少,同时只允许使用非托管类型,禁止一切OOP对于项目开发而言许多时候也不切实际(至少在ECS全面普及开来前是这样),所以Burst在目前只会被我们当成开发时佐料。

而我们主要的优化思路基本分为以下几种:

  1. 尽量使用非托管的数据类型,并手动管理内存,做到真正的Runtime 0 GC。

  2. 尽量把C#当成C++来开发,原因上方已经介绍过。

  3. 尽可能多的使用Job System计算逻辑,充分发挥多线程优势。

  4. 尝试使用Burst Compiler编译纯数学运算的代码。

在之前文章中介绍的GPU Driven Pipeline的代码其实有很大的优化空间,因此我们首先处理的就是光照部分,因为我们的管线使用了Tile/Cluster Based Deferred Rendering,所以需要在CPU中准备传入Compute Buffer的数据,同时灯光阴影的运算需要生成视锥体的矩阵,那么这一部分的运算就可以扔到Job System中进行运算,当然这一部分中有不少访问托管类型的部分,而且运算也大多数是逻辑而非运算,所以这里使用传统的IL2CPP编译而非Burst,代码结构大概是这样:

如何使用Unity 2018深度优化渲染管线

因为整个渲染管线都是单例的存在,因此直接使用static非常安全,另外Job System中是不允许出现实例化的变量,所以这里也只能使用静态,如果不想使用静态,但又一定要使用托管类型,也并非没有办法,我的解决方案如下:

如何使用Unity 2018深度优化渲染管线

这样的方法可以将托管类型的地址强行赋值到指针中,但是指针并不在GC的管理范围,所以在这样处理时一定要注意,是否应该开启GCHandle保证非托管代码执行的途中该托管类型不会被GC干掉。当然,Unity的Component是受引擎管理的,所以不需要考虑C# GC会作妖。

可以看到,在上方的Job中,处理了包括点光源,聚光源的信息和矩阵,并将运算结果转存到上方的NativeArray和NativeList中,最后再将值直接传入到Compute Buffer中,这个过程的计算一直是一个逻辑重点,比如Cubemap需要计算6个面的View projection Matrix。在将这一部分代码放入到Job中后,在本人的高端PC测试机上,在灯光数量较多的时候,代码执行时间直接减少了0.1ms-0.2ms,这是一个可喜的进步,尤其是项目开发后期主线程的压力可能会成为性能短板,这样的优化是非常有意义的。

除此之外,还有一个运算的大头,就是Cascade Shadowmap的矩阵布置,Cascade Shadowmap的计算方法,这里已经不需多做解释了,其中耗费较高的就是一个通过Frustum Corner获取正方体位置,以及通过ViewProjection的逆矩阵反推世界坐标,进而计算像素的棋盘格,防止因为摄像头移动导致的shadowmap锯齿抖动,而逆矩阵的推导看似其貌不扬,实则运算量颇高,在CPU单核运算能力较弱的平摊上消耗绝对不容小觑,因此我们将其放到Job System中是一个很不错的选择,值得一提的是,这个过程属于纯数值类运算,因此可以使用Burst Compiler:

如何使用Unity 2018深度优化渲染管线

传入一大堆需要用到的或者返回的属性值,最后在Execute中计算,值得一提的是,Unity现有的GL.GetGPUProjectionMatrix是不支持Burst也不支持在分线程中运算的,这个真的很坑,所以我们只能自己写一个D3D和OpenGL投影矩阵标准转换的函数,同时因为Burst不支持静态变量,所以连isD3D这种flag也需要手动传递(一种隐隐的蛋疼感传来)。

虽然因为Burst的鬼畜限制,代码变得奇丑无比,But it just works well! 计算部分的代码大概如下:

如何使用Unity 2018深度优化渲染管线

总共4个cascade,因此这段代码将会在4个线程中分别执行,这段代码又为我们省下了主线程的0.1ms。

灯光数据的运算基本是整个渲染管线中消耗最大的一部分了,毕竟SRP已经将更复杂的场景剔除之类逻辑封装好了,所以实际需要我们做的大概就是这些自定义的数据类型,一些自定义的Volume组件的视锥体也是需要我们手动计算的,比如之前Froxel volumetric Rendering中曾经提到的Fog Volume,也就是管理局部雾效的组件,我们当仁不让的也使用了Burst Compiler加速:

如何使用Unity 2018深度优化渲染管线

其他大大小小的Job还是有不少的,Unity的Job System的原理是将任务累计到一起,通过调用JobHandle.ScheduleBatchedJobs()的方法同时开始计算所有任务,这是因为开启线程本身是一个相对昂贵的步骤,所以我们应该尽量把更多的任务统一堆到Job Queue中,再让执行流程负责启动任务,最后通过Complete保证当前任务执行完毕,保证线程安全。综上所述,Job System的使用本着:“益多,益杂,不益散”的原则,多,是因为每个struct在加入队列的时候需要memcpy,因此体积不应该太大,同时较多的任务也更容易让Unity的Job Threads自行挑选,平衡核心负载,益杂的意思是,许多大大小小的任务,积少成多,应该尽可能的写成Job而不是堆砌在主线程,不益散则映射了刚才说过的ScheduleBatchedJobs的原理,应该尽可能同时启动所有的任务,保证执行的统一性。

除了多线程优化,在内存优化方面我们也是完成了许多的工作,首先值得说的一点就是Unity的Memory Allocator的使用方法,学过C语言的朋友自然知道Malloc和Free的用法,这里就不用多说了,Unity提供了3种Allocator,分别是Temp, TempJob和Persistant。

Temp: 开辟的内存会在帧结束后被释放,适合只在当前帧使用的,同时也是开辟速度最快的,可以说其开辟速度仅次于栈内存stackalloc,如果为了处理帧内传递的数据,应该尽可能使用这种,而且不需要Free,在帧结束后会被Allocator自己回收,没有泄露危险。

TempJob:开辟速度仅次于Temp,可以维持4帧,在4帧后会被自动回收,坦率的讲我个人不是很喜欢这个设定,因为“4帧”这个限制显得有点不伦不类,但是存在即是正义,想必也有其用途所在。

Persistant:永久的开辟内存,开辟速度最慢,必须手动调用Free,否则会泄露,这也是最传统的malloc方法,适合常驻的数据结构使用。

配合C# 7.3给出的unmanaged的泛型限制,可以使用这套内存管理系统写出纯手动管理内存的泛型,真正做到0 GC。然而目前引擎本身还是有一些限制,譬如SRP还不支持获取ECS的Component类型,因此代码还很难做到纯粹的“面向性能编程”,总是显得有些不伦不类,还带着许多面向对象的包袱,希望在接下里几年里,Unity能够逐渐完善ECS和SRP,并逐渐彻底抛弃传统面向对象的开发方式。

到此,关于“如何使用Unity 2018深度优化渲染管线”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI