第13章 ScriptableRenderPass深入

第13章 ScriptableRenderPass深入

理论讲解

13.1 ScriptableRenderPass生命周期

ScriptableRenderPass是URP中最小的渲染单元,它定义了渲染管线中的一个特定渲染步骤。理解其生命周期对于正确实现自定义渲染通道至关重要。

ScriptableRenderPass的完整生命周期包括:

  1. 创建阶段:通过构造函数创建渲染通道实例
  2. 配置阶段:在Configure方法中设置渲染目标和渲染状态
  3. 执行阶段:在Execute方法中执行实际的渲染命令
  4. 清理阶段:在FrameCleanup方法中释放资源

生命周期方法详解:

构造函数:初始化渲染通道的基本参数,如执行事件、渲染目标等。

Configure:配置渲染通道的渲染目标和初始状态。这个方法在每帧渲染前被调用,用于设置渲染目标和清除状态。

Execute:执行渲染通道的核心逻辑。这是渲染通道的主要工作方法,包含实际的渲染命令。

FrameCleanup:清理帧级别的资源。在每帧渲染完成后调用,用于释放临时资源。

13.2 Configure()、Execute()、OnCameraCleanup()

Configure()方法

Configure方法是渲染通道配置的核心,它决定了渲染通道的渲染目标和初始状态。

1
2
3
4
5
6
7
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 配置渲染目标
ConfigureTarget(renderTextureIdentifier);
// 配置深度目标
ConfigureClear(ClearFlag.All, Color.black);
}

Configure方法的参数:

  • cmd:用于记录配置命令的CommandBuffer
  • cameraTextureDescriptor:相机纹理描述符,包含分辨率、格式等信息

Execute()方法

Execute方法是渲染通道的核心执行逻辑,它使用ScriptableRenderContext执行实际的渲染命令。

1
2
3
4
5
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
// 执行渲染命令
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
}

Execute方法的参数:

  • context:ScriptableRenderContext,用于执行渲染命令
  • renderingData:RenderingData,包含当前帧的渲染数据

FrameCleanup()方法

FrameCleanup方法用于清理帧级别的资源,确保没有资源泄漏。

1
2
3
4
public override void FrameCleanup(ref RenderingData renderingData)
{
// 释放临时资源
}

13.3 RenderPassEvent枚举详解

RenderPassEvent定义了渲染通道在渲染流程中的执行时机,这是控制渲染顺序的关键。

主要的RenderPassEvent值:

BeforeRendering:在所有渲染之前执行

  • BeforeRenderingShadows:在阴影渲染之前
  • BeforeRenderingPrePasses:在预通道渲染之前
  • BeforeRenderingOpaques:在不透明物体渲染之前
  • BeforeRenderingTransparents:在透明物体渲染之前
  • BeforeRenderingPostProcessing:在后处理渲染之前

AfterRendering:在所有渲染之后执行

  • AfterRenderingOpaques:在不透明物体渲染之后
  • AfterRenderingTransparents:在透明物体渲染之后
  • AfterRenderingPostProcessing:在后处理渲染之后
  • AfterRendering:在所有渲染之后

RenderPassEvent的使用原则:

  1. 依赖关系:确保依赖的渲染通道在当前通道之前执行
  2. 性能考虑:将计算密集型通道安排在合适的位置
  3. 资源管理:确保所需资源在通道执行前已准备就绪

13.4 RTHandle资源管理

RTHandle是URP中用于管理渲染纹理资源的类型安全句柄,它提供了自动资源管理和生命周期控制。

RTHandle的优势:

  1. 自动管理:自动处理渲染纹理的创建和释放
  2. 类型安全:编译时检查,避免运行时错误
  3. 性能优化:减少不必要的资源分配和释放

RTHandle的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private RTHandle m_CameraColorTarget;
private RTHandle m_CameraDepthTarget;

// 初始化
m_CameraColorTarget = RTHandles.Alloc(1280, 720, colorFormat: GraphicsFormat.R8G8B8A8_SRGB);
m_CameraDepthTarget = RTHandles.Alloc(1280, 720, depthBufferBits: DepthBits.Depth24);

// 在渲染中使用
ConfigureTarget(m_CameraColorTarget);
ConfigureClear(ClearFlag.All, Color.black);

// 释放资源
m_CameraColorTarget?.Release();
m_CameraDepthTarget?.Release();

13.5 Pass之间的数据传递

在多Pass渲染中,Pass之间的数据传递是实现复杂渲染效果的关键。

数据传递方式:

  1. 渲染纹理:通过共享的渲染纹理在Pass间传递数据
  2. 常量缓冲区:通过Shader属性传递参数
  3. 渲染状态:通过渲染状态传递配置信息

实现数据传递的步骤:

  1. 创建共享资源:创建Pass间共享的渲染纹理
  2. 配置数据源:在输出Pass中写入数据到共享资源
  3. 配置数据目标:在输入Pass中从共享资源读取数据

13.6 自定义RenderPass完整实现

自定义RenderPass的实现需要遵循特定的模式,确保正确集成到URP渲染流程中。

自定义RenderPass的实现步骤:

  1. 继承ScriptableRenderPass:创建自定义渲染通道类
  2. 实现构造函数:初始化通道参数
  3. 重写Configure方法:配置渲染目标
  4. 重写Execute方法:实现渲染逻辑
  5. 重写FrameCleanup方法:清理资源
  6. 集成到渲染器:将自定义通道添加到渲染器

代码示例

13.7 自定义深度渲染通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 自定义深度渲染通道
public class CustomDepthPass : ScriptableRenderPass
{
private FilteringSettings m_FilteringSettings;
private RenderTargetHandle m_DepthTextureHandle;
private List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>();
private Material m_DepthMaterial;
private ProfilingSampler m_ProfilingSampler;

public CustomDepthPass(RenderPassEvent renderPassEvent, Material depthMaterial)
{
this.renderPassEvent = renderPassEvent;
m_DepthMaterial = depthMaterial;
m_ProfilingSampler = new ProfilingSampler("Custom Depth Pass");

// 设置过滤条件,只渲染需要深度的物体
m_FilteringSettings = new FilteringSettings(RenderQueueRange.all);

// 添加常用的深度Shader标签
m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));
m_ShaderTagIdList.Add(new ShaderTagId("LightweightForward"));
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 配置深度纹理格式
var depthDescriptor = cameraTextureDescriptor;
depthDescriptor.colorFormat = RenderTextureFormat.Depth;
depthDescriptor.depthBufferBits = 32;

// 配置渲染目标
ConfigureTarget(m_DepthTextureHandle.Identifier());
ConfigureClear(ClearFlag.Depth, Color.black);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (m_DepthMaterial == null)
{
Debug.LogError("Depth material is null in CustomDepthPass");
return;
}

var cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, m_ProfilingSampler))
{
// 创建绘制设置
var drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, SortingCriteria.CommonOpaque);

// 设置深度材质
drawingSettings.overrideMaterial = m_DepthMaterial;

// 执行渲染
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref m_FilteringSettings);
}

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

public override void FrameCleanup(ref RenderingData renderingData)
{
// 清理帧资源
}

// 初始化深度纹理句柄
public void SetupDepthTextureHandle(RenderTargetHandle depthHandle)
{
m_DepthTextureHandle = depthHandle;
}
}

13.8 自定义后处理渲染通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 自定义后处理渲染通道
public class CustomPostProcessPass : ScriptableRenderPass
{
private ProfilingSampler m_ProfilingSampler;
private Material m_PostProcessMaterial;
private int m_SourceTextureId;
private int m_DestinationTextureId;
private string m_KernelName;

public CustomPostProcessPass(RenderPassEvent renderPassEvent, Material postProcessMaterial, string kernelName)
{
this.renderPassEvent = renderPassEvent;
m_PostProcessMaterial = postProcessMaterial;
m_KernelName = kernelName;
m_ProfilingSampler = new ProfilingSampler("Custom Post Process Pass");

m_SourceTextureId = Shader.PropertyToID("_SourceTexture");
m_DestinationTextureId = Shader.PropertyToID("_DestinationTexture");
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 配置渲染目标为相机颜色纹理
ConfigureTarget(BuiltinRenderTextureType.CameraTarget);
ConfigureClear(ClearFlag.None, Color.black);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (m_PostProcessMaterial == null)
{
Debug.LogError("Post process material is null in CustomPostProcessPass");
return;
}

var cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, m_ProfilingSampler))
{
// 获取相机纹理
var source = renderingData.cameraData.renderer.cameraColorTarget;
var destination = RenderTargetHandle.CameraTarget;

// 设置材质属性
m_PostProcessMaterial.SetTexture(m_SourceTextureId, source);

// 执行后处理
cmd.Blit(source, destination.Identifier(), m_PostProcessMaterial);
}

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

public override void FrameCleanup(ref RenderingData renderingData)
{
// 清理帧资源
}

// 设置后处理参数
public void SetFloat(string name, float value)
{
if (m_PostProcessMaterial != null)
{
m_PostProcessMaterial.SetFloat(name, value);
}
}

public void SetVector(string name, Vector4 value)
{
if (m_PostProcessMaterial != null)
{
m_PostProcessMaterial.SetVector(name, value);
}
}

public void SetTexture(string name, Texture texture)
{
if (m_PostProcessMaterial != null)
{
m_PostProcessMaterial.SetTexture(name, texture);
}
}
}

13.9 自定义阴影渲染通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 自定义阴影渲染通道
public class CustomShadowPass : ScriptableRenderPass
{
private FilteringSettings m_FilteringSettings;
private List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>();
private Material m_ShadowCasterMaterial;
private ProfilingSampler m_ProfilingSampler;
private int m_MaxShadowCasters = 16;

public CustomShadowPass(RenderPassEvent renderPassEvent, Material shadowCasterMaterial)
{
this.renderPassEvent = renderPassEvent;
m_ShadowCasterMaterial = shadowCasterMaterial;
m_ProfilingSampler = new ProfilingSampler("Custom Shadow Pass");

// 只渲染需要投射阴影的物体
m_FilteringSettings = new FilteringSettings(RenderQueueRange.all);

// 添加阴影相关的Shader标签
m_ShaderTagIdList.Add(new ShaderTagId("ShadowCaster"));
m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));
m_ShaderTagIdList.Add(new ShaderTagId("LightweightForward"));
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 阴影通道通常不需要配置特定的渲染目标
// 因为它会渲染到阴影贴图中
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (m_ShadowCasterMaterial == null)
{
Debug.LogError("Shadow caster material is null in CustomShadowPass");
return;
}

var cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, m_ProfilingSampler))
{
// 创建阴影绘制设置
var shadowDrawingSettings = new ShadowDrawingSettings(renderingData.cullResults, 0);

// 设置阴影材质
shadowDrawingSettings.SetOverrideMaterial(m_ShadowCasterMaterial, 0);

// 执行阴影渲染
context.DrawShadows(ref shadowDrawingSettings);
}

context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

public override void FrameCleanup(ref RenderingData renderingData)
{
// 清理阴影相关资源
}

// 设置阴影参数
public void SetShadowBias(float bias, float normalBias)
{
if (m_ShadowCasterMaterial != null)
{
m_ShadowCasterMaterial.SetFloat("_ShadowBias", bias);
m_ShadowCasterMaterial.SetFloat("_NormalBias", normalBias);
}
}
}

13.10 RenderPass管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class RenderPassManager : MonoBehaviour
{
[System.Serializable]
public class RenderPassConfiguration
{
public string name;
public RenderPassEvent passEvent;
public bool isEnabled = true;
public Material material;
public string kernelName;
public float executionPriority = 0f;
}

[Header("Render Pass Configurations")]
public List<RenderPassConfiguration> passConfigurations = new List<RenderPassConfiguration>();

[Header("Runtime Settings")]
public bool enablePassManagement = true;
public bool logPassExecution = false;

private List<ScriptableRenderPass> m_RenderPasses = new List<ScriptableRenderPass>();
private Dictionary<string, ScriptableRenderPass> m_PassMap = new Dictionary<string, ScriptableRenderPass>();
private List<float> m_PassExecutionTimes = new List<float>();

void Start()
{
if (enablePassManagement)
{
InitializeRenderPasses();
}
}

private void InitializeRenderPasses()
{
m_RenderPasses.Clear();
m_PassMap.Clear();

// 按优先级排序配置
passConfigurations.Sort((a, b) => a.executionPriority.CompareTo(b.executionPriority));

foreach (var config in passConfigurations)
{
if (config.isEnabled)
{
var renderPass = CreateRenderPass(config);
if (renderPass != null)
{
m_RenderPasses.Add(renderPass);
m_PassMap[config.name] = renderPass;

if (logPassExecution)
{
Debug.Log($"[RenderPassManager] Created pass: {config.name} at event: {config.passEvent}");
}
}
}
}
}

private ScriptableRenderPass CreateRenderPass(RenderPassConfiguration config)
{
switch (config.name.ToLower())
{
case "customdepth":
return new CustomDepthPass(config.passEvent, config.material);
case "custompostprocess":
return new CustomPostProcessPass(config.passEvent, config.material, config.kernelName);
case "customshadow":
return new CustomShadowPass(config.passEvent, config.material);
default:
Debug.LogWarning($"[RenderPassManager] Unknown pass type: {config.name}");
return null;
}
}

// 添加自定义渲染通道
public void AddRenderPass(string name, ScriptableRenderPass pass, RenderPassEvent passEvent)
{
if (pass != null && !m_PassMap.ContainsKey(name))
{
pass.renderPassEvent = passEvent;
m_RenderPasses.Add(pass);
m_PassMap[name] = pass;

if (logPassExecution)
{
Debug.Log($"[RenderPassManager] Added custom pass: {name}");
}
}
}

// 移除渲染通道
public bool RemoveRenderPass(string name)
{
if (m_PassMap.ContainsKey(name))
{
var pass = m_PassMap[name];
m_RenderPasses.Remove(pass);
m_PassMap.Remove(name);

if (logPassExecution)
{
Debug.Log($"[RenderPassManager] Removed pass: {name}");
}

return true;
}
return false;
}

// 启用/禁用渲染通道
public bool SetPassEnabled(string name, bool enabled)
{
if (m_PassMap.ContainsKey(name))
{
var pass = m_PassMap[name];
// 通过从列表中添加或移除来控制启用状态
if (enabled && !m_RenderPasses.Contains(pass))
{
m_RenderPasses.Add(pass);
}
else if (!enabled && m_RenderPasses.Contains(pass))
{
m_RenderPasses.Remove(pass);
}

return true;
}
return false;
}

// 获取渲染通道
public ScriptableRenderPass GetRenderPass(string name)
{
if (m_PassMap.ContainsKey(name))
{
return m_PassMap[name];
}
return null;
}

// 获取所有渲染通道
public List<ScriptableRenderPass> GetAllRenderPasses()
{
return new List<ScriptableRenderPass>(m_RenderPasses);
}

// 执行所有渲染通道
public void ExecuteRenderPasses(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (!enablePassManagement) return;

// 按RenderPassEvent排序
m_RenderPasses.Sort((a, b) => a.renderPassEvent.CompareTo(b.renderPassEvent));

for (int i = 0; i < m_RenderPasses.Count; i++)
{
var pass = m_RenderPasses[i];
if (pass != null)
{
var startTime = Time.realtimeSinceStartup;

pass.Execute(context, ref renderingData);

var executionTime = (Time.realtimeSinceStartup - startTime) * 1000f; // 转换为毫秒
m_PassExecutionTimes.Add(executionTime);

if (logPassExecution)
{
Debug.Log($"[RenderPassManager] Executed pass: {pass.GetType().Name}, Time: {executionTime:F2}ms");
}
}
}
}

// 获取性能统计
public string GetPerformanceStats()
{
if (m_PassExecutionTimes.Count == 0) return "No pass execution data available";

float totalTime = 0f;
float maxTime = 0f;
float minTime = float.MaxValue;

foreach (var time in m_PassExecutionTimes)
{
totalTime += time;
if (time > maxTime) maxTime = time;
if (time < minTime) minTime = time;
}

float avgTime = totalTime / m_PassExecutionTimes.Count;

return $"Render Pass Performance:\n" +
$"Total Passes: {m_PassExecutionTimes.Count}\n" +
$"Avg Time: {avgTime:F2}ms\n" +
$"Max Time: {maxTime:F2}ms\n" +
$"Min Time: {minTime:F2}ms\n" +
$"Total Time: {totalTime:F2}ms";
}

// 重置性能统计
public void ResetPerformanceStats()
{
m_PassExecutionTimes.Clear();
}

// 验证渲染通道配置
public bool ValidatePassConfiguration()
{
bool isValid = true;
List<string> errors = new List<string>();

foreach (var config in passConfigurations)
{
if (config.isEnabled)
{
if (config.material == null && RequiresMaterial(config.name))
{
errors.Add($"Pass '{config.name}' requires a material but none is assigned");
isValid = false;
}
}
}

if (!isValid && logPassExecution)
{
foreach (var error in errors)
{
Debug.LogError($"[RenderPassManager] Configuration Error: {error}");
}
}

return isValid;
}

private bool RequiresMaterial(string passName)
{
return passName.ToLower().Contains("postprocess") ||
passName.ToLower().Contains("depth") ||
passName.ToLower().Contains("shadow");
}
}

实践练习

13.11 练习1:创建自定义渲染通道

目标:实现一个简单的自定义渲染通道

步骤

  1. 创建继承自ScriptableRenderPass的类
  2. 实现Configure方法配置渲染目标
  3. 实现Execute方法执行渲染逻辑
  4. 实现FrameCleanup方法清理资源
  5. 将自定义通道集成到渲染器中

实现要求

  • 正确设置RenderPassEvent
  • 处理渲染数据
  • 管理渲染资源

13.12 练习2:实现深度渲染通道

目标:创建专门的深度渲染通道

步骤

  1. 创建CustomDepthPass类
  2. 配置深度纹理格式
  3. 实现深度渲染逻辑
  4. 测试深度纹理生成
  5. 验证深度精度

技术要点

  • 深度纹理格式选择
  • 深度材质配置
  • 渲染队列过滤

13.13 练习3:实现后处理通道

目标:创建自定义后处理渲染通道

步骤

  1. 创建CustomPostProcessPass类
  2. 实现Blit操作
  3. 配置材质参数
  4. 测试后处理效果
  5. 优化性能

实现要素

  • 材质参数传递
  • 纹理采样
  • 性能优化

13.14 练习4:渲染通道性能测试

目标:测试不同渲染通道的性能

步骤

  1. 创建性能监控工具
  2. 记录各通道执行时间
  3. 分析性能瓶颈
  4. 优化通道实现
  5. 验证优化效果

监控指标

  • 执行时间
  • 内存使用
  • GPU占用率

13.15 练习5:多Pass数据传递

目标:实现Pass间的数据传递

步骤

  1. 创建共享渲染纹理
  2. 在输出Pass中写入数据
  3. 在输入Pass中读取数据
  4. 验证数据传递正确性
  5. 优化数据传递效率

传递方式

  • 渲染纹理共享
  • 常量缓冲区
  • 渲染状态

总结

第13章深入探讨了ScriptableRenderPass的实现原理和使用方法。ScriptableRenderPass是URP渲染管线中的基本构建块,通过它可以实现各种自定义渲染效果。

关键要点总结:

  1. 生命周期管理:理解Configure、Execute、FrameCleanup方法的作用
  2. 事件系统:掌握RenderPassEvent控制渲染顺序
  3. 资源管理:使用RTHandle进行类型安全的资源管理
  4. 数据传递:实现Pass间的高效数据传递
  5. 性能优化:优化渲染通道的执行效率

ScriptableRenderPass为开发者提供了强大的渲染定制能力,通过合理设计和实现自定义渲染通道,可以实现复杂的渲染效果和优化特定的渲染需求。理解其工作原理对于深入掌握URP渲染管线至关重要。

下一章将探讨RenderingData与Context的概念,了解渲染数据的传递和上下文管理机制。