4.9 脚本化渲染 (Scriptable Render Pipeline)


文档摘要

4.9 脚本化渲染 (Scriptable Render Pipeline) 4.9 脚本化渲染管线 (Scriptable Render Pipeline) 脚本化渲染管线 (Scriptable Render Pipeline, SRP) 是 Unity 引擎中一个强大的系统,它允许开发者完全掌控和自定义 Unity 的渲染流程。在传统的内置渲染管线中,渲染过程是固定的,开发者只能通过材质属性和一些全局设置来间接影响渲染结果。而 SRP 的出现,打破了这种限制,赋予了开发者前所未有的灵活性和控制力,可以根据项目需求定制高效且具有独特视觉风格的渲染管线。 4.9.1 SRP 的概念与优势 什么是脚本化渲染管线 (SRP)?

4.9 脚本化渲染 (Scriptable Render Pipeline)

4.9 脚本化渲染管线 (Scriptable Render Pipeline)

脚本化渲染管线 (Scriptable Render Pipeline, SRP) 是 Unity 引擎中一个强大的系统,它允许开发者完全掌控和自定义 Unity 的渲染流程。在传统的内置渲染管线中,渲染过程是固定的,开发者只能通过材质属性和一些全局设置来间接影响渲染结果。而 SRP 的出现,打破了这种限制,赋予了开发者前所未有的灵活性和控制力,可以根据项目需求定制高效且具有独特视觉风格的渲染管线。

4.9.1 SRP 的概念与优势

什么是脚本化渲染管线 (SRP)?

SRP 是一种允许开发者使用 C# 脚本完全控制 Unity 渲染过程的架构。它将原本由引擎内部硬编码的渲染流程开放出来,开发者可以自定义渲染的各个阶段,例如:

  • 剔除 (Culling):决定哪些物体需要被渲染。

  • 渲染排序 (Rendering Order):决定物体的渲染顺序。

  • 渲染路径 (Rendering Path):决定使用哪种渲染技术(例如前向渲染、延迟渲染)。

  • 后处理 (Post-processing):在渲染完成后对图像进行处理,例如颜色校正、景深等。

SRP 的优势:

  • 高度自定义性 (High Customization):SRP 允许开发者根据项目需求定制渲染管线,实现独特的视觉效果和渲染策略。例如,可以创建专门用于移动平台的轻量级渲染管线,或者为高性能 PC 平台打造具有复杂光照和后处理效果的管线。

  • 性能优化 (Performance Optimization):通过自定义渲染流程,开发者可以精确控制渲染过程的每一个步骤,避免不必要的计算,从而优化性能。例如,可以针对特定平台和硬件特性进行渲染优化,减少 Draw Call,降低 GPU 负载。

  • 易于扩展 (Extensibility):SRP 提供了清晰的接口和架构,方便开发者扩展和添加自定义的渲染特性。例如,可以添加自定义的渲染通道、后处理效果、光照模型等。

  • 代码可维护性 (Code Maintainability):使用 C# 脚本编写渲染管线,使得代码更易于理解、维护和版本控制。相比于修改引擎源码,SRP 的自定义方式更加安全和灵活。

  • 跨平台一致性 (Cross-platform Consistency):SRP 可以帮助开发者在不同平台上实现更加一致的渲染效果。通过针对不同平台定制渲染管线,可以更好地控制画面质量和性能平衡。

Unity 提供的预制 SRP:

为了简化 SRP 的使用,Unity 官方提供了两个预制的 SRP 管线:

  • 通用渲染管线 (Universal Render Pipeline, URP):适用于广泛平台,包括移动平台、PC 和主机。URP 注重性能和可扩展性,提供了现代化的渲染特性,例如前向渲染、后处理效果、体积光照等。

  • 高清渲染管线 (High Definition Render Pipeline, HDRP):面向高性能平台,例如 PC 和主机。HDRP 专注于高质量的渲染效果,支持物理光照、体积雾、屏幕空间反射等高级渲染特性,适用于制作电影级画质的游戏和应用。

开发者可以直接使用预制的 SRP 管线,也可以基于它们进行修改和扩展,或者从零开始创建完全自定义的 SRP 管线。

4.9.2 SRP 的核心架构

SRP 的核心架构主要包含以下几个关键组件:

  • 渲染管线资源 (Render Pipeline Asset):这是一个 ScriptableObject 资源,用于配置整个渲染管线的全局设置,例如渲染路径、后处理设置、阴影设置等。每个项目只能有一个激活的渲染管线资源。

  • 渲染管线实例 (Render Pipeline Instance):这是一个 C# 类,负责实际的渲染逻辑。它继承自 RenderPipeline 抽象类,开发者需要在这个类中实现自定义的渲染流程。渲染管线资源会创建并管理渲染管线实例。

  • 渲染上下文 (ScriptableRenderContext):这是一个结构体,提供了与 Unity 渲染引擎交互的接口。渲染管线实例通过渲染上下文来执行渲染命令,例如设置渲染目标、绘制物体、执行后处理等。

  • 摄像机渲染器 (Camera Renderer) (URP 和 HDRP 中):这是负责单个摄像机渲染的组件。它从渲染管线资源中获取配置,并调用渲染管线实例来完成实际的渲染工作。在自定义 SRP 中,开发者需要自行处理摄像机的渲染流程。

  • 渲染通道 (Render Pass):渲染通道是 SRP 中最基本的渲染单元。它封装了一系列渲染命令,用于完成特定的渲染任务,例如绘制不透明物体、绘制透明物体、应用后处理效果等。开发者可以自定义渲染通道,并将其添加到渲染管线中。

  • 渲染特性 (Render Feature) (URP 和 HDRP 中):渲染特性是 URP 和 HDRP 中用于扩展渲染管线功能的模块。它可以插入到渲染管线的特定阶段,添加自定义的渲染逻辑,例如屏幕空间环境光遮蔽 (SSAO)、颜色分级等。

可以用 Mermaid 的 graph TD 图来表示 SRP 的核心架构:

图 4.9.1 SRP 核心架构图

此图展示了 SRP 各个组件之间的关系:

  • Render Pipeline Asset 是配置中心,创建 Render Pipeline Instance

  • Render Pipeline Instance 使用 ScriptableRenderContext 与引擎交互,并管理 Render Pass 的执行顺序。

  • 在 URP 和 HDRP 中,Camera Renderer 负责处理单个摄像机的渲染,并调用 Render Pipeline Instance

  • Render Feature 可以扩展 URP 和 HDRP 的功能,插入到渲染管线中。

4.9.3 创建自定义 SRP 管线

接下来,我们将通过一个简单的代码示例,演示如何创建一个最基本的自定义 SRP 管线,并绘制一个简单的场景。

1. 创建 Render Pipeline Asset:

首先,我们需要创建一个继承自 RenderPipelineAsset 的 C# 脚本,例如 CustomRenderPipelineAsset.cs

using UnityEngine; using UnityEngine.Rendering; [CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline Asset")] public class CustomRenderPipelineAsset : RenderPipelineAsset { protected override RenderPipeline CreatePipeline() { return new CustomRenderPipeline(); } }

这段代码创建了一个名为 CustomRenderPipelineAsset 的 ScriptableObject 资源,当 Unity 需要创建渲染管线实例时,它会调用 CreatePipeline() 方法,并返回一个新的 CustomRenderPipeline 实例。

2. 创建 Render Pipeline Instance:

然后,我们需要创建渲染管线实例类 CustomRenderPipeline.cs,它继承自 RenderPipeline 抽象类,并实现 Render() 方法:

using UnityEngine; using UnityEngine.Rendering; public class CustomRenderPipeline : RenderPipeline { protected override void Render(ScriptableRenderContext context, Camera[] cameras) { foreach (var camera in cameras) { RenderSingleCamera(context, camera); } } private void RenderSingleCamera(ScriptableRenderContext context, Camera camera) { // 1. 设置渲染目标 (Render Target) context.SetupCameraProperties(camera); // 2. 清除渲染目标 (Clear Render Target) ClearRenderTarget(context, camera); // 3. 执行剔除 (Culling) CullingResults cullingResults = PerformCulling(context, camera); // 4. 设置渲染状态 (Set Render State) ShaderTagId shaderTagId = new ShaderTagId("SRPDefaultUnlit"); // 使用 Unlit ShaderTagId var drawingSettings = new DrawingSettings(shaderTagId, new SortingSettings(camera)) { enableDynamicBatching = true, enableInstancing = true }; var filteringSettings = new FilteringSettings(RenderQueueRange.opaque); // 渲染不透明物体 var renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing); // 5. 绘制不透明物体 (Draw Opaque Objects) context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings, ref renderStateBlock); // 6. 提交渲染命令 (Submit Render Commands) context.Submit(); } private void ClearRenderTarget(ScriptableRenderContext context, Camera camera) { CommandBuffer cmd = CommandBufferPool.Get(); using (new ProfilingScope(cmd, new ProfilingSampler("Clear Render Target"))) { cmd.ClearRenderTarget(true, true, camera.backgroundColor); // 清除颜色和深度缓冲 } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } private CullingResults PerformCulling(ScriptableRenderContext context, Camera camera) { ScriptableCullingParameters cullingParameters; if (!camera.TryGetCullingParameters(out cullingParameters)) { return new CullingResults(); } // 可在此处自定义剔除参数,例如设置层级遮挡剔除 (Hierarchical-Z Culling) // ... CullingResults cullingResults = context.Cull(ref cullingParameters); return cullingResults; } }

这段代码实现了最基本的渲染流程:

  • Render() 方法: 遍历所有摄像机,并调用 RenderSingleCamera() 方法处理单个摄像机的渲染。

  • RenderSingleCamera() 方法:

    • context.SetupCameraProperties(camera): 设置渲染上下文的摄像机属性。

    • ClearRenderTarget(): 清除渲染目标。

    • PerformCulling(): 执行剔除,获取可见的渲染物体。

    • 创建 DrawingSettingsFilteringSettings,配置绘制设置和过滤设置。

    • context.DrawRenderers(): 绘制不透明物体。

    • context.Submit(): 提交渲染命令,将渲染结果显示到屏幕上。

  • ClearRenderTarget() 方法: 使用 CommandBuffer 清除渲染目标,包括颜色缓冲和深度缓冲。

  • PerformCulling() 方法: 执行剔除,获取可见的渲染物体。

3. 创建场景并应用 SRP:

  1. 在 Unity 编辑器中创建一个新的 3D 场景。

  2. 创建一些简单的 3D 物体,例如 Cube、Sphere 等,并为其赋予材质,确保材质的 Shader 使用 "Universal Render Pipeline/Unlit" 或 "Legacy Shaders/Unlit/Texture"。

  3. 在 Project 窗口中,右键点击 Create -> Rendering -> Custom Render Pipeline Asset,创建一个 Custom Render Pipeline Asset 资源。

  4. 在 Edit -> Project Settings -> Graphics 中,将 Scriptable Render Pipeline Settings 设置为刚刚创建的 Custom Render Pipeline Asset 资源。

现在运行场景,你应该可以看到场景中的物体被渲染出来。虽然这个自定义 SRP 管线非常基础,但它展示了 SRP 的基本工作流程。

4.9.4 深入理解 Render Pass 和 Command Buffer

在 SRP 中,Render PassCommandBuffer 是两个非常重要的概念。

Render Pass:

Render Pass 是 SRP 中最基本的渲染单元,它封装了一系列渲染命令,用于完成特定的渲染任务。开发者可以创建自定义的 Render Pass,并将其添加到渲染管线中,以扩展渲染功能。

在 URP 和 HDRP 中,Render Pass 提供了更高级的抽象和管理机制,例如:

  • RenderQueueRange: 指定 Render Pass 渲染的物体的渲染队列范围。

  • FilteringSettings: 提供更灵活的过滤设置,例如基于 LayerMask、ShaderTagId 等进行过滤。

  • RenderStateBlock: 用于设置渲染状态,例如 Blend 模式、Cull 模式、Depth Test 等。

  • Profiling: 集成 Profiling 功能,方便性能分析和优化。

CommandBuffer:

CommandBuffer 是 Unity 提供的一种用于记录渲染命令的缓冲区。开发者可以使用 CommandBuffer 来批量执行渲染命令,减少 CPU 和 GPU 之间的同步开销,提高渲染效率。

CommandBuffer 可以用于:

  • 清除渲染目标: cmd.ClearRenderTarget()

  • 设置渲染目标: cmd.SetRenderTarget()

  • 绘制渲染物体: cmd.DrawRenderer(), cmd.DrawMesh()

  • 执行后处理效果: cmd.Blit()

  • 设置全局 Shader 属性: cmd.SetGlobalFloat(), cmd.SetGlobalTexture()

在 SRP 中,Render Pass 通常会使用 CommandBuffer 来记录和执行渲染命令。例如,在 CustomRenderPipeline.csClearRenderTarget() 方法中,我们使用了 CommandBuffer 来清除渲染目标。

代码示例:使用 CommandBuffer 绘制透明物体

假设我们需要在自定义 SRP 管线中添加透明物体的渲染支持。我们可以创建一个新的 Render Pass 来专门处理透明物体的渲染。

首先,在 CustomRenderPipeline.cs 中,修改 RenderSingleCamera() 方法,添加透明物体的渲染逻辑:

private void RenderSingleCamera(ScriptableRenderContext context, Camera camera) { // ... (之前的代码,渲染不透明物体) ... // 7. 绘制透明物体 (Draw Transparent Objects) var transparentDrawingSettings = new DrawingSettings(shaderTagId, new SortingSettings(camera)) { enableDynamicBatching = true, enableInstancing = true }; var transparentFilteringSettings = new FilteringSettings(RenderQueueRange.transparent); // 渲染透明物体 context.DrawRenderers(cullingResults, ref transparentDrawingSettings, ref transparentFilteringSettings, ref renderStateBlock); // ... (提交渲染命令) ... }

这段代码与绘制不透明物体的代码非常相似,唯一的区别在于 FilteringSettingsRenderQueueRange 设置为 RenderQueueRange.transparent,表示只渲染渲染队列为 Transparent 或更高的物体。

为了让透明物体正确渲染,我们需要确保透明物体的材质 Shader 使用支持透明渲染的 Shader,例如 "Universal Render Pipeline/Lit" 或 "Legacy Shaders/Transparent/Diffuse"。

使用 CommandBuffer 实现简单的后处理效果

我们还可以使用 CommandBuffer 来实现简单的后处理效果,例如颜色反转。

CustomRenderPipeline.csRenderSingleCamera() 方法的末尾,添加后处理代码:

private void RenderSingleCamera(ScriptableRenderContext context, Camera camera) { // ... (之前的渲染代码) ... // 8. 应用后处理 (Apply Post-processing) ApplyColorInversion(context, camera); // ... (提交渲染命令) ... } private void ApplyColorInversion(ScriptableRenderContext context, Camera camera) { CommandBuffer cmd = CommandBufferPool.Get(); using (new ProfilingScope(cmd, new ProfilingSampler("Color Inversion Post-processing"))) { int tempRTId = Shader.PropertyToID("_TempRT"); // 创建临时渲染纹理 ID cmd.GetTemporaryRT(tempRTId, camera.pixelWidth, camera.pixelHeight, 0, FilterMode.Bilinear, RenderTextureFormat.Default); // 获取临时渲染纹理 cmd.Blit(BuiltinRenderTextureType.CameraTarget, tempRTId); // 将当前渲染结果拷贝到临时纹理 cmd.Blit(tempRTId, BuiltinRenderTextureType.CameraTarget, colorInversionMaterial); // 使用颜色反转材质进行 Blit cmd.ReleaseTemporaryRT(tempRTId); // 释放临时渲染纹理 } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); }

这段代码实现了简单的颜色反转后处理效果:

  • 创建一个临时渲染纹理 _TempRT

  • 使用 cmd.Blit() 将当前渲染结果拷贝到临时纹理。

  • 使用 cmd.Blit() 将临时纹理作为输入,应用颜色反转材质 colorInversionMaterial,并将结果输出到 BuiltinRenderTextureType.CameraTarget,即屏幕。

  • 释放临时渲染纹理。

为了使用这段代码,我们需要创建一个颜色反转材质 colorInversionMaterial,并为其指定一个颜色反转 Shader。Shader 代码如下:

Shader "Hidden/ColorInversion" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { 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; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return fixed4(1.0 - col.r, 1.0 - col.g, 1.0 - col.b, 1.0); // 颜色反转 } ENDCG } } }

CustomRenderPipeline.cs 中,我们需要声明并初始化 colorInversionMaterial

public class CustomRenderPipeline : RenderPipeline { private Material colorInversionMaterial; public CustomRenderPipeline() { colorInversionMaterial = new Material(Shader.Find("Hidden/ColorInversion")); } // ... (Render() 和 RenderSingleCamera() 方法) ... }

现在运行场景,你应该可以看到整个画面颜色都被反转了。

4.9.5 SRP Batcher 与性能优化

SRP Batcher 是 Unity 2019.3 版本引入的一项重要的性能优化技术,它可以显著减少 CPU 开销,提高渲染效率。

SRP Batcher 的原理:

SRP Batcher 的核心思想是通过减少 Draw Call 的 CPU 开销来提高性能。传统的渲染流程中,每个 Draw Call 都需要 CPU 进行大量的准备工作,例如设置材质属性、绑定纹理、计算矩阵等。当场景中物体数量较多时,Draw Call 的 CPU 开销会成为性能瓶颈。

SRP Batcher 通过以下两种技术来减少 Draw Call 的 CPU 开销:

  • Shader Stripping (Shader 剥离):SRP Batcher 会自动分析场景中使用的 Shader,并剥离掉不必要的 Shader 变体,减少 Shader 编译时间和内存占用。

  • Constant Buffer Batching (常量缓冲批量处理):SRP Batcher 会将多个 Draw Call 中相同的 Shader 常量(例如矩阵、颜色、纹理等)打包到一个常量缓冲区 (Constant Buffer) 中,并一次性上传到 GPU。这样可以避免每个 Draw Call 都需要上传相同的常量数据,减少 CPU 和 GPU 之间的通信开销。

SRP Batcher 的优势:

  • 显著提高性能: SRP Batcher 可以显著减少 Draw Call 的 CPU 开销,尤其是在场景物体数量较多、材质和 Shader 复杂度较高的情况下,性能提升更加明显。

  • 易于使用: SRP Batcher 是自动启用的,开发者无需进行额外的配置和代码修改。只需要使用 SRP 管线,并确保材质和 Shader 符合 SRP Batcher 的要求即可。

  • 广泛适用: SRP Batcher 适用于 URP 和 HDRP,可以应用于各种类型的项目,包括移动平台、PC 和主机。

SRP Batcher 的限制:

  • 材质和 Shader 的限制: 为了充分利用 SRP Batcher 的优势,材质和 Shader 需要满足一定的条件,例如:

    • 使用 Shader Graph 或 ShaderLab 编写 Shader。

    • 尽量使用 Shader Keywords 来控制 Shader 变体,避免使用材质属性来控制 Shader 变体。

    • 材质属性尽量使用 Shader Graph 的 Blackboard 或 ShaderLab 的 Properties 块中定义的属性。

  • 动态批处理的限制: SRP Batcher 与 Unity 的动态批处理 (Dynamic Batching) 不兼容。如果启用了动态批处理,SRP Batcher 将会禁用动态批处理。在大多数情况下,SRP Batcher 的性能提升要优于动态批处理。

如何优化 SRP Batcher 性能:

  • 减少材质变体: 尽量减少场景中使用的材质变体数量。可以使用材质变体收集器 (Material Variant Collector) 来分析和优化材质变体。

  • 优化 Shader: 优化 Shader 代码,减少 Shader 指令数和纹理采样次数。可以使用 Shader 性能分析工具来分析和优化 Shader 性能。

  • 使用 Shader Keywords: 尽量使用 Shader Keywords 来控制 Shader 变体,避免使用材质属性来控制 Shader 变体。

  • 合理组织场景: 合理组织场景中的物体,尽量将使用相同材质和 Shader 的物体放在一起,以便 SRP Batcher 进行批量处理。

4.9.6 总结与展望

脚本化渲染管线 (SRP) 是 Unity 引擎中一项革命性的渲染技术,它赋予了开发者前所未有的渲染控制权和自定义能力。通过 SRP,开发者可以根据项目需求定制高效且具有独特视觉风格的渲染管线,实现各种高级渲染效果和性能优化。

本章节介绍了 SRP 的核心概念、架构、创建自定义 SRP 管线的基本步骤,并深入探讨了 Render Pass、CommandBuffer 和 SRP Batcher 等重要概念。希望通过本章节的学习,你能够对 SRP 有更深入的理解,并能够开始尝试使用 SRP 来定制自己的渲染管线。

未来,SRP 将会成为 Unity 渲染技术的主流方向。随着 Unity 引擎的不断发展和 SRP 功能的不断完善,SRP 将会在游戏开发、影视制作、虚拟现实、增强现实等领域发挥越来越重要的作用。掌握 SRP 技术,将有助于开发者在未来的图形技术领域保持竞争力。

进一步学习建议:

  • Unity 官方文档: 详细阅读 Unity 官方关于 SRP 的文档,了解 SRP 的最新特性和最佳实践。

  • URP 和 HDRP 源码: 研究 URP 和 HDRP 的源码,深入理解预制 SRP 管线的实现原理和架构设计。

  • SRP 示例项目: 学习 Unity 官方提供的 SRP 示例项目,例如 Custom Render Pipeline Example 和 Universal Rendering Examples,了解 SRP 的实际应用和代码实现。

  • Shader Graph 和 ShaderLab: 深入学习 Shader Graph 和 ShaderLab,掌握编写 SRP 兼容 Shader 的技能。

  • 图形学基础知识: 学习图形学基础知识,例如渲染管线、光照模型、后处理效果等,有助于更深入地理解 SRP 的原理和应用。

希望本章节能够帮助你开启 SRP 的学习之旅,并在未来的项目开发中灵活运用 SRP 技术,创造出更优秀的视觉体验和更高性能的应用!


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U