自2021年8月发布以来,大逃杀类新作《永劫无间》在全球掀起了一波又一波的游戏狂潮。游戏源自中国玄幻的美术风格给世界各地的玩家们留下了极为深刻的印象。
作为网易的独立子公司,作品不断的24 Entertainment工作室在其首款桌面端游戏《永劫无间》就搏得了开门红,迅速吸引了全球玩家的目光。游戏在发布第一周内便登上Steam排行榜前10名,活跃玩家数要比《彩虹六号:围攻》、《Splitgate》等热门游戏还要多。
这场大逃杀式的动作冒险将场景设立在五光十色的山巅、郁郁葱葱的森林和断壁残垣的古城中,每一个都蕴含了惊人的细节。为了抓住环境的美感,以足够的性能和帧率支撑多达60人的多人游戏,24 Entertainment与知名技术企业NVIDIA和Unity展开了亲密合作。
在与NVIDIA的合作中,24 Entertainment提前用上了深度学习超采样 (DLSS) 技术:一项以渲染高帧率、高分辨率实时图形为目的的新渲染技术。DLSS可借助人工智能举重若轻地增强图形性能和整体质量。
为了维持高性能,《永劫无间》以低分辨率渲染,避免了像素着色计算等流程。在运行期间,DLSS将利用神经网络生成高分辨率图像,为玩家们保留美术细节。这样一来,游戏不仅能生成高质量的图像,还能借助人工智能填补图像缺失,使得渲染速度提高几乎一倍,这对于如此大体量的多人竞技游戏非常关键。
在DLSS的帮助下,24 Entertainment成功实现了高帧率、高分辨率和高清细节。可以说,DLSS的4K几乎可以比肩原生4K。
经过训练的AI能参考前几帧画面进行渲染,辅助抗锯齿等功能。并且,同一游戏的神经网络模型无须再度训练即可处理各种画面。
在几年前《永劫无间》的开发初期,团队在Unity可编程渲染管线 (SRP,一种支持添加自定义C#渲染架构的管线)的基础上建立了自己的渲染管线。
在NVIDIA的Developer Relations专家及Unity Core Support的支持下,24 Entertainment还率先在Unity中应用了DLSS技术。
为了帮助其他开发者更深入地了解DLSS在实时环境下的运作机制,《永劫无间》的图形开发团队披露了部分技术应用细节、提示及开发时遇到的挑战。
DLSS应用的第一步是在低分辨率图像中进行高采样。
为了降低采样对最终画面的影响,24 Entertainment将这一步放在了泛光、色调映射、特效光等后处理效果之前,所有后处理效果都应用在了采样后的高清图像上。
整个管线的运行流程如下:
第1步:将画面设为高质量模式,再使用NVIDIA的getOptimalsettings接口来计算输入数据大小和最佳清晰度。不同的质量模式有着不同的图像缩放比例。
第2步:使用NVIDIA的CreateFeature接口在各个摄像机中抓取特征,据此设置质量模式、输出图像大小及锐化程度。锐化后的输出图像可包含更多细节。
第3步:在后处理之前使用以下代码执行DLSS推算:
commandBuffer.ApplyDLSS(m_DLSSArguments);
团队稍微调整了渲染管线来保证输入图像可以兼容DLSS。
由于低分辨率图像需要按一定比例进行放大,游戏窗口需要缓存到G-Buffer(Geometry Buffer,包含色彩、法线、坐标信息的缓存纹理)中,才能正确在管线中行进,而窗口必须以正确的比例重新创建。
pixelRect = new Rect(0.0f, 0.0f, Mathf.CeilToInt(renderingData.cameraData.pixelWidth * viewportScale), Mathf.CeilToInt(renderingData.cameraData.pixelHeight * viewportScale)); commandBuffer.SetViewport(pixelRect); // RenderScale Supported
渲染完成后,DLSS的源图像大小、目标大小、目标色彩渲染和目标深度等参数将根据低分辨率原图进行设定。
int scaledWidth = UpSamplingTools.Instance.GetRTScaleInt(cameraData.pixelWidth); int scaledHeight = UpSamplingTools.Instance.GetRTScaleInt(cameraData.pixelHeight);
// Set the argument m_DLSSArguments.SrcRect.width = scaledWidth; m_DLSSArguments.SrcRect.height = scaledHeight;
m_DLSSArguments.DestRect.width = cameraData.pixelWidth; m_DLSSArguments.DestRect.height = cameraData.pixelHeight;
m_DLSSArguments.InputColor = sourceHandle.rt; m_DLSSArguments.InputDepth = depthHandle.rt;
接下来的问题是输入图像的Jitter Offset,这一步与时域上的样本积累有关。
在渲染时,着色器如果只渲染三角形所覆盖的像素,会导致光栅化图元变得分散,生成有锯齿、不自然、边缘不光滑的图像。
如果渲染的分辨率更高,则图像亦会变得精细、自然起来。然而,如果缺少分散的图像样本,要想生成连续的图像信号会变得非常困难,更有可能产生锯齿。
在4K分辨率下,锯齿现象的确可以通过提高分辨率来缓解。但在8K分辨率下,此方法会导致渲染速度慢近四倍,并且很可能会产生纹理带宽(内存使用)的问题。
另一种常见的抗锯齿方法是由GPU硬件驱动的多重采样抗锯齿(Multisample Anti-aliasing,常称为MSAA)。MSAA除了检测像素的中心点外,还会检测亚像素位置的样本。三角形片元的色彩将根据图元覆盖的样本数量进行调整,来让图像边缘更显平滑。
时域抗锯齿 (TAA) 是另一种跨帧累积样本的方法。该方法将在每一帧上抖动采样位置,接着利用运动矢量混合帧之间的渲染色彩。
如果每个帧像素过去的色彩都可被识别,我们就可以利用这些过去的像素进行抗锯齿。
抖动(Jitter)通常是指像素采样位置的轻微调整,以达到累积多帧样本的目的,免去了一次性解决欠采样的必要。
24 Entertainment之所以转而采用DLSS,就是因为它不仅降低了渲染分辨率的要求,而且还生成边缘平滑的高质量图像。
我们推荐在DLSS中使用的采样模式包括Halton序列,一种看似随机、覆盖更均匀的低离散度序列。
在实践中,Jitter Offset的应用可以非常简单。要想实现高效的应用,可以参考以下步骤:
第1步:在特定摄像机中根据Halton序列生成不同图像设定下的样本。输出信号的抖动量应该在-0.5到0.5之间。
Vector2 temporalJitter = m_HalotonSampler.Get(m_TemporalJitterIndex, samplesCount);
第2步:将抖动量存储到一个Vector4中,将其乘以2、再除以缩放后的分辨率,来将其应用到屏幕空间的单位像素上。将结果存储到zw组件中。
然后,使用这两个值修改投影矩阵、改变总体的渲染结果:
m_TemporalJitter = new Vector4(temporalJitter.x, temporalJitter.y, temporalJitter.x * 2.0f / UpSamplingTools.GetRTScalePixels(cameraData.pixelWidth), temporalJitter.y * 2.0f / UpSamplingTools.GetRTScalePixels(cameraData.pixelHeight) );
第3步:将视图投影矩阵设置为全局属性UNITY_MATRIX_VP。顶点着色器会调用相同的函数来转换屏幕上的世界位置,而将矩阵归入该属性可免去修改着色器的必要。
var projectionMatrix = cameraData.camera.nonJitteredProjectionMatrix; projectionMatrix.m02 += m_TemporalJitter.z; projectionMatrix.m12 += m_TemporalJitter.w;
projectionMatrix = GL.GetGPUProjectionMatrix(projectionMatrix, true); var jitteredVP = projectionMatrix * cameraData.viewMatrix;
在解决Jitter Offset问题后,接下便是借助工具生成运动矢量。
运动着的摄像机和对象会改变屏幕图像,而要根据多帧样本来生成图像,则我们还需要抓取对象的前一位置。
例如,当摄像机的位置从q变为p时(如图所示),屏幕上的某个点很可能会被投射到另一个点上。将这两个点相减便能得到此次运动的矢量,即前一帧相对于当前帧的位置。
运动矢量可采用如下步骤计算:
第1步:在管线的深度信息通道中计算对象的运动矢量。通道中的深度信息主要用于描绘对象距摄像机的距离,辅助系统检测“景深”。
第2步:根据前一帧的摄像机View Projection矩阵移动窗口、抓取前一窗口位置,再填补新像素。
第3步:在DLSS中写入矢量相关的属性。
我们可以根据不同的清晰度(分辨率)来计算对应的运动矢量,通过按比例缩放矢量来实现想要的结果。这里,24 Entertainment将矢量设为了负的宽高比。DLSS要求运动矢量以像素为单位,而团队在屏幕空间中生成运动矢量,
之所以使用负号,是为了在管线中互换减数(当前帧)和被减数(前一帧)的位置。
m_DLSSArguments.MotionVectorScale = new Vector2(-scaledWidth, -scaledHeight); m_DLSSArguments.InputMotionVectors = motionHandle.rt;
24 Entertainment的渲染管线结合了延迟渲染和前向渲染。为了节省内存,所有渲染对象都在放大之前作了分配。
DLSS管理器会使用RTHandle系统在摄像机创建之际分为渲染对象分配一次内存,省去为每次摄像机循环都分配一次内存的必要。
再到深度通道中生成运动矢量(仅包括运动对象),来实现时域上的Jitter Offset等效果。接着,在屏幕通道中生成摄像机的运动矢量。
DLSS管理器支持在后处理开始时使用RTHandle 系统为放大后的渲染对象分配内存。
到这里,DLSS推算已经能获取除信号抖动以外所有参数信息了,
再加上信号抖动就可以实现时域化采样了。
《永劫无间》的渲染管线带有一个可以缓存摄像机Halton采样阶段索引和矩阵抖动与否等信息的系统,并且所有光栅化步骤都采用了View Projection和Jitter矩阵,除了矢量通道。矢量通道采用的是无抖动的矩阵。
Mip Map Bias: Mip Map是一种预先计算好的、分辨率逐级递减(后者为前者的二分之一)的图像序列。它们可以有效地逐级过滤纹理,然后在原纹理中对所有组成屏幕像素的纹素进行采样。
在下例中,远离摄像机的对象会在低分辨率的Mip Map中被采样,从而生成更符合实际的结果。
在为DLSS采集纹理样本时,一定要添加Mip Map Bias。DLSS与棋盘渲染等其他类似的上采样方法一样,需要在较高的分辨率下进行采样才能渲染出低分辨率的窗口图像。这时,在高分辨率下还原出的纹理不会显得模糊。
Mip Map Bias可使用以下方法计算:
MipLevelBias = log2(RenderResolution.x / DisplayResolution.x)
如果输出为负,你可以利用偏差值回滚为更高分辨率的图像。只有设置了Mip Map Bias,DLSS才能在采集低分辨率纹理样本时维持图像质量不变。
我们应该在摄像机上缓存抓取的DLSS特征。
为了反映摄像机尺寸和质量模式的变更,DLSS必须抓取新的特征。有时,多个大小和质量模式相同的摄像机需要同时使用DLSS渲染。但每个特征都是根据前一帧的信息总结而来,某个特征只能适用于特定摄像机。
在《永劫无间》中,24 Entertainment以每台摄像机的哈希值作为键值将各个特征缓存到了一个字典中,藉此保证每个特征只能被对应的摄像机使用。
commandBuffer.ApplyDLSS(m_DLSSArguments, cameraDescriptor);
切忌将DLSS与其他抗锯齿方法混合使用。最后,不要将DLSS与其他抗锯齿方法组合使用。作为一种新技术,DLSS与其它抗锯齿方法一同使用可能会产生不可预测的瑕疵。
Unity与NVIDIA有着紧密的技术合作关系,而Unity引擎和HDRP都已经集成了DLSS及上述所有功能,并且进一步的集成开发也将展开!
Unity将自2021.2版本起支持并维护所有NVIDIA技术。以下为NVIDIA技术集成至Unity的详情:
引擎核心的DLSS集成
在引擎内核中,我们专门为DLSS模块编写了一层恰当的C# API。这一层代码还负责处理平台兼容性、引擎内的#define(用于在运行平台上屏蔽特定的DLSS代码)以及提供正确的说明文档,它属于在SRP中调取完整DLSS的官方API,也是其它可编程渲染管线的整合基础。
HDRP中的图形集成
DLSS以动态分辨率系统(DRS) 功能的形式应用在了HDRP中,功能也使用了上述的脚本API。
我们以《永劫无间》的 TAA 抖动算法为基础,在后处理之前类似地应用了分辨率上采样,再并以全分辨率渲染后处理效果。
并且Unity的DLSS还有了一点改进:应用以动态分辨率系统为基础,允许实时切换分辨率。动态分辨率系统可完全兼容RTHandle系统,并支持硬件驱动的DLSS(基于纹理锯齿)及软件驱动的分辨率修改(基于游戏窗口)。而前边提到的Mip Map Bias功能可用于所有DRS过滤器和技术。
为了减少粒子重影现象,我们建立了一个特殊的渲染通道,来保证动态分辨率系统与其它HDRP功能的兼容。
此外,我们还建立了一个包含DLSS版本信息和帧状态等信息的调试面板。
最后,DLSS现在支持VR、实时光追、DX11、DX12和Vulkan。
启用DLSS的方法如下:
2. 在主摄像机中启用DLSS。
更多使用详情,请参阅DLSS编程支持和DLSS support on HDRP文档。
Is this article helpful for you?
Thank you for your feedback!