研发实战:Oculus分享美术工具色调映射解决方案Graphics Showcase
Oculus相信色调映射是一个重要的美术工具
(映维网 2021年08月19日)色调映射可以提供有用的各种全屏效果,包括gamma校正、颜色分级Lookup Tables(LUT)、color tint、渐晕、全屏淡入和其他。色调映射效果在渲染场景后利用应用于颜色缓冲区的全局着色器。通常,这需要额外的渲染过程。所以,大多数Quest开发者不使用色调映射,因为额外渲染过程的性能成本太高。UE4同样要求Mobile HDR(Quest或Quest 2不支持)使用色调映射效果。
Oculus相信色调映射是一个重要的美术工具,并构建了一个包含基于子通道且性能要优于传统技术的解决方案的Graphics Showcase。在特定配置中,色调映射子通道增加的时间仅为0.6 ms,与在第二渲染通道中仅存储颜色操作所需的时间相当(请注意,所述引擎改动仅支持Oculus fork of UE 4.26)。日前,团队的扎克·德雷克(Zac Drake)和洛伦·麦克夸德(Loren McQuad)撰文介绍了所述的Graphics Showcase。下面是映维网的具体整理:
Vulkan提供子通道以作为执行附加渲染操作的一种方式,无需完整的渲染通道。尽管子通道存在一定的限制,但它们非常适合色调映射。如果我们使用一个单独的渲染过通道,颜色数据将在第一个渲染通道结束时写入RAM,然后从RAM读回。利用子通道,我们可以访问依然在Tile Memory中的前一个子通道中的颜色数据,并避免写入和读取操作的延迟。子通道获取的一个限制是:我们只有当前像素坐标的数据,所以我们只能使用当前像素的数据应用效果。Bloom、模糊或景深等效果不适用于基于子通道的色调映射,因为它们需要来自周围像素的颜色数据。
在Graphics Showcase中,你可以看到色调映射子通道的三个示例。以下是未应用色调映射时的画面。
没有应用色调映射
如果看起来熟悉,那是因为它是在2016年登陆Oculus Rift的《Dreamdeck》。
下面是一个简短的演示视频:
要查看没有色调映射的场景,请按A将其关闭或重新打开。这将触发蓝图逻辑以设置“r.Mobile.TonemapSubpass=1”或“r.Mobile.TonemapSubpass=0”。你同时可以创建自己的逻辑,以便在运行时禁用色调映射。例如,如果仅在关卡开始和结束时使用色调映射进行淡入淡出,则可以在剩余时间禁用子通道。或者,如果希望它始终处于启用状态,只需在DefaultEngine.ini中设置“r.Mobile.TonemapSubpass=1”。
我们的每个效果都利用Post Process Volumes来控制其参数。对于Post Process Volumes的属性,你将看到一长串功能。并非所有都可以由Vulkan子通道支持,我们只实现了部分。但有了这个框架,你不需要花费太多的精力来实现其他功能,或者你可以通过自定义色调映射着色器来获得目标的效果。对于Graphics Showcase,我们利用多个Post Process Volumes来支持动态切换效果。如果你没有动态切换,一个Volume就足够。确保Volume已启用并设置为无限。
1. 淡入/淡出
你在Graphics Showcase中看到的第一个效果是淡入/淡出。它是通过关卡序列中的淡入淡出轨迹实现。当场景开始时,它从黑色淡入,然后淡出。如果没有色调映射,淡入淡出轨迹将不起作用,而且你必须使用包含透明纹理的camera attached polys来产生这种类型的淡入淡出。有了色调映射,只需将淡入淡出轨迹和几个关键点添加到关卡序列即可。
2. Color Tint昼夜循环
下一个效果再次使用关卡序列来修改模拟昼夜循环的Color Tint值。关卡的不同颜色键设置Tint值。当序列播放时,Tint在所述值之间混合,给人黎明和黄昏的感觉。你可以直接设置“Color Tint”值,而不是使用关卡序列。选择 PostProcess_DayNightCycle并依次导航Color Grading > Misc > Scene Color Tint。
3. Color Grading LUT颜色分级LUT
最后一个效果是使用LUT进行颜色分级。这允许我们完全改变游戏的调色板。LUT可以由美术根据Unreal文档创建,或利用Unreal Marketplace的数百个预制LUT。将LUT纹理添加到项目内容中,然后依次导航到Color Grading > Misc > Color Grading LUT下的Post Process Volume。
点击控制器B键循环播放效果*
色调映射颜色分级LUT(深褐色和淡红色)
4. 实现细节
如果你对所需的引擎改动好奇,这里是High Level的概述。
4.1 Subpass Hint
UE4使用subpass hint告知RHI启用了哪个子通道。正向渲染器通常启用ESubpassHint::DepthReadSubpass。我们添加了一个新的subpass hint ESubpassHint::MobileTonemapSubpass。尽管ESubpassHint是一个枚举,但它的设置不支持多个hints。我们将ESubpassHint改为位掩码,以允许同时运行两个子通道。
4.2 SubpassFetch
SubpassFetch用于从上一个子通道的tile memory中检索颜色数据。尽管UE4中已经存在这个功能,但它不支持MSAA。我们通过添加SubpassFetchMS来扩展这一点。SubpassFetch和SubpassFetchMS都是利用subpassInput和subpassInputMS uniforms types的内在函数。接下来,我们设置一个预处理器指令,以便像素着色器知道需要多少采样。在着色器中,我们平均采样的场景颜色值,执行自己的resolve。这导致了下一个问题。
4.3 Handling Num Samples
通常,resolve阶段会处理MSAA。由于我们在着色器内部进行resolve,所以需要渲染通道从2个或4个采样开始,然后在色调映射子通道的输出切换到1个采样。这方面的处理是检查色调映射子通道是否已启用,并在Vulkan管道中将光栅化采样设置为1。这允许我们的子通道接收2或4个采样,并仅输出1个。
4.4 Resolve Texture for nonMSAA
不使用MSAA时,没有要输出到的resolve纹理。我们的子通道不能将场景颜色纹理同时用作输入附件和颜色附件,所以我们修改了逻辑,以便在启用色调映射子通道时为非SAA创建resolve纹理。颜色纹理和resolve纹理的ERenderTargetActions分别为Clear_DontStore和Clear_Store。只有resolve纹理写入RAM。我们在RHIEndRenderPass中禁用了对RHICopyToResolveTarget的调用,因为这将覆盖子通道刚刚输出到的帧缓冲区。通过使用非MSAA的resolve纹理,我们避免了创建第二个颜色纹理附件,并保持代码path与MSAA path几乎相同。
4.5 PostProcessTonemapSubpass Pixel Shader
我们在PostProcessTonemap.usf中基于MainPS_Mobile创建了一个新的像素着色器。这提供了Post Process Volume中的参数,允许我们从编辑器GUI控制效果。除子Passfetch和resolve逻辑(平均采样数)外,大多数逻辑都是从MainPS_Mobile复制。
4.6 Color Grading LUT
着色器中的另一大区别是颜色LUT逻辑。移动色调映射着色器中不予支持,所以我们从非移动版本复制。在色调映射着色器可以使用LUT纹理之前,它将通过组合LUT着色器运行。这作为一个额外的渲染通道添加,但比我们的完整显示器小得多(1024x32),并且对性能的影响最小。从组合LUT通道输出的颜色分级纹理随后传递到色调映射子通道。请注意,左侧和右侧显示的LUT纹理相同,所以每帧只能渲染一次。
4.7 Beware of Invalid Subpasses
必须非常小心处理子通道依赖项、颜色actions和输入附件。如果设置不正确,图像可能会正确渲染,但会作为附加渲染通道而非子通道运行。这将失去所有性能优势。始终验证颜色数据仅存储一次。你可以通过在adb shell中运行“ovrgupprofiler-t”进行检查,并确保一个Render后面是一个StoreColor。
雷米·帕兰德里(Rémi Palandri)在这篇贴文中提供了更多的细节。
5. 剖析数据
现在让我们看看额外的子通道是如何影响性能的。下表显示了VRAPI logcat输出的GPU定时数据。
2x MSAA FFR3是我们的子通道的最佳选择,它使帧持续时间增加不到1毫秒。GPU能够在不暂停的情况下执行2次并行纹理抓取,但不能执行4次。所以,切换到4x MSAA时效率降低。
从数据中可以看出,使用Vulkan子通道提供了一个在Quest中应用色调映射的性能优化选项。有了色调映射,你可以改变游戏的整体画面效果。我们希望这个Graphics Showcase能够鼓励你在下一款游戏中添加色调映射。