英文原文:https://thegamedev.guru/rendering/unity-srp-overview-scriptable-render-pipeline/
我决定做一些研究并编写即将推出的实验性 Unity 功能:Scriptable Rendering Pipelines。 为什么? 因为它关乎你,它关乎我。 但不要惊慌。 或者至少,现在还没有。 也许甚至明年都不会,但它最终会改变你的工作方式。 你越准备好,你就会越好。
Scriptable Render Pipeline (SRP) 是一种新的 Unity 系统和思维方式,它允许任何图形程序员开发自定义的渲染循环。 这意味着,您将能够调整、减少、扩展游戏创建帧的方式。 这将增加您优化游戏、创建自定义视觉效果、使您的系统更易于维护、修复直到现在无法修复的错误的潜力,但它的主要优势在于它可以让您更详细地了解图形的工作原理。 这个想法基本上与(传统)黑盒内置渲染器相反,Unity 垄断了所应用的渲染算法。
该技术随 Unity 2018.1 Beta 一起提供。 不过要小心,它仍处于试验阶段,可能会保持这种状态一段时间。 它的主要支柱是与 C++ 引擎紧密绑定的 C# API。 API 很可能在功能开发过程中发生变化。 它背后的主要赌注是,您将对游戏将执行的渲染过程进行更细粒度的控制。
Unity 在其代码中提供了两个渲染管道:
为了理解 SRP,有必要了解典型的每相机渲染过程的整体情况:
如果您了解这些方面,请随时跳过下一部分。
帧的渲染通常从剔除开始。 让我们从一个非正式但简单的定义开始,这将有助于我们暂时理解它。CullingCPU 过程包括获取可渲染对象并根据相机的可见性标准对其进行过滤,以生成要渲染的对象列表。
可渲染对象基本上是带有渲染器组件(如 MeshRenderer)的游戏对象,过滤只是意味着它是否会包含在列表中。 但是请注意,真正的剔除过程也会将灯光添加到等式中,但我们将跳过这些。
剔除很重要,因为它大大减少了 GPU 必须处理的数据量和指令量。 如果我们在洞穴中,渲染一架正在运行的飞机可能没有意义,因为我们看不到它(但它可能会,例如它在洞穴内投射阴影)。 剔除本身需要一些处理器时间,这是引擎在平衡 CPU/GPU 负载时必须注意的事实。
传入的几何体/灯光 + 相机设置 + 剔除算法 = 要绘制的渲染器列表
在我们确定应该显示哪些数据之后,我们就去做吧。 一个常见的过程可以总结为以下步骤:
这是一个过度简化的典型渲染过程。
在 GPU 填充缓冲区(颜色、深度和可能的其他)之后,开发人员可能会选择应用进一步的图像增强功能。 它们包括将着色器应用于输入纹理(创建的缓冲区)以用校正后的图像覆盖它们。 下面列出了一些:
效果 | 描述 | 性能成本 |
---|---|---|
Bloom | 它突出了明亮的发光区域,在源周围营造出一种光环效果 | 中等的 |
景深 (DoF) | 根据设置的参数模糊屏幕的某些部分 | 昂贵的 |
SS 抗锯齿 | 柔化由有限分辨率产生的像素颜色之间的突然过渡 | 轻到贵 |
色彩校正 | 根据定义的规则更改颜色的行为 | 轻 |
SS 环境光遮蔽 | 添加接触阴影(它使对象之间的区域变暗) | 中等的 |
请注意,性能成本实际上取决于平台,但作为一般规则,后期效果对于移动设备来说是令人望而却步的。
它们昂贵的一个原因是每个生成的片段通常需要从帧缓冲区(在集成 GPU 的 RAM 中)进行多次读取,进行一些计算,然后覆盖缓冲区。 如果将此过程添加到多个后期效果中,由于生成的过度绘制,您最终会使用过多的内存带宽。
现在我们有了初步的了解,回到我们的 SRP 主题。 还在我这儿? 我们为什么要学习 SRP? 它将如何影响您?
主要问题是 Unity 的内置渲染器是单一的、巨大的黑盒渲染管道,它考虑了每个用例。 让它如此通用需要付出很大的代价:
这就是我打赌 Unity 决定采用 SRP 的原因。 这是一个重大举措,因为您可以在资产商店中找到的大量软件包需要进行调整才能与 SRP(场景光强度、材质、着色器等)一起使用。
SRP 的优点基本上与其缺点相反,还有其他一些额外的好处,例如可以使用即将推出的工具,例如用于图形着色器编程的 Shadergraph(最终使使用表面着色器变得罕见)。 在我看来,最大的优势之一是通过了解渲染的工作原理,您将获得大量的学习。
我基于 Unity 示例编写了一个简单的 SRP,以展示创建自定义渲染算法是多么容易(但没用?)。 它首先为一个可脚本对象编写代码,该对象将作为 Unity 在启动时实例化我们的 SRP 的工厂:
[CreateAssetMenu(menuName = "SRP/Create RubenPipeline")]
public class RubenPipelineAsset : RenderPipelineAsset
{
[SerializeField] private Color _clearColor;
protected override IRenderPipeline InternalCreatePipeline()
{
return new RubenPipelineImplementation(_clearColor);
}
}
我添加了一个虚拟的可选变量,它指示要使用的清晰颜色。 创建脚本对象实例后,您最终必须在图形设置中分配它,以便 Unity 可以将其用于上述任务。
public class RubenPipelineImplementation : RenderPipeline
{
private Color _clearColor;
public RubenPipelineImplementation(Color clearColor)
{
_clearColor = clearColor;
}
public override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
{
base.Render(renderContext, cameras);
RenderPipeline.BeginFrameRendering(cameras);
// You can sort the cameras however you want here
foreach (var camera in cameras)
{
RenderPipeline.BeginCameraRendering(camera);
renderContext.SetupCameraProperties(camera);
// Clear
var cb = new CommandBuffer();
cb.ClearRenderTarget(true, true, _clearColor);
renderContext.ExecuteCommandBuffer(cb);
// 1. Cull
CullResults cullResults;
CullResults.Cull(camera, renderContext, out cullResults);
// 2. Render
var drawRendererSettings = new DrawRendererSettings(camera, new ShaderPassName("BasicPass"));
var filterRenderersSettings = new FilterRenderersSettings(true);
renderContext.DrawRenderers(cullResults.visibleRenderers, ref drawRendererSettings, filterRenderersSettings);
renderContext.Submit();
}
}
}
这个过程部分对应于我们之前描述的通用渲染算法。 第一个任务是触发一些事件,以便 Unity 和第三方插件可以在渲染期间注入自定义代码:BeginFrameRendering、BeginCameraRendering。 然后,为了让 Unity 辅助函数工作,我们通过 renderContext.SetupCameraProperties 设置一些用于绘图的相机属性(矩阵、FoV、透视/正交、剪切平面等)。 然后我们清除当前颜色和深度缓冲区内容,将初始颜色设置为可编写脚本对象中提供的颜色。 我们对当前相机进行剔除处理,获取要绘制的渲染器列表。 因此,我们继续使用默认设置绘制剔除结果,并在准备好所有指令后,将它们提交给 API + 驱动程序。
在我们开始测试我们的新渲染循环之前还有一件事。 我们需要一个带有自定义着色器的自定义材质来渲染我们的几何体。 为此,我准备了一个可以使用它的简单无光照着色器。 除了通过 LightMode 通道命名我们的着色器通道之外,没有什么特别的。
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Tags { "LightMode" = "BasicPass" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
现在准备好尝试了吗? 是的!
我为此感到自豪吗? 不,但一步一步。 对于好奇的人:它在 API 方面看起来如何? 是在听我的吩咐吗? 让我们打电话给我们的#bff RenderDoc。
所以它完成了我们的要求,忽略了 Unity 编辑器细节的绘制调用。 不多也不少。 你明白为什么 SRP 很棒了吗? 全权控制,全责,最大的责备潜力! 我之前并没有真正指出,但我们使用可脚本化的对象来进行 SRP 的事实确实很强大。 您可以自定义您提供的参数并在运行时更改它们。 我还没有尝试过,但我敢打赌,我们将能够实时更改管道,让我们可以随意调整它以适应不同的设备。
Unity 带有两个预定义的可编写脚本的渲染管道,您可以使用它们而无需进一步复杂化。 事实上,建议您使用其中任何一个作为模板来个性化您自己的管道,因为创建和维护其中一个肯定很难,相信我。 让我们简要描述一下它们:
LWRP(轻量级渲染管线):
你可以在网上找到官方内置渲染器与 LWRP 的比较以及 LWRP 源代码,如果你有一些(实际上,很多)空闲时间,我建议你看看。 还要检查比较,因为每次他们改变主意时我都不会修改此博客条目。
HDRP(高清渲染管线)针对高端设备(台式机、PS4/XBO)并提供开箱即用的更好质量,包括延迟着色、TAA、HDR、PPAA、SSR、SSS 等。 喝杯茶,享受一段轻松而又令人兴奋的源代码阅读时光。
很抱歉让您失望了,但是现在用一个经常变化的高度不稳定的 API 进行基准测试是不可行的。 我从人们对 HDRP 和 LWRP 与内置渲染器的基准测试中发现的结果不一致,几乎没有可比性。 我将在以后的文章中介绍这一点,但由于我在上面几节中提到的原因,预计它会变得更好。
我们已经看到了渲染是什么样子,为什么 Unity 决定实现如此大的功能来支持弃用当前系统,Unity 是如何做到的,以及从今天开始渲染的开箱即用的可能性。 你可能已经意识到这篇文章是多么的简单,但是写得更彻底会让大多数读者望而却步。