渲染管线简介

渲染管线是计算机图形学中用于将 3D 模型转换为 2D 图像的一系列处理步骤。这个过程从读取 3D 模型的数据开始,一直到将最终的像素颜色输出到屏幕上结束。概括来说,这些操作如下:

  • 剔除
  • 渲染
  • 后期处理

这里把他们详细的分为几个阶段:

  1. 应用阶段(Application Stage)

    • 在这一阶段,开发者通过编程控制模型的变换、材质和光照设置。
    • 这里通常涉及到模型、视图和投影矩阵的设置,用于控制物体在世界中的位置、方向和大小,以及摄像机的位置和视角。
  2. 顶点着色(Vertex Shading)

    • 顶点着色器接收原始顶点数据,执行一系列数学运算,如模型变换、光照计算等,来准备顶点供下一步处理。
    • 这一步骤也常用于处理动画、皮肤和骨骼绑定。
  3. 光栅化(Rasterization)

    • 光栅化阶段将 3D 空间中的多边形转换为 2D 屏幕上的像素。
    • 这个过程中,每个多边形被分解成像素,然后这些像素被着色。
  4. 片段着色(Fragment Shading)

    • 片段着色器(也称为像素着色器)为每个像素计算最终颜色,包括光照、纹理贴图、阴影和透明度等效果。
    • 这一步骤负责产生最终的视觉细节,如高光、反射和折射。
  5. 混合(Blending)

    • 当多个片段落在同一像素上时,需要决定如何结合它们的颜色。
    • 混合可以用来处理透明物体,使它们看起来自然地与其他物体叠加在一起。
  6. 后处理(Post-Processing)

    • 后处理效果包括色彩校正、模糊、锐化、景深、镜头眩光等,这些效果通常是在整个图像上应用的。
    • 这些效果可以增强最终图像的质量和艺术感。
  7. 显示(Display)

    • 最终,所有处理过的像素被输出到屏幕上,完成从 3D 模型到可见图像的转换。

不同的渲染管线具有不同的功能和性能特征,并且适用于不同的游戏、应用程序和平台。

将项目从一个渲染管线切换到另一个渲染管线可能很困难,因为不同的渲染管线使用不同的着色器输出,并且可能没有相同的特性。因此,必须要了解 Unity 提供的不同渲染管线,以便可以在开发早期为项目做出正确决定。渲染管线特征比较

Unity 提供以下渲染管线:

  • 内置渲染管线是 Unity 的默认渲染管线。这是通用的渲染管线,其自定义选项有限。
  • 通用渲染管线 (URP) 是一种可快速轻松自定义的可编程渲染管线,允许您在-各种平台上创建优化的图形。
  • 高清渲染管线 (HDRP) 是一种可编程渲染管线,可让您在高端平台上创建出色的高保真图形。
  • 可以使用 Unity 的可编程渲染管线 API 来创建自己的自定义渲染管线

内置渲染管线

Unity 的内置渲染管线是 Unity 较陈旧的渲染管线。它并非基于可编程渲染管线。内置渲染管线专注于提供广泛兼容性和性能优化,适用于大多数平台,包括移动设备、桌面和游戏主机。

渲染路径

Unity 的内置渲染管线(Built-in Render Pipeline)提供了几种不同的渲染路径,每种路径针对不同的场景和需求进行了优化。Unity 提供了几种渲染路径包括前向渲染(Forward Rendering)、延迟渲染(Deferred Rendering)、旧版延迟 (Legacy Deferred)、旧版顶点光照。现在内建渲染管线的渲染路径主要是:前向渲染路径延迟渲染路径渲染路径比较

前向渲染(Forward Rendering)

概述

前向渲染是 Unity 中最常见的渲染路径,也是最简单的。它为每个光源生成一个渲染 Pass,这意味着如果场景中有多个光源,每个光源都会有自己的 Pass,每个 Pass 都会对可见的几何体进行渲染。

渲染流程概述

  1. 初始化:清空帧缓冲,包括颜色缓冲和深度缓冲。
  2. 顶点着色:使用顶点着色器处理每个顶点,计算变换后的顶点位置、法线和其他属性,如纹理坐标。
  3. 片段处理:对于场景中的每一个光源,进行以下步骤:
    • 将每个三角形栅格化为像素。
    • 对于每个像素,使用片段着色器计算光照效果,包括光照强度、颜色和阴影。
    • 将计算得到的颜色值和深度值写入颜色缓冲和深度缓冲,进行深度测试以确定像素是否应该被绘制。
  4. 后处理:应用任何额外的后处理效果,如模糊、色调映射或抗锯齿。
  5. 显示:将最终的颜色缓冲输出到屏幕上。

实现详细信息

在前向渲染中,场景中一些(最多 4 个)最亮的灯光将进行逐像素渲染,然后最多 4 个点光源进行逐顶点光照,其他的灯光则使用球谐函数(Spherical Harmonics)进行计算。光源是否为每像素光源根据以下原则而定:

  • Render Mode 设置为 Not Important 的光源始终为每顶点或 SH 光源。
  • 最亮的方向光始终为每像素光源。
  • Render Mode 设置为 Important 的光源始终为每像素光源。
  • 如果上面的灯光数量还没达到 QualitySetting 中设置的逐像素灯光的上限时,则其他灯光以亮度优先的方式优先作为逐像素灯光使用。

每个对象的渲染按如下方式进行:

  • 基础通道应用一个每像素方向光和所有每顶点/SH 光源。
  • 其他每像素光源在额外的通道中渲染(每个光源对应一个通道)。

Base Pass

基础通道使用一个每像素方向光和所有 SH/每顶点光源来渲染对象。此通道还会添加着色器中的所有光照贴图、环境光照和发射光照。在此通道中渲染的方向光可以具有阴影。请注意,光照贴图的对象不会从 SH 光源获得光照。

请注意,在着色器中使用“OnlyDirectional”通道标志时,前向基础通道仅渲染主方向光、环境光/光照探针和光照贴图(SH 和顶点光源不包括在通道数据中)。

Additional Passes

对于影响此对象的每个额外的每像素光源,需要额外的渲染通道。默认情况下,这些通道中的光源没有阴影(因此在结果中,前向渲染支持一个带阴影的方向光),除非使用 multi_compile_fwdadd_fullshadows 变体快捷方式。

优点

简单易懂,易于调试和优化,适合光源数量较少的场景。

缺点

采用前向渲染方式渲染实时光源会非常消耗资源,当场景中光源数量较多时,会导致大量的渲染 Pass,从而降低渲染效率,可以设置每像素渲染的光源数量,其余光源会以较低保真度渲染。

性能注意事项

  1. 应使用光照贴图实现静态对象的光照,而不是每帧计算其光照。每顶点动态光照可能会为顶点变换增加显著的工作量,因此尽量避免多个光源照射单个对象的情况。
  2. 避免组合距离足够远而需要受到不同像素光照影响的网格。通常情况下,为渲染组合对象而必须创建的 pass 数为每个单独对象的 pass 数之和,因此进行网格组合并不会获得任何好处。
  3. 在渲染过程中应该更具光源是否重要选择合适的 Render Mode 设置

Spherical Harmonics lights

球谐函数(Spherical Harmonics) 灯光的渲染速度非常快。 它们在 CPU 上的开销很小,实际上对 GPU 来说是无额外开销的(也就是说,base pass 总是计算 SH 光照;但由于 SH 光照的工作方式,无论多少 SH 光照,成本都是完全相同的)。 SH 灯光的缺点: - 由于 SH 是在逐顶点计算的,不是逐像素,这也意味着它们不支持灯光 Cookies 或法线贴图。 - SH 照明的频率非常低。 使用 SH 灯无法实现锐利的照明过渡。它们也只影响漫反射照明。 - SH 照明不是局部的; 靠近某个表面的点或点 SH 灯会“看起来不对”。

总而言之,SH 灯通常对于小的动态物体来说已经足够了。

延迟渲染(Deferred Rendering)

概述

延迟渲染是一种高效的渲染技术,特别适合处理大量光源的场景。在第一阶段,它只渲染几何体的深度、法线和颜色信息到所谓的 G-Buffer 中。在第二阶段,它从 G-Buffer 中读取信息,并为每个光源计算光照效果。

流程概述

  1. 初始化:清空帧缓冲,包括 G-Buffer(几何缓存)和深度缓冲。
  2. 几何遍历:仅使用顶点着色器和片段着色器处理几何体,但不计算光照。
    • 将顶点数据转换到屏幕空间,并将颜色、法线、深度等信息写入 G-Buffer。
  3. 光照遍历
    • 对于场景中的每个光源,使用 G-Buffer 中的信息计算光照效果。
    • 将光照结果累加到累积缓冲中。
  4. 后处理:应用后处理效果,如抗锯齿、色调映射等。
  5. 显示:将最终的累积缓冲输出到屏幕上。

实现详细信息

在延迟渲染完成后,再使用前向渲染路径渲染那些不支持延迟着色的 Shader 对象。 在 G-buffer 中渲染目标(RT0-RT4)的默认布局如下,数据类型存储在每个渲染目标的不同通道中:

  • RT0, ARGB32 格式:漫反射颜色(RGB), 遮挡(A)。
  • RT1, ARGB32 格式:高光颜色(RGB), 粗糙度(A)。
  • RT2, ARGB2101010 格式:世界空间下的法线,未被使用(A)。
  • RT3, ARGB2101010(非 HDR)或 ARGBHalf(HDR 每个通道 16bits)格式:自发光+光照+lightmap+反射探针+深度缓冲和模板缓冲。
  • 深度+模板缓冲区。

所以默认的 G-buffer 布局每个像素是 160bits/pixel(非 HDR)或 192bits/pixel(HDR)。 如果对于混合光照使用了 Shadowmask 或 Distance Shadowmask 模式,那么第 5 个渲染目标(RT)将被使用:

  • RT4, ARGB32 格式:灯光遮挡值(RGBA)

因此 G-buffer 布局大小将增至每个 192bits/pixel(非 HDR)或 224bits/pixel(HDR)。

如果硬件不支持五个并发渲染目标,则使用阴影遮罩的对象将回退到前向渲染路径。 当摄像机不使用 HDR 时,发射+光照缓冲区 (RT3) 采用对数编码,因此提供的动态范围高于 ARGB32 纹理通常可能提供的范围。

请注意,当摄像机使用 HDR 渲染时,不会为发射 + 光照缓冲区 (RT3) 创建单独的渲染目标;而是将摄像机渲染到的渲染目标(即传递给图像效果的渲染目标)用作 RT3。

优点

可以高效处理大量光源,减少渲染 Pass 的数量,提高性能。光照的处理开销与灯光照亮的像素数成正比。光照的开销是由场景中的光量大小决定的不管它照亮了多少游戏对象。

缺点

需要额外的内存来存储 G-Buffer,需要 GPU 支持,不适用于半透明对象、正交投影、硬件抗锯齿的场景(可以同通过后处理解决-边缘查找做模糊)。此外,它也不支持网格渲染器 (Mesh Renderer) 的接受阴影 (Receive Shadows) 标志,并且仅在有限程度上支持剔除遮罩。最多只能使用四个剔除遮罩。

性能注意事项

在延迟渲染中实时灯光的性能消耗跟这个灯光所照明的像素是成正比的,不依赖于场景的复杂度。所以小的 Point 光源或 Spot 光源的性能消耗是非常低的,如果照明对象是被其他对象部分或完全遮挡的那么它的性能开销将更小。

当然,带阴影的灯光是比不带阴影的灯光更耗的。在延迟着色中,对于每个投射阴影的灯光,阴影投射对象还是需要被渲染一次或多次。

G 缓冲区通道

G 缓冲区通道将每个游戏对象渲染一次。漫射和镜面反射颜色、表面平滑度、世界空间法线和发射+环境+反射+光照贴图都将渲染到 G 缓冲区纹理中。G 缓冲区纹理设置为全局着色器属性供着色器以后访问(_CameraGBufferTexture0 .._CameraGBufferTexture3 指定)。

光照通道

光照 pass 根据 G-Buffer 和深度来计算光照。光照是在屏幕空间内计算的,因此处理所需的时间与场景复杂性无关。光照将添加到发射缓冲区。

不穿过相机近平面的点光源和聚光灯被渲染为 3D 形状,并启用了 Z 缓冲区针对场景的测试。 这使得部分或完全遮挡的点光源和聚光灯的渲染成本非常低。穿过近平面的方向光和点或聚光灯被渲染为全屏四边形。

如果光源启用了阴影,那么也会在此通道中渲染并应用阴影。请注意,阴影并非是“无成本”的;需要渲染阴影投射物,并且必须应用更复杂的光照着色器。

唯一可用的光照模型是标准 (Standard) 光照模型。如果需要不同的模型,可修改光照 pass 着色器,方法是将内置着色器中的 Internal-DeferredShading.shader 文件的修改版本放入“Assets”文件夹中名为“Resources”的文件夹内。然后打开 Graphics 设置(菜单:Edit > Project Settings,然后单击 Graphics 类别)。将“Deferred”下拉选单改为“Custom Shader”。然后,更改当前使用的着色器对应的着色器 (Shader) 选项。

使用 CommandBuffer 来扩展内置渲染管线

//TODO
使用 CommandBuffer 来扩展内置渲染管线

内置渲染管线的硬件要求

详细信息参考文档内置渲染管线的硬件要求

SRP

Unity 的可编程渲染管线 (Scriptable Render Pipeline, SRP) 是一项可以通过 C# 脚本来控制渲染的功能。SRP 技术可以强化通用渲染管线 (URP) 和高清渲染管线 (HDRP)。

可编程渲染管线是一个轻量级 API 层,允许使用 C# 脚本来调度和配置渲染命令。Unity 将这些命令传递给它的低级图形架构,后者随后将指令发送给图形 API。URP 和 HDRP 是基于 SRP 技术开发的,我们也可以基于 SRP 自定义渲染管线

渲染管线实例和渲染管线资源

每个基于 SRP 的渲染管线都有两个关键的自定义元素:

  • 渲染管线实例 这是定义渲染管线功能的类的实例。它的脚本继承自 RenderPipeline 并覆盖其 Render() 方法。
  • 渲染管线资源 这是 Unity 项目中的一项资源,用于存储有关所使用的渲染管线实例以及如何对其进行配置的数据。它的脚本继承自 RenderPipelineAsset 并覆盖其 CreatePipeline() 方法。

ScriptableRenderContext

ScriptableRenderContext 是一个类,用作渲染管线中的自定义 C# 代码与 Unity 的低级图形代码之间的接口。

入口点和回调

使用 SRP 时,使用它们可让 Unity 在特定时间调用您的 C# 代码。

  • RenderPipeline.Render 是 SRP 的主要入口点。Unity 会自动调用此方法。如果要编写自定义渲染管线,这就是开始编写代码的地方。
  • RenderPipelineManager 类有以下事件可供您订阅,以便您可以在渲染循环中的特定点执行自定义代码:
    • beginFrameRendering (将产生 GC 使用 beginContextRendering 代替)
    • endFrameRendering (将产生 GC 使用 endContextRendering 代替)
    • beginContextRendering
    • endContextRendering
    • beginCameraRendering
    • endCameraRendering

在 SRP 中调度和执行渲染命令

在 SRP 中,应使用 C# 脚本来配置和调度渲染命令。然后,需要告诉 Unity 的低级图形架构执行这些命令,此过程会将指令发送到图形 API。

主要做法是对 ScriptableRenderContext 进行 API 调用,不过也可以立即执行 CommandBuffers。

使用 ScriptableRenderContext API

在 SRP 中,ScriptableRenderContext 类用作 C# 渲染管线代码与 Unity 的低级图形代码之间的接口。SRP 使用延迟执行的方式来实现渲染;需要使用 ScriptableRenderContext 来构建渲染命令列表,然后告诉 Unity 执行这些命令。Unity 的低级图形架构随后将指令发送到图形 API。

要调度渲染命令,可以:

  • 使用 ScriptableRenderContext.ExecuteCommandBuffer 将 CommandBuffers 传递到 ScriptableRenderContext
  • 对可编程渲染上下文进行直接 API 调用(例如 ScriptableRenderContext.Cull 或 ScriptableRenderContext.DrawRenderers)

当安排好命令后,通过调用 ScriptableRenderContext.Submit 方法告诉 Unity 去执行你的命令。注意:不论你是使用的 CommandBuffer 还是直接调用的 API, 在 ScriptableRenderContext 中都同样的,并且也只有调用了 Submit 才会去执行。

以下示例代码演示如何使用 CommandBuffer 来调度和执行命令以清除当前渲染目标。

using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipeline : RenderPipeline
{
public ExampleRenderPipeline() {
}

protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
// 创建并调度命令以清除当前渲染目标
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.red);
context.ExecuteCommandBuffer(cmd);
cmd.Release();

// 告诉Unity执行命令
context.Submit();
}
}

立即执行 CommandBuffers

可通过调用 Graphics.ExecuteCommandBuffer 来立即执行 CommandBuffers,而不使用 ScriptableRenderContext。对该 API 的调用发生在渲染管线之外。

自定义渲染管线

创建自定义渲染管线

Unity 提供两种根据 SRP 预构建的渲染管线:高清渲染管线 (HDRP) 和通用渲染管线 (URP)。HDRP 和 URP 提供广泛的定制选项;但是,如果需要更多地控制渲染管线,可以基于 SRP 创建自己的自定义渲染管线。

基于 SRP 创建自定义渲染管线

创建新项目并安装自定义渲染管线所需的包

自定义渲染需要使用 SRP Core 包来创建。SRP Core 是 Unity 创建的包,其中包含可复用代码来帮助您创建自己的渲染管线,包括用于与平台特定的图形 API 结合使用的样板代码、用于常见渲染操作的实用函数以及供 URP 和 HDRP 使用的着色器库。有关 SRP Core 的更多信息,请参阅 SRP Core 包文档

  1. 创建一个新的 Unity 项目

  2. SRP 源码仓库中下载对应 Unity 版本的 SRP 包。

  3. 将下载的包按序放到工程中

    • com.unity.render-pipelines.core : SRP 的核心包
    • com.unity.render-pipelines.shadergraph :(可选)一个可视化的 shader 编辑器
    • com.unity.render-pipelines.visualeffectgraph :(可选)视觉效果图像编辑器

创建自定义版本的 URP 或 HDRP

通用渲染管线 (URP) 和高清渲染管线 (HDRP) 提供广泛的自定义选项,可帮助您获得所需的图形和性能。但是,如果您希望获得更多控制权,可为这些渲染管线之一创建自定义版本,并修改源代码。分别需要安装以下包体:

URP:

  • com.unity.render-pipelines.shadergraph
  • com.unity.render-pipelines.core
  • com.unity.render-pipelines.universal

HDRP:

  • com.unity.render-pipelines.core
  • com.unity.render-pipelines.shadergraph
  • com.unity.render-pipelines.high-defintion

在自定义渲染管线中创建渲染管线资源和渲染管线实例

如果创建基于 SRP 的自定义渲染管线,项目必须包含:

  • 一个继承自 RenderPipelineAsset 并覆盖其 CreatePipeline() 方法的脚本。此脚本用于定义渲染管线资源。
  • 一个继承自 RenderPipeline 并覆盖其 Render() 方法的脚本。此脚本定义渲染管线实例,是编写自定义渲染代码的地方。
  • 一个从 RenderPipelineAsset 脚本创建的渲染管线资源。此资源充当渲染管线实例的工厂类。

因为这些元素非常紧密相关,所以应该同时创建它们。

创建基本渲染管线资源和渲染管线实例

示例代码如下:

渲染管线资源脚本

using UnityEngine;
using UnityEngine.Rendering;

[CreateAssetMenu(menuName = "Rendering/ExampleRenderPipelineAsset")]
public class ExampleRenderPipelineAsset : RenderPipelineAsset
{
// Unity 在渲染第一帧之前调用此方法。
// 如果渲染管线资源上的设置改变,Unity 将销毁当前的渲染管线实例,并在渲染下一帧之前再次调用此方法。
protected override RenderPipeline CreatePipeline() {
// 实例化此自定义 SRP 用于渲染的渲染管线。
return new ExampleRenderPipelineInstance();
}
}

渲染管线实例脚本

using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipelineInstance : RenderPipeline
{
public ExampleRenderPipelineInstance() {
}
protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
// 可以在此处编写自定义渲染代码。渲染主入口
}
}

渲染管线资源

CreatePipelineAsset

创建出如下资源文件

RenderPipelineAsset

创建可配置的渲染管线资源和实例

默认情况下,渲染管线资源存储关于用于渲染的渲染管线实例以及默认材质和着色器的信息。 在 RenderPipelineAsset 脚本中,可以扩展您的渲染管线资源,以便它存储额外的数据,并且可以在项目中拥有多个具有不同配置的不同渲染管线资源。例如,可以使用渲染管线资源来保存每个不同硬件层的配置数据。 高清渲染管线 (HDRP) 和通用渲染管线 (URP) 包括这方面的示例。

以下示例展示了如何创建一个 RenderPipelineAsset 脚本,该脚本定义一个带有公共数据的渲染管线资源,可以在 Inspector 窗口为每个实例设置这些数据,以及在其构造函数中接收渲染管线资源并使用这些数据。

实例代码如下:

渲染资源脚本

using UnityEngine;
using UnityEngine.Rendering;

[CreateAssetMenu(menuName = "Rendering/ExampleRenderPipelineAsset")]
public class ExampleRenderPipelineAsset : RenderPipelineAsset
{
// 可以在 Inspector 中为每个渲染管线资源定义数据
public Color exampleColor;
public string exampleString;

protected override RenderPipeline CreatePipeline() {
// 实例化此自定义 SRP 用于渲染的渲染管线,然后传递对此渲染管线资源的引用。
// 然后,渲染管线实例可以访问上方定义的配置数据。
return new ExampleRenderPipelineInstance(this);
}
}

渲染实例脚本

using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipelineInstance : RenderPipeline
{
// 使用此变量来引用传递给构造函数的渲染管线资源
private ExampleRenderPipelineAsset renderPipelineAsset;
public ExampleRenderPipelineInstance(ExampleRenderPipelineAsset asset) {
renderPipelineAsset = asset;
}
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
// 这是使用渲染管线资源数据的示例。
Debug.Log(renderPipelineAsset.exampleString);
}
}

渲染管线资源

创建出如下资源文件

RenderPipelineAsset2

在自定义渲染管线中创建简单渲染循环

渲染循环是在单个帧中发生的所有渲染操作的术语。此节代码示例说明使用 SRP 的基础概念。你能使用这些信息去构建自己的自定可编程渲染管线,也可以用于理解 SRP 是如何工作的。

准备项目

步骤如下所示:

  1. 创建与 SRP 兼容的着色器。
  2. 创建一个或多个要渲染的游戏对象。
  3. 创建自定义 SRP 的基本结构。
  4. 可选:如果计划扩展简单自定义 SRP 以添加更复杂的功能,需要安装 SRP Core 包。SRP Core 包中包含 SRP Core 着色器库(可以用于使着色器与 SRP Batcher 兼容)以及用于常见操作的实用程序函数。有关更多信息,请参阅 SRP Core 包文档

创建 Shader

在可编程渲染管线中,使用 LightMode 通道标签确定如何绘制几何体。有关通道标签的更多信息,请参阅 ShaderLab:向通道分配标签

此任务演示如何创建非常简单的无光照 Shader 对象,其 LightMode 通道标签值为 ExampleLightModeTag。

新建 Shader 名为 SimpleUnlitColor,代码如下:

// 这定义一个与自定义可编程渲染管线兼容的简单无光照 Shader 对象。
// 它应用硬编码颜色,并演示 LightMode 通道标签的使用。
// 它不与 SRP Batcher 兼容。

Shader "Examples/SimpleUnlitColor"
{
SubShader
{
Pass
{
// LightMode 通道标签的值必须与 ScriptableRenderContext.DrawRenderers 中的 ShaderTagId 匹配
Tags { "LightMode" = "ExampleLightModeTag"}

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

float4x4 unity_MatrixVP;
float4x4 unity_ObjectToWorld;

struct Attributes
{
float4 positionOS : POSITION;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
};

Varyings vert (Attributes IN)
{
Varyings OUT;
float4 worldPos = mul(unity_ObjectToWorld, IN.positionOS);
OUT.positionCS = mul(unity_MatrixVP, worldPos);
return OUT;
}

float4 frag (Varyings IN) : SV_TARGET
{
return float4(0.5,1,0.5,1);
}
ENDHLSL
}
}
}

创建渲染对象

创建一个东西进行渲染,使用材质附带 Shader 作用于该物体。

SimpleAsset

创建自定义 SRP 的基本结构

前面章节所描述的步骤:

  1. 创建渲染管线资源和渲染管线实例脚本
  2. 将当前创建的管线资源设置为激活的渲染管线

创建渲染循环

在简单渲染循环中,基本操作有:

  • 清除渲染目标,移除在最后一帧期间绘制的几何体。
  • 剔除,过滤掉对摄像机不可见的几何体。
  • 绘制,向 GPU 告知要绘制的几何体以及如何进行绘制。

清除

清除意味着移除在最后一帧期间绘制的内容。渲染目标通常是屏幕;但是,也可以渲染到纹理以创建“画中画”效果。

要清除可编程渲染管线中的渲染目标,请执行以下操作:

  1. 使用 Clear 命令配置 CommandBuffer。
  2. 将 CommandBuffer 添加到 ScriptableRenderContext 上的命令队列;为此,请调用 ScriptableRenderContext.ExecuteCommandBuffer。
  3. 指示图形 API 执行 ScriptableRenderContext 上的命令队列;为此,请调用 ScriptableRenderContext.Submit。
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}

protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();

context.Submit();
}
}

剔除

剔除是过滤掉对摄像机不可见的几何体的过程。

要在可编程渲染管线中进行剔除,请执行以下操作:

  • 使用有关摄像机的数据填充 ScriptableCullingParameters 结构;为此,请调用 Camera.TryGetCullingParameters。
  • 可选:手动更新 ScriptableCullingParameters 结构的值。
  • 调用 ScriptableRenderContext.Cull,并将结果存储在一个 CullingResults 结构中。
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}

protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();

// 遍历所有相机
foreach (Camera camera in cameras)
{
// 获取剔除参数
camera.TryGetCullingParameters(out var cullingParameters);

// 剔除并存储剔除结果
var cullingResults = context.Cull(ref cullingParameters);
}

// 通知图像API执行命令
context.Submit();
}
}

绘制

绘图是指示图形 API 以给定设置绘制一组给定几何图形的过程。在 SRP 中的绘制应有以下步骤:

  1. 执行剔除(上一节描述的),并存储结果在 CullingResults 结构体中。
  2. 创建并且配置 FilteringSetting 结构体,此结构描述了如果去过滤剔除结果。
  3. 创建并配置 DrawingSettings 结构体, 此结构描述哪些几何体将被绘制以及如何绘制它。
  4. 可以选步骤,默认 Unity 基于 Shader 对象设置渲染状态(深度,蒙版和模板等)。如果你想对一些或所有打算绘制的几何对象覆写这些渲染状态,你能使用 RenderStateBlock 结构体去做这件事情。
  5. 调用 ScriptableRenderContext.DrawRenderers,并传递上面创建的结构体对象。Unity 将根据上面的设置的信息进行绘制。
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}

protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();

// 遍历所有相机
foreach (Camera camera in cameras)
{
// 获取剔除参数
camera.TryGetCullingParameters(out var cullingParameters);

// 剔除并存储剔除结果
var cullingResults = context.Cull(ref cullingParameters);

//设置视图、投影和剪切平面全局着色器变量。
context.SetupCameraProperties(camera);

//根据 LightMode Pass 标签值,告诉Unity要绘制哪个几何图形
ShaderTagId shaderTagId = new ShaderTagId("ExampleLightModeTag");

// 告诉Unity,基于当前相机该如何去排序
var sortingSettings = new SortingSettings(camera);

// 创建用于描述需要绘制的几何体,以及如何绘制他们
DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings);

// 告诉Unity如何去过滤裁剪的结果,进一步指定要绘制的几何体
// 使用FilteringSettings.defaultValue指定不进行过滤
FilteringSettings filteringSettings = FilteringSettings.defaultValue;

// 基于前面的设置绘制几何体
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);

// 安排天空盒子的绘制
if (camera.clearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null)
{
context.DrawSkybox(camera);
}

// 提交执行
context.Submit();
}
}
}

测试

生成资源文件
Ex3Assets

在 ProjectSettings 中启用当前管线资源
Ex3Settings

观察 Scene 视图,渲染结果如下:
Result

URP

通用渲染管线

HDRP

高清渲染管线