随着以后越来越多的手游向“3A”聚拢,手机上的各种性能优化也在致力地为“3A”保驾护航,巴不得要把芯片上每一个晶体管的性能都开掘进去。然而,当一台“高分低能”的手机摆在你背后的时候,是不是总是有一种“欲哭无泪”的无力感——既要放弃高帧率又要保障画面质量。成年人从来不做选择题,在两个都要的状况下,降分辨率往往是起效最快的方法。

说到调整设施的分辨率,Screen.SetResolution这个办法大家必定是很相熟了,然而这种调整是全局的,是硬件级别的调整,无奈做到3D和UI渲染指标的离开调整。当然,随着SRP管线的推出,咱们曾经能够实现3D相机和UI相机分辨率的离开调整,并且UWA上已有相干文章的介绍了(见参考9)。

明天这篇文章要探讨的是,Unity和Unreal都提供的动静分辨率的计划,它能够动静缩放单个渲染指标,以缩小GPU上的工作量。

说到3D和UI的离开渲染,聪慧的小伙伴必定想到了一种计划:3D渲染到一张RT上,最初把3D的RT Blit到最终的RT上。那么这种计划跟Unity提出的动静分辨率计划有何不同的中央吗?还是说只是新瓶装旧酒?

接下来,我就跟大家一起摸索一下动静分辨率(以Unity为主)的原理以及它的利用场景。

传统的3D和UI拆散计划

如上图所示,基本原理是渲染场景的时候调整视口大小(Viewport),将渲染束缚到屏幕外Render Target的一部分,而后再把场景的Render Target上的内容Blit到最终的RT上。例如,渲染指标的大小可能为(1920,1080),但视口的原点可能为(0,0),大小为 (1280,720)。

这种实现形式可能会有如下几个问题:

  1. Blit的性能损耗,这个操作必定不能是实时的,个别也就是在游戏初始化后或者在进入某个场景前设置一次,是一个低频操作,无奈做到真正的“实时”调整。
  2. 可能受限于渲染管线

    • 如果是默认渲染管线的话,最初这个Blit的操作机会就要选好,因为游戏中个别会有后处理阶段,咱们要利用好这个阶段顺便把Blit也做了。这个能够利用CommandBuffer向相机的不同渲染阶段插入视口批改和后处理操作。
    • 如果是SRP渲染管线的话(Unity 2018当前的版本),咱们就能有本人解决Blit的机会了,当然这个操作也不能是个高频操作。

应用流程

参考Unity官网文档,咱们先来看一下动静分辨率的应用流程。

首先咱们要确认一点:动静分辨率启用的前提是GPU Bound了。所以要通过实时获取每帧GPU的运行工夫来决定:

  • 是否是GPU压力过大导致游戏掉帧
  • 渲染指标的缩放系数

再依据缩放系数对渲染指标进行动静缩放。在这个过程中须要保障批改渲染指标分辨率的时候不重新分配GPU显存,否则就跟Screen.SetResolution一样了(会导致画面闪动)。

  1. 在须要动静缩放的相机上勾选,如图所示:

  1. 在PlayerSettings中勾选上“Enable Frame Timing Stats”:

  • 通过两个接口FrameTimingManager.CaptureFrameTimings()和FrameTimingManager.GetLatestTimings获取CPUTime和GPUTime后自行判断缩放系数
  • 最初调用ScalableBufferManager.ResizeBuffers(m_widthScale, m_heightScale)设置缩放

平台反对

Unity官网文档上是这么写的:

可能跟了解程度有关系,看到以上的阐明,我就犯迷糊了:OpenGLES不反对动静分辨率,内置渲染管线、URP等兼容,那么如果是URP下的OpenGLES平台呢?反对还是不反对?

不论如何,先把纳闷放一边,咱们来探索一下动静分辨率的实现原理。

原理探索

咱们顺着官网文档上的应用流程,摸入Unity源码外部,看看为什么对OpenGLES如此一视同仁。因为波及到源码局部,这里就间接说论断了。

  • 缩放RT是跟平台相干的,OpenGLES无奈创立缩放RT,起因咱们前面再讲
  • 动静分辨率的原理为Vulkan的内存混叠(Memory Aliasing)性能

Memory Aliasing
Memory Aliasing能够翻译成内存混叠或内存别名,参考[1]是Vulkan针对此概念的阐明。

古代图形 API(如DirectX 12或Vulkan)能够让用户定义内存地位,将调配的GPU资源放入手动创立的堆中。它容许咱们创立纹理和缓冲区,它们的内存局部甚至能够齐全重叠。这也是为什么OpenGLES不反对动静分辨率的起因,因为OpenGLES没有凋谢更底层的API让咱们能够实现更高效的内存治理。

以游戏中典型的一帧为例:光栅化一些几何体,执行着色,而后运行一堆后处理。这里的每个阶段的输入都将写入纹理或缓冲区,稍后在一帧中被其余阶段应用。然而,某个阶段产生的资源可能只被多数其余阶段应用,比方在后处理中:Bloom产生的输入,只会被下一阶段的Tone mapping(色调映射)应用,并且在帧中的其余任何中央都不须要。咱们能够看到,资源的无效生命周期可能很短,但很可能是事后调配的,并且在整个帧中都占用了它的内存。

解决内存频繁调配开释的办法就是对象池,Unity的RenderTexture.GetTemporary就是在外部保护了一个RenderTexture的对象池。然而这种办法只实用于后处理阶段,因为不同格局、大小的资源不能复用,后处理通常是全屏的Pass,读取、写入的Texture通常都有雷同的属性,一些简略的后处理只须要两个RT重复交替应用就能实现(这个我会在稍后的URP章节中重点解读一下) 。

对象池实质上是一种更下层的Memory Aliasing,开发者不须要关注内存治理;但古代图形API(DX12和Vulkan)提供了内存治理的接口,能够实现底层的Memory Aliasing。Memory Aliasing指的是不同变量指向同一地址,即在同一片内存区域中同时寄存多个资源,如果有很多大型资源在工夫上不会重叠,就能够在雷同的内存调配这些资源。相比对象池,Memory Aliasing能够进一步升高内存占用,因为在底层都是一堆字节,所以就不须要思考资源的类型、格局、大小等。具体的示意如下图所示:

小结
从以上的剖析咱们大略理解到了Unity实现动静分辨率的原理:利用Vulkan提供的内存治理接口,实现底层对内存高效地复用。这样咱们在游戏中就能够高效实时地调整分辨率,根本没有性能损耗。

URP实现

思考到URP的前身LWRP还有项目组在用,上面先简略看一下LWRP。

LWRP
简略点说就是通过从新创立相机的渲染指标来实现的。Setup时会先进入函数RequiresIntermediateColorTexture判断是否要创立新的RT,外面就有个变量isScaledRender,如果须要缩放,则进入创立RT的Pass:

m_CreateLightweightRenderTexturesPasspublic void Setup(ScriptableRenderer renderer, ref RenderingData renderingData){    ...    bool requiresRenderToTexture = ScriptableRenderer.RequiresIntermediateColorTexture(ref renderingData.cameraData, baseDescriptor);    RenderTargetHandle colorHandle = RenderTargetHandle.CameraTarget;    RenderTargetHandle depthHandle = RenderTargetHandle.CameraTarget;    if (requiresRenderToTexture)    {          colorHandle = ColorAttachment;          depthHandle = DepthAttachment;          var sampleCount = (SampleCount)renderingData.cameraData.msaaSamples;          m_CreateLightweightRenderTexturesPass.Setup(baseDescriptor, colorHandle, depthHandle, sampleCount);          renderer.EnqueuePass(m_CreateLightweightRenderTexturesPass);    }    ...}public static bool RequiresIntermediateColorTexture(ref CameraData cameraData, RenderTextureDescriptor baseDescriptor){     if (cameraData.isOffscreenRender)          return false;     bool isScaledRender = !Mathf.Approximately(cameraData.renderScale, 1.0f);     bool isTargetTexture2DArray = baseDescriptor.dimension == TextureDimension.Tex2DArray;     bool noAutoResolveMsaa = cameraData.msaaSamples > 1 && !SystemInfo.supportsMultisampleAutoResolve;     return noAutoResolveMsaa || cameraData.isSceneViewCamera || isScaledRender || cameraData.isHdrEnabled ||           cameraData.postProcessEnabled || cameraData.requiresOpaqueTexture || isTargetTexture2DArray || !cameraData.isDefaultViewport;}

URP
从Unity 2019.3.0a这个版本开始,LWRP开始正式降级为URP。URP次要分为两个文件夹:一个是独自提取进去跟HDRP共用的根底外围库core,另一个就是URP本人用的universal。

翻看了URP各个版本的代码,直到Core RP库10.2版本(对应Unity版本为2020.2.0b)开始,Unity才开始器重(提供)Render Target(渲染指标)的治理性能。

从上一章节的“原理探索”中,咱们晓得渲染指标治理是任何渲染管线的重要组成部分;咱们也晓得RenderTexture只有在新渲染纹理应用完全相同的属性和分辨率时能力重用内存。

为了解决渲染纹理内存调配的这些问题,Unity的SRP(URP&HDRP)引入了RTHandle零碎。该零碎是RenderTexture之上的一个形象层,可较好地治理渲染纹理,具体介绍能够看参考8,这里我就简略介绍一下。

如上截图中枚举所示,SRP实现了“硬件”和“软件”两种动静分辨率,“硬件动静分辨率“就是利用内存混叠硬实现的,而”软件动静分辨率“就是缩放RT适应以后视口的软实现。当硬件动静分辨率不反对以后平台时,RTHandle零碎会主动切换为软件动静分辨率。不仅如此,最新的URP版本还基于RTHandle实现了双缓冲,感兴趣的能够去URP源码查看RenderTargetBufferSystem。

利用

一路下来,咱们对“动静分辨率”也有了一个比拟粗浅的意识了,当说到“动静分辨率”时,咱们说的就是真正的硬件层面实现的动静分辨率,即:可能充分利用古代图形API的Memory Aliasing,为把FPS维持在肯定的程度,当产生GPU引起的掉帧时,可能在不重新分配GPU显存(利用图形API的Memory Aliasing)的状况下动静调整渲染指标分辨率。

然而,思考到设施的兼容性,咱们大部分游戏反对的平台都只能是OpenGLES而不是Vulkan,因而很遗憾,动静分辨率派不上用场了。退而求其次,针对不同的渲染管线,上面简略阐明一下咱们可能采纳的计划:

  • 默认渲染管线——Unity 2017(含)以前的版本
  • 能够应用本文“传统的3D和UI拆散计划”中介绍的计划,利用CommandBuffer在适合的机会对视口进行动静调整,但不能高频应用。
  • LWRP——Unity 2018~Unity 2019.3.0a
  • URP——Unity 2019.3.0a12+~Unity 2020.2.0b8+
    LWRP作为URP的前身,有好多功能还在欠缺中,曾经能够比拟好地实现3D和UI的离开渲染,相比默认渲染管线灵活性更好了。但还是没有提供比拟好的RT治理,须要本人参考URP来定制一套高效的RT管理系统。
  • URP——Unity 2020.2.0b12+

如上所述,直到SRP的Core RP库10.2版本开始,Unity才提供了一套比较完善的RT管理系统,大家能够酌情参考应用。


参考

[1] https://www.khronos.org/regis...

[2] 内存混叠的一种实现

[3] https://developer.nvidia.com/...

[4] https://docs.unrealengine.com...

[5] https://www.intel.com/content...

[6] https://docs.unity3d.com/Manu...

[7] https://github.com/Unity-Tech...

[8] https://docs.unity3d.com/Pack...

[9] 如何只降3D相机不降UI相机的分辨率


这是侑虎科技第1198篇文章,感激作者吕强供稿。欢送转发分享,未经作者受权请勿转载。如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ群:793972859)

作者在UWA学堂上线的《五天实现PBR保姆级教程》课程限时优惠中~

再次感激吕强的分享,如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ群:793972859)