乐趣区

关于unity:游戏中动态分辨率从原理到应用

随着以后越来越多的手游向“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_CreateLightweightRenderTexturesPass

public 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)

退出移动版