动态分辨率渲染





5.00/5 (1投票)
动态分辨率渲染允许开发者动态地改变屏幕渲染的分辨率,而不是进行静态的分辨率选择。最终结果是:即使在低端系统上,也能以高品质的 GUI 实现稳定的高帧率。此代码示例可免费使用。
引言
自 3D 游戏诞生以来,分辨率选择屏幕一直是 PC 游戏的一个标志性方面。在这篇白皮书和配套的代码示例中,我们认为情况不再需要如此;开发者可以动态地改变渲染分辨率,而不是进行静态的分辨率选择。
动态分辨率渲染涉及通过视口将渲染限制在渲染目标的一部分来调整 3D 场景的渲染分辨率,然后将其缩放到输出的后备缓冲区。然后,图形用户界面组件可以以后备缓冲区分辨率进行渲染,因为这些通常是成本较低的绘制元素。最终结果是,可以以高品质的 GUI 实现稳定的高帧率。
我们将在本文中展示在预发布版本的移动设备二代 Intel® Core™ i7 处理器(Intel® 微架构代号 Sandy Bridge,D1 步进四核 2.4 GHz CPU,配 4GB DDR3 1333MHz RAM)和 Intel® HD Graphics 3000 上拍摄的性能结果和截图。
本文及配套示例最初于 2011 年在旧金山的 GDC(游戏开发者大会)上发表,其演讲视频可在 GDC Vault [GDC Vault 2011] 找到,演讲幻灯片可在 Intel 网站 [Intel GDC 2011] 上找到。自演讲以来,作者发现一些游戏公司已经在游戏主机上使用了这项技术;LucasArts 关于抗锯齿的演讲者 Dmitry Andreev 的演讲是唯一公开的来源,但对所使用的动态分辨率技术细节不多 [Andreev 2011]。
动机
游戏几乎总是有很强的分辨率性能变化,而着色器复杂度的增加以及后处理技术的应用,使得每像素成本在现代游戏中占主导地位。提高分辨率还会增加纹理采样和渲染目标带宽。因此,根据系统性能适当设置分辨率至关重要。能够动态地改变分辨率为开发者提供了额外的性能控制选项,可以使游戏保持稳定且适当的帧率,从而提高整体体验质量。
以原生屏幕分辨率渲染图形用户界面对于角色扮演、实时战略和大型多人在线游戏尤为重要。这样,即使在低端系统上,玩家也可以在关注队友数据的同时,沉浸于复杂的聊天消息。
最后,随着笔记本电脑在 PC 游戏中的主导地位日益增长,功耗开始与游戏开发相关。当机器从市电转为电池供电时,性能设置可能导致 CPU 和 GPU 频率降低,而通过动态分辨率渲染,游戏可以自动调整分辨率进行补偿。一些游戏可能希望提供低功耗模式选项,以进一步降低功耗,从而实现更长的移动游戏时间。对示例进行的实验发现,当垂直同步开启以维持帧率时,将分辨率降低到 0.5 倍可以将处理器封装的功耗降低到正常功耗的 0.7 倍。
基本原理
动态分辨率渲染的基本原理是使用视口将渲染限制在离屏渲染目标的一部分,然后缩放视图。例如,渲染目标可能大小为 (1920, 1080),但视口的原点可能是 (0, 0) 且尺寸为 (1280, 720)。
通过创建比后备缓冲区大的渲染目标,动态分辨率可以从欠采样到超采样进行变化。需要注意确保所需渲染目标和纹理的完整集合适合图形内存,但基于 Intel® 微架构代号 Sandy Bridge 处理器图形的系统通常拥有可观的内存,因为它们使用系统内存。
当进行普通渲染到动态视口时,无需进行任何更改——光栅化规则可确保这一点得到处理。但是,当从渲染目标读取时,需要注意适当缩放坐标并在右侧和底部边缘进行钳位。
以下示例像素着色器代码展示了如何钳位 UV。这主要用于依赖读取(即,当对 UV 进行逐像素操作,然后用于采样动态渲染目标时)。
// Clamp UVs to texture size
// PSSubSampleRTCurrRatio is the fraction of the render target in use.
float2 clampedUV = min( unclampedUV, g_PSSubSampleRTCurrRatio.xy );
在运动模糊的情况下——一种常见的后处理操作,它从渲染目标进行依赖读取——所需的额外数学运算对性能影响很小,因为着色器是纹理获取绑定的。
除了钳位,确保着色器中使用的分辨率比例代表实际视口比例,而不仅仅是应用程序的期望比例也很重要。这可以通过从动态视口尺寸重新计算比例轻松获得。例如,在示例代码函数 DynamicResolution::SetScale 中,在确保比例满足边界条件后会执行以下操作
缩放滤波器
渲染完 3D 场景后,需要将视口区域缩放到后备缓冲区分辨率。可以使用多种滤波器来执行此操作,并且示例在此处描述了几种实现。
点过滤
点过滤是一种快速的基本过滤选项。从 0.71 倍比例动态视口缩放到 1280x720 需要约 0.4 毫秒。
双线性过滤
由于硬件支持,双线性过滤几乎与点过滤一样快,它通过平滑减少边缘的锯齿伪影,但也会模糊场景。从 0.71 倍比例动态视口缩放到 1280x720 需要约 0.4 毫秒。
三次插值过滤
三次插值过滤仅在分辨率为后备缓冲区的 0.5 倍时才明显优于双线性过滤,并且其性能即使使用快速三次插值滤波器也慢 7 倍 [Sigg 2005]。从 0.71 倍比例动态视口缩放到 1280x720 需要约 3.5 毫秒。
噪声过滤
为点过滤添加一些噪声有助于添加高频,以较低的成本稍微打破锯齿。示例中的实现相当基础,改进的胶片颗粒过滤可能在艺术上适合您的渲染。从 0.71 倍比例动态视口缩放到 1280x720 需要约 0.5 毫秒。
噪声偏移过滤
在缩放过程中向采样位置添加一个小的随机偏移可以减少锯齿边缘的规律性。这种方法在阴影图的快速过滤中很常见。从 0.71 倍比例动态视口缩放到 1280x720 需要约 0.7 毫秒。
时间抗锯齿过滤
此缩放过滤器需要在初始渲染路径中进行额外支持,以渲染 X 和 Y 方向偏移半个像素的奇偶帧。当智能过滤以消除重影伪影时,通过从两倍的像素采样,图像质量会得到显著改善。这种过滤方法将在下面的单独部分中更详细地描述。从 0.71 倍比例动态视口缩放到 1280x720 需要约 1.1 毫秒,其质量几乎与以全分辨率渲染相同。
时间抗锯齿详细信息
时间抗锯齿已经存在一段时间了;然而,由于连续帧中对象位置差异导致的重影问题限制了其使用。现代渲染技术终于使其成为一个有吸引力的选择,因为它具有较低的性能开销。
基本方法是渲染抖动(偏移)的奇偶帧,X 和 Y 方向各偏移半个像素。示例代码通过转换投影矩阵来实现这一点。最终的缩放结合了当前帧和前一帧,并偏移它们与它们抖动的逆值。最终图像因此由两倍数量的像素组成,排列成类似于骰子五点图案的模式,通常称为五点图案。
与动态分辨率结合使用时,当动态分辨率低于后备缓冲区时,此方法可以增加场景中观察到的像素数量,从而提高场景的细节。当动态分辨率等于或高于后备缓冲区时,结果是一种抗锯齿形式。
为了获得更高的纹理分辨率,需要对纹理应用 MIP LOD 偏差。在 Microsoft Direct3D* 11 中,在 3D 场景通道期间使用 D3D11_SAMPLER_DESC MipLODBias 为 -0.5f。此外,缩放期间使用的采样器需要使用双线性缩小过滤,例如:D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT。
为了减少重影,我们使用为运动模糊输出的运动矢量缓冲区。重要的是,此缓冲区包含屏幕空间中每个像素的运动矢量,从而考虑了摄像机移动。从当前帧和前一帧的运动矢量计算出一个缩放因子,并应用于前一帧的颜色,以确定其对最终图像的贡献。这会根据两个帧中样本位置在真实空间中的相似程度来缩放贡献。
该示例的 K 值经过调整,以提供作者认为对实时应用最佳的结果,在实际可玩帧率下未观察到重影。屏幕截图确实在对比度高的区域显示少量重影,如图下截图所示,如果需要,可以进行调整。
对于游戏而言,透明度是一个特殊的问题,因为它并不总是渲染运动矢量信息。在这种情况下,可以在透明度前向渲染期间使用 alpha 通道来存储一个值,该值用于以与当前使用运动矢量相同的方式缩放贡献。
另一种消除重影的方法是使用屏幕空间运动矢量从前一帧中采样当前像素所在的位置。这是 CryENGINE* 3 中使用的技术,首次在游戏 Crysis* 2 中展示 [Crytek 2010]。有趣的是,LucasArts 的 Dmitry Andreev 考虑使用时间抗锯齿,但由于他们的引擎使用了动态分辨率而没有使用 [Andreev 2011]。作者认为它们是兼容的,正如示例代码所示。
运动模糊的效果
运动模糊会使像素模糊并有效减少观察到的锯齿,因此当摄像机移动时可以使用较低的分辨率。但是,该示例在其分辨率控制算法中并未利用这一点。以下屏幕截图显示,将分辨率降低到后备缓冲区的 0.71 倍可提高性能,但图像质量大致相同。结合变化的运动模糊采样率,这可能是一种减少由于相机大幅移动而导致的欠采样伪影,同时保持一致性能的方法。
超采样
超采样是一种简单的技术,其中用于渲染场景的渲染目标大于后备缓冲区。当前实时渲染社区在很大程度上忽略了这项技术——多重采样抗锯齿和其他抗锯齿技术因其更好的内存消耗和性能而取代了它的使用。
使用动态分辨率可以显著降低添加超采样的性能影响,因为实际使用的分辨率可以动态调整。启用超采样会产生轻微的性能影响,主要是由于清除较大缓冲区所带来的额外成本。该示例代码在启用超采样时实现 2 倍分辨率的渲染目标,但当分辨率仅比后备缓冲区分辨率略微提高时,就能观察到良好的质量结果,因此如果内存有限,可以使用较小的渲染目标。在处理器图形平台上,内存问题较少,因为 GPU 可以访问相对大量的系统内存,所有这些内存都可以全性能访问。
一旦集成了动态分辨率渲染方法,使用超采样就变得非常简单。我们鼓励开发者考虑这一点,因为它可能有利于较小的屏幕尺寸和未来能够以超过其最高质量运行游戏的硬件。
渲染目标清除
由于动态分辨率渲染并不总是使用整个渲染目标表面,因此仅清除所需部分可能会有所帮助。该示例实现了一个像素着色器清除,并且在测试的 Intel® HD Graphics 3000 系统上,当动态比例小于 0.71 倍(对于 1280x720 后备缓冲区)时,像素着色器清除的性能优于标准清除。在许多情况下,可能不需要清除渲染目标,因为它们在每一帧都会被完全覆盖。
深度缓冲区仍应使用标准清除方法完全清除,因为这些可能实现分层深度。一些多重采样渲染目标也可能使用压缩,因此应正常清除。
性能缩放
尽管由于场景中没有细节级别(level of detail)和仅进行了非常简单的剔除,顶点处理负载很重,但示例代码在分辨率方面的缩放效果很好。这使得所选的控制方法在维持所需帧率方面具有很大的优势。
大多数游戏使用细节级别机制来控制顶点负载。如果这些与对象在屏幕上的近似大小相关联,则性能缩放效果会更大。
分辨率控制
除了允许手动控制外,该示例还实现了一种分辨率控制方法。代码位于 DynamicResolutionRendering.cpp 文件中,函数名为 ControlResolution。目标性能可以在刷新率(通常为 60Hz 或 60FPS)和刷新率的一半(通常为 30FPS)之间选择。
控制方案很简单:分辨率缩放增量与目标帧时间和当前帧时间之间的无量纲差值成比例计算。
其中 *S'* 是新的分辨率比例,*S* 是当前分辨率比例, 是缩放增量,*k* 是变化率常数,*T* 是目标帧时间,*t* 是当前帧时间。
当前帧时间使用 GPU 内部帧时间(不包括使用 Microsoft DirectX* 查询计算的 present)的平均值,以及以正常方式计算的帧间隔时间。当垂直同步开启时,需要 GPU 内部帧时间,因为在这种情况下,帧时间被限制在同步速率,但我们需要知道实际渲染时间是否短于该速率。与实际帧率平均有助于考虑 present 以及一些 CPU 帧工作负载。如果实际帧时间远大于 GPU 内部帧时间,则会忽略这种情况,因为这通常是由于 CPU 端出现峰值,例如从窗口模式切换到全屏模式。
潜在改进
以下列表绝不完整,只是作者认为可以自然地扩展当前工作的某些功能
- 将动态分辨率场景渲染与类似的阴影图方法结合起来。
- 将此技术与粒子系统的单独控制机制结合使用,允许在只渲染少量小粒子时提高质量,并在填充率增加时提高性能。
- 该技术与其他抗锯齿技术兼容,这些技术也可以与时间抗锯齿一起应用。
- 时间抗锯齿可以使用改进的加权和,该加权和取决于当前帧和前一帧的像素中心距离,而不是简单的求和混合。也可以使用速度相关的偏移读取,例如 CryENGINE* 3 中使用的 [Crytek 2010]。
- 某些游戏可能受益于在图像的一小部分区域运行更高质量的抗锯齿技术,例如主要角色或鼠标高亮的 RTS 单位。
结论
动态分辨率渲染为开发者提供了在用户干预最少的情况下提高整体质量的工具,尤其是在与时间抗锯齿结合使用时。鉴于 PC GPU 市场的巨大性能范围,我们鼓励开发者使用此技术作为实现游戏所需帧率的方法之一。
参考文献
- [Sigg 2005] Christian Sigg, Martin Hadwiger, "Fast Third Order Filtering", GPU Gems 2. Addison-Wesley, 2005.
- [Crytek 2010] HPG 2010 "Future graphics in games", Cevat Yerli & Anton Kaplanyan. http://www.crytek.com/cryengine/presentations
- [GDC Vault 2011] http://www.gdcvault.com/play/1014646/-SPONSORED-Dynamic-Resolution-Rendering
- [Intel GDC 2011] http://software.intel.com/en-us/articles/intelgdc2011/
- [Andreev 2011] http://www.gdcvault.com/play/1014550/Anti-aliasing-from-a-Different [PPT 4.6MB]