[Render] Unity SRP 概述:可编写脚本的渲染管道

56 篇文章 2 订阅

英文原文:https://thegamedev.guru/rendering/unity-srp-overview-scriptable-render-pipeline/

在这里插入图片描述
  我决定做一些研究并编写即将推出的实验性 Unity 功能:Scriptable Rendering Pipelines。 为什么? 因为它关乎你,它关乎我。 但不要惊慌。 或者至少,现在还没有。 也许甚至明年都不会,但它最终会改变你的工作方式。 你越准备好,你就会越好。

1.什么是SRP?

  Scriptable Render Pipeline (SRP) 是一种新的 Unity 系统和思维方式,它允许任何图形程序员开发自定义的渲染循环。 这意味着,您将能够调整、减少、扩展游戏创建帧的方式。 这将增加您优化游戏、创建自定义视觉效果、使您的系统更易于维护、修复直到现在无法修复的错误的潜力,但它的主要优势在于它可以让您更详细地了解图形的工作原理。 这个想法基本上与(传统)黑盒内置渲染器相反,Unity 垄断了所应用的渲染算法。

  该技术随 Unity 2018.1 Beta 一起提供。 不过要小心,它仍处于试验阶段,可能会保持这种状态一段时间。 它的主要支柱是与 C++ 引擎紧密绑定的 C# API。 API 很可能在功能开发过程中发生变化。 它背后的主要赌注是,您将对游戏将执行的渲染过程进行更细粒度的控制。

Unity 在其代码中提供了两个渲染管道:

  • LWRP:轻量级渲染管线
  • HDRP:高清渲染管线

为了理解 SRP,有必要了解典型的每相机渲染过程的整体情况:

  1. Culling
  2. Drawing
  3. Post-processing

如果您了解这些方面,请随时跳过下一部分。

1.1 剔除

  帧的渲染通常从剔除开始。 让我们从一个非正式但简单的定义开始,这将有助于我们暂时理解它。CullingCPU 过程包括获取可渲染对象并根据相机的可见性标准对其进行过滤,以生成要渲染的对象列表。

  可渲染对象基本上是带有渲染器组件(如 MeshRenderer)的游戏对象,过滤只是意味着它是否会包含在列表中。 但是请注意,真正的剔除过程也会将灯光添加到等式中,但我们将跳过这些。

  剔除很重要,因为它大大减少了 GPU 必须处理的数据量和指令量。 如果我们在洞穴中,渲染一架正在运行的飞机可能没有意义,因为我们看不到它(但它可能会,例如它在洞穴内投射阴影)。 剔除本身需要一些处理器时间,这是引擎在平衡 CPU/GPU 负载时必须注意的事实。

  传入的几何体/灯光 + 相机设置 + 剔除算法 = 要绘制的渲染器列表

在这里插入图片描述

1.2 渲染

  在我们确定应该显示哪些数据之后,我们就去做吧。 一个常见的过程可以总结为以下步骤:

  1. 清除(返回)缓冲区内容(CPU) 我们丢弃之前生成的缓冲区。 它通常是颜色和深度缓冲区,但它可能包括用于其他技术(例如延迟着色)的自定义缓冲区。
  2. 排序 (CPU) 对象根据渲染队列进行排序(例如,从前到后不透明,从后到前透明以进行正确混合)。排序 (CPU) 根据渲染队列对对象进行排序(例如,从前到后不透明, 并且从后到前透明以进行适当的混合)。
  3. 动态批处理 (CPU) 我们尝试将渲染器组合为一个对象,这样我们就可以节省绘制调用。 此优化是可选的。
  4. 命令(绘制调用)准备和调度(CPU) 对于每个渲染器,我们准备一个绘制命令及其几何数据:顶点、uv 坐标、顶点颜色、着色器参数(如变换矩阵(MVP)、纹理 ID 等)。 沿着其数据的指令被提交给 API,API 将与驱动程序一起将这些原始信息打包并正确格式化为适合 GPU 的数据结构。
  5. Render pipeline (GPU) 非常粗略的描述:GPU接收并处理命令; 然后 GPU 前端组装几何体,执行顶点着色器,光栅化器启动,片段着色器完成它们的工作,GPU 后端管理混合,渲染目标并将所有内容写入不同的缓冲区。
  6. 等待 GPU 完成并交换缓冲区
    根据 VSync 设置,它甚至可能等待更长的时间来执行前后缓冲区交换。

这是一个过度简化的典型渲染过程。

在这里插入图片描述
在 GPU 填充缓冲区(颜色、深度和可能的其他)之后,开发人员可能会选择应用进一步的图像增强功能。 它们包括将着色器应用于输入纹理(创建的缓冲区)以用校正后的图像覆盖它们。 下面列出了一些:

效果描述性能成本
Bloom它突出了明亮的发光区域,在源周围营造出一种光环效果中等的
景深 (DoF)根据设置的参数模糊屏幕的某些部分昂贵的
SS 抗锯齿柔化由有限分辨率产生的像素颜色之间的突然过渡轻到贵
色彩校正根据定义的规则更改颜色的行为
SS 环境光遮蔽添加接触阴影(它使对象之间的区域变暗)中等的

请注意,性能成本实际上取决于平台,但作为一般规则,后期效果对于移动设备来说是令人望而却步的。

  它们昂贵的一个原因是每个生成的片段通常需要从帧缓冲区(在集成 GPU 的 RAM 中)进行多次读取,进行一些计算,然后覆盖缓冲区。 如果将此过程添加到多个后期效果中,由于生成的过度绘制,您最终会使用过多的内存带宽。
在这里插入图片描述
现在我们有了初步的了解,回到我们的 SRP 主题。 还在我这儿? 我们为什么要学习 SRP? 它将如何影响您?

2. 为什么选择 SRP?

  主要问题是 Unity 的内置渲染器是单一的、巨大的黑盒渲染管道,它考虑了每个用例。 让它如此通用需要付出很大的代价:

  • 很难优化。
  • 在不破坏当前项目的情况下很难改变。
  • 它应该保持与以前版本的兼容性。
  • 很难进行自定义渲染过程和微调项目。
  • 自定义渲染代码容易产生难以追踪的副作用。
  • 大工作室害怕受到限制。

  这就是我打赌 Unity 决定采用 SRP 的原因。 这是一个重大举措,因为您可以在资产商店中找到的大量软件包需要进行调整才能与 SRP(场景光强度、材质、着色器等)一起使用。

  SRP 的优点基本上与其缺点相反,还有其他一些额外的好处,例如可以使用即将推出的工具,例如用于图形着色器编程的 Shadergraph(最终使使用表面着色器变得罕见)。 在我看来,最大的优势之一是通过了解渲染的工作原理,您将获得大量的学习。


基本 SRP

  我基于 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 可以将其用于上述任务。

在这里插入图片描述
创建 SRP 工厂代码后,现在我们进入真正的实现:

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(轻量级渲染管线):

  • 针对中低端设备的特定渲染算法
  • 内置渲染器的剥离版本
  • 以缩放分辨率渲染游戏
  • UI 以原始分辨率呈现
  • 没有实时GI

  你可以在网上找到官方内置渲染器与 LWRP 的比较以及 LWRP 源代码,如果你有一些(实际上,很多)空闲时间,我建议你看看。 还要检查比较,因为每次他们改变主意时我都不会修改此博客条目。

  HDRP(高清渲染管线)针对高端设备(台式机、PS4/XBO)并提供开箱即用的更好质量,包括延迟着色、TAA、HDR、PPAA、SSR、SSS 等。 喝杯茶,享受一段轻松而又令人兴奋的源代码阅读时光。

性能

  很抱歉让您失望了,但是现在用一个经常变化的高度不稳定的 API 进行基准测试是不可行的。 我从人们对 HDRP 和 LWRP 与内置渲染器的基准测试中发现的结果不一致,几乎没有可比性。 我将在以后的文章中介绍这一点,但由于我在上面几节中提到的原因,预计它会变得更好。

总结

  我们已经看到了渲染是什么样子,为什么 Unity 决定实现如此大的功能来支持弃用当前系统,Unity 是如何做到的,以及从今天开始渲染的开箱即用的可能性。 你可能已经意识到这篇文章是多么的简单,但是写得更彻底会让大多数读者望而却步。

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值