第19章 自定义Renderer Feature实战

第19章 自定义Renderer Feature实战

理论讲解

19.1 全屏特效Renderer Feature

全屏特效Renderer Feature是URP中用于在渲染管线中应用全屏效果的扩展机制。它允许开发者在不修改渲染管线核心代码的情况下,添加自定义的全屏后处理效果。

全屏特效Renderer Feature的核心组件:

  1. ScriptableRendererFeature:渲染器功能的基类,负责创建和管理Render Pass
  2. ScriptableRenderPass:具体的渲染通道,执行实际的渲染操作
  3. CommandBuffer:用于执行渲染命令
  4. Material:用于执行全屏效果的材质

全屏特效的执行流程:

  1. 初始化阶段:在Create()方法中创建Render Pass并配置参数
  2. 渲染阶段:在AddRenderPasses()方法中将Pass添加到渲染队列
  3. 执行阶段:在Render Pass的Execute()方法中执行实际渲染
  4. 清理阶段:在FrameCleanup()方法中释放资源

全屏特效的类型:

  • 颜色调整效果:亮度、对比度、饱和度调整
  • 模糊效果:高斯模糊、径向模糊、运动模糊
  • 色彩效果:色差、色调映射、颜色分级
  • 几何效果:扭曲、缩放、旋转

19.2 描边效果实现

描边效果是游戏中常用的视觉效果,用于突出显示特定对象或创建艺术风格。在URP中实现描边效果有多种方法:

基于深度的描边:

  1. 深度差值检测:通过比较相邻像素的深度差异来检测边缘
  2. 法线差值检测:通过比较相邻像素的法线差异来检测边缘
  3. 组合检测:同时使用深度和法线信息来提高描边质量

描边效果的实现步骤:

  1. 渲染深度/法线纹理:首先渲染场景的深度和法线信息到纹理
  2. 边缘检测:使用Shader检测边缘像素
  3. 描边应用:将描边颜色应用到检测到的边缘
  4. 混合输出:将描边效果与原图混合

性能优化考虑:

  • 分辨率缩放:在较低分辨率下执行描边计算
  • 邻域大小:选择合适的邻域大小平衡质量和性能
  • 阈值调整:通过阈值控制描边的敏感度

19.3 屏幕空间反射

屏幕空间反射(Screen Space Reflection, SSR)是基于屏幕空间信息计算反射效果的技术,能够提供高质量的反射效果。

SSR实现原理:

  1. 屏幕空间追踪:从屏幕空间的像素位置追踪反射射线
  2. 深度缓冲利用:利用深度缓冲信息进行射线与场景的相交检测
  3. 反射颜色采样:从相交点采样反射颜色
  4. 混合处理:将反射颜色与原颜色混合

SSR的挑战:

  • 追踪精度:确保射线追踪的精度和稳定性
  • 性能开销:SSR计算通常比较昂贵
  • 边界处理:处理屏幕边界和遮挡情况

19.4 自定义深度处理

自定义深度处理允许开发者在渲染管线中插入自定义的深度处理逻辑,用于实现特殊效果或优化。

深度处理的应用:

  1. 深度预处理:在光照计算前对深度进行预处理
  2. 深度后处理:在渲染后对深度信息进行处理
  3. 深度可视化:将深度信息可视化用于调试
  4. 深度优化:优化深度缓冲的使用

深度处理的实现:

  • 深度纹理生成:生成高质量的深度纹理
  • 深度插值:实现精确的深度插值
  • 深度压缩:优化深度数据的存储

19.5 Feature的性能考量

在实现自定义Renderer Feature时,性能是一个关键考虑因素。需要在视觉效果和性能之间找到平衡。

性能优化策略:

  1. 减少Draw Call:合并相似的渲染操作
  2. 优化Shader:简化Shader计算,减少指令数
  3. 分辨率管理:在适当分辨率下执行效果
  4. 条件执行:只在需要时执行效果

性能监控指标:

  • 渲染时间:每个Pass的执行时间
  • 内存使用:临时纹理和缓冲区的内存占用
  • GPU负载:GPU的使用率和瓶颈
  • 帧率影响:对整体帧率的影响

19.6 多个Feature协同工作

在复杂的渲染管线中,通常需要多个Renderer Feature协同工作,需要考虑它们之间的兼容性和执行顺序。

协同工作的考虑:

  1. 执行顺序:确保Feature按正确的顺序执行
  2. 资源共享:避免重复创建相同的资源
  3. 状态管理:正确管理渲染状态
  4. 兼容性检查:确保Feature之间不冲突

代码示例

19.7 全屏特效Renderer Feature

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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 全屏特效渲染Pass
public class FullScreenEffectPass : ScriptableRenderPass
{
private Material m_EffectMaterial;
private ProfilingSampler m_ProfilingSampler;
private FullScreenEffectFeature.Settings m_Settings;

// 渲染纹理ID
private static readonly int k_SourceTextureId = Shader.PropertyToID("_SourceTexture");
private static readonly int k_DestinationTextureId = Shader.PropertyToID("_DestinationTexture");

public FullScreenEffectPass(FullScreenEffectFeature.Settings settings)
{
m_Settings = settings;
m_ProfilingSampler = new ProfilingSampler($"FullScreen {settings.effectName}");

// 加载材质
if (settings.material != null)
{
m_EffectMaterial = settings.material;
}
else if (!string.IsNullOrEmpty(settings.shaderName))
{
var shader = Shader.Find(settings.shaderName);
if (shader != null)
{
m_EffectMaterial = new Material(shader);
}
}

renderPassEvent = settings.renderPassEvent;
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
if (m_EffectMaterial == null)
{
return;
}

ConfigureInput(ScriptableRenderPassInput.Color);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (m_EffectMaterial == null)
{
return;
}

var cmd = CommandBufferPool.Get($"FullScreen {m_Settings.effectName}");

using (new ProfilingScope(cmd, m_ProfilingSampler))
{
var cameraData = renderingData.cameraData;
var source = cameraData.renderer.cameraColorTargetHandle;

// 如果相机使用MSAA,需要先解析到纹理
if (cameraData.resolveFinalTarget)
{
var descriptor = GetRenderTargetDescriptor(cameraData);
cmd.GetTemporaryRT(k_SourceTextureId, descriptor);
cmd.Blit(source, k_SourceTextureId);
source = RTHandles.Alloc(k_SourceTextureId);
}

// 设置材质参数
SetMaterialParameters();

// 执行全屏效果
ExecuteFullScreenEffect(cmd, source, cameraData);

// 如果之前创建了临时纹理,需要复制回原目标
if (cameraData.resolveFinalTarget)
{
cmd.Blit(source, cameraData.renderer.cameraColorTargetHandle);
cmd.ReleaseTemporaryRT(k_SourceTextureId);
}
}

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

private RenderTextureDescriptor GetRenderTargetDescriptor(CameraData cameraData)
{
var descriptor = cameraData.cameraTargetDescriptor;
descriptor.msaaSamples = 1;
descriptor.volumeDepth = 1;
descriptor.mipCount = 1;
descriptor.bounceUsage = RenderTextureSubElement.Color;
return descriptor;
}

private void SetMaterialParameters()
{
if (m_EffectMaterial == null) return;

// 设置通用参数
m_EffectMaterial.SetFloat("_Intensity", m_Settings.intensity);
m_EffectMaterial.SetFloat("_Threshold", m_Settings.threshold);
m_EffectMaterial.SetColor("_Color", m_Settings.color);
m_EffectMaterial.SetFloat("_Parameter1", m_Settings.parameter1);
m_EffectMaterial.SetFloat("_Parameter2", m_Settings.parameter2);
m_EffectMaterial.SetFloat("_Parameter3", m_Settings.parameter3);
m_EffectMaterial.SetFloat("_Parameter4", m_Settings.parameter4);
}

private void ExecuteFullScreenEffect(CommandBuffer cmd, RTHandle source, CameraData cameraData)
{
var descriptor = cameraData.cameraTargetDescriptor;

// 根据效果类型执行不同的处理
switch (m_Settings.effectType)
{
case FullScreenEffectFeature.EffectType.ColorAdjustment:
ExecuteColorAdjustment(cmd, source, descriptor);
break;
case FullScreenEffectFeature.EffectType.Blur:
ExecuteBlurEffect(cmd, source, descriptor);
break;
case FullScreenEffectFeature.EffectType.EdgeDetection:
ExecuteEdgeDetection(cmd, source, descriptor);
break;
case FullScreenEffectFeature.EffectType.Custom:
ExecuteCustomEffect(cmd, source, descriptor);
break;
default:
cmd.Blit(source, cameraData.renderer.cameraColorTargetHandle, m_EffectMaterial);
break;
}
}

private void ExecuteColorAdjustment(CommandBuffer cmd, RTHandle source, RenderTextureDescriptor descriptor)
{
// 颜色调整效果
cmd.Blit(source, RenderTargetHandle.CameraTarget, m_EffectMaterial);
}

private void ExecuteBlurEffect(CommandBuffer cmd, RTHandle source, RenderTextureDescriptor descriptor)
{
// 模糊效果 - 使用多Pass实现高斯模糊
var tempTexture1 = RTHandles.Alloc(descriptor);
var tempTexture2 = RTHandles.Alloc(descriptor);

// 水平模糊
cmd.Blit(source, tempTexture1, m_EffectMaterial, 0); // Pass 0: Horizontal Blur

// 垂直模糊
cmd.Blit(tempTexture1, tempTexture2, m_EffectMaterial, 1); // Pass 1: Vertical Blur

// 可能需要多次迭代以获得更强的模糊效果
for (int i = 0; i < m_Settings.blurIterations - 1; i++)
{
cmd.Blit(tempTexture2, tempTexture1, m_EffectMaterial, 0);
cmd.Blit(tempTexture1, tempTexture2, m_EffectMaterial, 1);
}

cmd.Blit(tempTexture2, RenderTargetHandle.CameraTarget, m_EffectMaterial, 2); // Pass 2: Final Blit

tempTexture1?.Release();
tempTexture2?.Release();
}

private void ExecuteEdgeDetection(CommandBuffer cmd, RTHandle source, RenderTextureDescriptor descriptor)
{
// 边缘检测效果
cmd.Blit(source, RenderTargetHandle.CameraTarget, m_EffectMaterial, 0);
}

private void ExecuteCustomEffect(CommandBuffer cmd, RTHandle source, RenderTextureDescriptor descriptor)
{
// 自定义效果
cmd.Blit(source, RenderTargetHandle.CameraTarget, m_EffectMaterial);
}

public override void FrameCleanup(CommandBuffer cmd)
{
// 清理临时资源
}

public Material GetMaterial()
{
return m_EffectMaterial;
}
}

// 全屏特效Feature
public class FullScreenEffectFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public string effectName = "Full Screen Effect";
public EffectType effectType = EffectType.ColorAdjustment;
public Material material;
public string shaderName = "Hidden/FullScreenEffect";
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;

[Header("Effect Parameters")]
public float intensity = 1.0f;
public float threshold = 0.5f;
public Color color = Color.white;
public float parameter1 = 0.0f;
public float parameter2 = 0.0f;
public float parameter3 = 0.0f;
public float parameter4 = 0.0f;

[Header("Blur Specific")]
public int blurIterations = 1;
public float blurRadius = 1.0f;

[Header("Performance")]
public bool enableEffect = true;
public bool useLowResolution = false;
public int resolutionScale = 1; // 1=full, 2=half, 4=quarter
}

public enum EffectType
{
ColorAdjustment,
Blur,
EdgeDetection,
Custom
}

public Settings settings = new Settings();
private FullScreenEffectPass m_FullScreenEffectPass;

public override void Create()
{
if (settings.enableEffect)
{
m_FullScreenEffectPass = new FullScreenEffectPass(settings);
}
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.enableEffect && m_FullScreenEffectPass != null)
{
renderer.EnqueuePass(m_FullScreenEffectPass);
}
}

// 动态更新设置
public void UpdateSettings(Settings newSettings)
{
settings = newSettings;
if (m_FullScreenEffectPass != null)
{
// 重新创建Pass以应用新设置
Create();
}
}

// 获取当前Pass
public FullScreenEffectPass GetPass()
{
return m_FullScreenEffectPass;
}
}

19.8 描边效果Renderer Feature

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 描边效果渲染Pass
public class OutlineEffectPass : ScriptableRenderPass
{
private Material m_OutlineMaterial;
private ProfilingSampler m_ProfilingSampler;
private OutlineEffectFeature.Settings m_Settings;

// 渲染纹理ID
private static readonly int k_DepthTextureId = Shader.PropertyToID("_CameraDepthTexture");
private static readonly int k_NormalTextureId = Shader.PropertyToID("_CameraNormalTexture");
private static readonly int k_OutlineTextureId = Shader.PropertyToID("_OutlineTexture");
private static readonly int k_SourceTextureId = Shader.PropertyToID("_SourceTexture");

public OutlineEffectPass(OutlineEffectFeature.Settings settings)
{
m_Settings = settings;
m_ProfilingSampler = new ProfilingSampler("Outline Effect Pass");

// 加载描边材质
if (settings.outlineMaterial != null)
{
m_OutlineMaterial = settings.outlineMaterial;
}
else
{
var shader = Shader.Find("Hidden/OutlineEffect");
if (shader != null)
{
m_OutlineMaterial = new Material(shader);
}
}

renderPassEvent = settings.renderPassEvent;
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
if (m_OutlineMaterial == null)
{
return;
}

// 配置输入:需要深度和法线纹理
ConfigureInput(ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (m_OutlineMaterial == null)
{
return;
}

var cmd = CommandBufferPool.Get("Outline Effect Pass");

using (new ProfilingScope(cmd, m_ProfilingSampler))
{
var cameraData = renderingData.cameraData;
var source = cameraData.renderer.cameraColorTargetHandle;

// 设置材质参数
SetMaterialParameters();

// 执行描边效果
ExecuteOutlineEffect(cmd, source, cameraData);
}

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

private void SetMaterialParameters()
{
if (m_OutlineMaterial == null) return;

// 设置描边参数
m_OutlineMaterial.SetFloat("_OutlineWidth", m_Settings.outlineWidth);
m_OutlineMaterial.SetColor("_OutlineColor", m_Settings.outlineColor);
m_OutlineMaterial.SetFloat("_DepthThreshold", m_Settings.depthThreshold);
m_OutlineMaterial.SetFloat("_NormalThreshold", m_Settings.normalThreshold);
m_OutlineMaterial.SetFloat("_Intensity", m_Settings.intensity);
m_OutlineMaterial.SetFloat("_FadeDistance", m_Settings.fadeDistance);
}

private void ExecuteOutlineEffect(CommandBuffer cmd, RTHandle source, CameraData cameraData)
{
var descriptor = cameraData.cameraTargetDescriptor;

// 创建临时纹理用于描边处理
var outlineTexture = RTHandles.Alloc(descriptor);

// 执行描边检测和渲染
if (m_Settings.outlineMethod == OutlineEffectFeature.OutlineMethod.DepthBased)
{
ExecuteDepthBasedOutline(cmd, source, outlineTexture, descriptor);
}
else if (m_Settings.outlineMethod == OutlineEffectFeature.OutlineMethod.NormalBased)
{
ExecuteNormalBasedOutline(cmd, source, outlineTexture, descriptor);
}
else
{
ExecuteCombinedOutline(cmd, source, outlineTexture, descriptor);
}

// 将描边结果与原图混合
cmd.Blit(outlineTexture, RenderTargetHandle.CameraTarget, m_OutlineMaterial, 1); // Pass 1: Blend

outlineTexture?.Release();
}

private void ExecuteDepthBasedOutline(CommandBuffer cmd, RTHandle source, RTHandle outlineTexture, RenderTextureDescriptor descriptor)
{
// 基于深度的描边
cmd.SetGlobalTexture(k_SourceTextureId, source.nameID);
cmd.SetGlobalTexture(k_DepthTextureId, cameraData.renderer.cameraDepthTargetHandle.nameID);

// 使用描边材质执行深度边缘检测
cmd.Blit(source, outlineTexture, m_OutlineMaterial, 0); // Pass 0: Depth Edge Detection
}

private void ExecuteNormalBasedOutline(CommandBuffer cmd, RTHandle source, RTHandle outlineTexture, RenderTextureDescriptor descriptor)
{
// 基于法线的描边
cmd.SetGlobalTexture(k_SourceTextureId, source.nameID);
cmd.SetGlobalTexture(k_NormalTextureId, cameraData.renderer.cameraDepthTargetHandle.nameID); // 注意:这里需要正常的法线纹理

// 使用描边材质执行法线边缘检测
cmd.Blit(source, outlineTexture, m_OutlineMaterial, 2); // Pass 2: Normal Edge Detection
}

private void ExecuteCombinedOutline(CommandBuffer cmd, RTHandle source, RTHandle outlineTexture, RenderTextureDescriptor descriptor)
{
// 组合深度和法线的描边
cmd.SetGlobalTexture(k_SourceTextureId, source.nameID);
cmd.SetGlobalTexture(k_DepthTextureId, cameraData.renderer.cameraDepthTargetHandle.nameID);
// 需要法线纹理的设置...

// 使用描边材质执行组合边缘检测
cmd.Blit(source, outlineTexture, m_OutlineMaterial, 3); // Pass 3: Combined Edge Detection
}

public override void FrameCleanup(CommandBuffer cmd)
{
// 清理临时资源
}

public Material GetMaterial()
{
return m_OutlineMaterial;
}
}

// 描边效果Feature
public class OutlineEffectFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public string effectName = "Outline Effect";
public OutlineMethod outlineMethod = OutlineMethod.Combined;
public Material outlineMaterial;
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;

[Header("Outline Parameters")]
public float outlineWidth = 1.0f;
public Color outlineColor = Color.red;
public float depthThreshold = 0.1f;
public float normalThreshold = 0.5f;
public float intensity = 1.0f;
public float fadeDistance = 10.0f;

[Header("Performance")]
public bool enableOutline = true;
public bool useLowResolution = false;
public int resolutionScale = 1;
}

public enum OutlineMethod
{
DepthBased,
NormalBased,
Combined
}

public Settings settings = new Settings();
private OutlineEffectPass m_OutlineEffectPass;

public override void Create()
{
if (settings.enableOutline)
{
m_OutlineEffectPass = new OutlineEffectPass(settings);
}
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.enableOutline && m_OutlineEffectPass != null)
{
renderer.EnqueuePass(m_OutlineEffectPass);
}
}

// 更新描边设置
public void UpdateOutlineSettings(Settings newSettings)
{
settings = newSettings;
if (m_OutlineEffectPass != null)
{
// 重新创建Pass以应用新设置
Create();
}
}

// 获取当前Pass
public OutlineEffectPass GetPass()
{
return m_OutlineEffectPass;
}
}

19.9 屏幕空间反射Renderer Feature

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 屏幕空间反射渲染Pass
public class ScreenSpaceReflectionPass : ScriptableRenderPass
{
private Material m_SSRMaterial;
private ProfilingSampler m_ProfilingSampler;
private ScreenSpaceReflectionFeature.Settings m_Settings;

// 渲染纹理ID
private static readonly int k_DepthTextureId = Shader.PropertyToID("_CameraDepthTexture");
private static readonly int k_NormalTextureId = Shader.PropertyToID("_CameraNormalTexture");
private static readonly int k_SSRTextureId = Shader.PropertyToID("_SSRTexture");
private static readonly int k_SourceTextureId = Shader.PropertyToID("_SourceTexture");
private static readonly int k_ReflectionTextureId = Shader.PropertyToID("_ReflectionTexture");

public ScreenSpaceReflectionPass(ScreenSpaceReflectionFeature.Settings settings)
{
m_Settings = settings;
m_ProfilingSampler = new ProfilingSampler("Screen Space Reflection Pass");

// 加载SSR材质
if (settings.ssrMaterial != null)
{
m_SSRMaterial = settings.ssrMaterial;
}
else
{
var shader = Shader.Find("Hidden/ScreenSpaceReflection");
if (shader != null)
{
m_SSRMaterial = new Material(shader);
}
}

renderPassEvent = settings.renderPassEvent;
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
if (m_SSRMaterial == null)
{
return;
}

// 配置输入:需要颜色、深度和法线纹理
ConfigureInput(ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (m_SSRMaterial == null)
{
return;
}

var cmd = CommandBufferPool.Get("Screen Space Reflection Pass");

using (new ProfilingScope(cmd, m_ProfilingSampler))
{
var cameraData = renderingData.cameraData;
var source = cameraData.renderer.cameraColorTargetHandle;

// 设置材质参数
SetMaterialParameters(cameraData);

// 执行SSR效果
ExecuteSSREffect(cmd, source, cameraData);
}

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

private void SetMaterialParameters(CameraData cameraData)
{
if (m_SSRMaterial == null) return;

// 设置SSR参数
m_SSRMaterial.SetFloat("_MaxDistance", m_Settings.maxDistance);
m_SSRMaterial.SetFloat("_FadeDistance", m_Settings.fadeDistance);
m_SSRMaterial.SetFloat("_Thickness", m_Settings.thickness);
m_SSRMaterial.SetFloat("_Intensity", m_Settings.intensity);
m_SSRMaterial.SetFloat("_Smoothness", m_Settings.smoothness);
m_SSRMaterial.SetFloat("_Roughness", 1.0f - m_Settings.smoothness);
m_SSRMaterial.SetFloat("_ReflectionScale", m_Settings.reflectionScale);
m_SSRMaterial.SetFloat("_FresnelPower", m_Settings.fresnelPower);

// 设置相机参数
m_SSRMaterial.SetMatrix("_InverseViewMatrix", cameraData.camera.cameraToWorldMatrix);
m_SSRMaterial.SetMatrix("_InverseProjectionMatrix", GL.GetGPUProjectionMatrix(cameraData.camera.projectionMatrix, true).inverse);
m_SSRMaterial.SetVector("_ScreenSize", new Vector2(cameraData.camera.pixelWidth, cameraData.camera.pixelHeight));
m_SSRMaterial.SetVector("_ScreenSizeReciprocal", new Vector2(1.0f / cameraData.camera.pixelWidth, 1.0f / cameraData.camera.pixelHeight));
}

private void ExecuteSSREffect(CommandBuffer cmd, RTHandle source, CameraData cameraData)
{
var descriptor = cameraData.cameraTargetDescriptor;

// 创建临时纹理用于SSR处理
var ssrTexture = RTHandles.Alloc(descriptor);

// 设置全局纹理
cmd.SetGlobalTexture(k_SourceTextureId, source.nameID);
cmd.SetGlobalTexture(k_DepthTextureId, cameraData.renderer.cameraDepthTargetHandle.nameID);
// 注意:法线纹理需要在渲染管线中生成

// 执行SSR追踪和反射计算
cmd.Blit(source, ssrTexture, m_SSRMaterial, 0); // Pass 0: SSR Ray Tracing

// 将SSR结果与原图混合
m_SSRMaterial.SetTexture("_ReflectionTexture", ssrTexture);
cmd.Blit(source, RenderTargetHandle.CameraTarget, m_SSRMaterial, 1); // Pass 1: Reflection Blending

ssrTexture?.Release();
}

public override void FrameCleanup(CommandBuffer cmd)
{
// 清理临时资源
}

public Material GetMaterial()
{
return m_SSRMaterial;
}
}

// 屏幕空间反射Feature
public class ScreenSpaceReflectionFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public string effectName = "Screen Space Reflection";
public Material ssrMaterial;
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPrePasses;

[Header("SSR Parameters")]
public float maxDistance = 100.0f;
public float fadeDistance = 100.0f;
public float thickness = 0.1f;
public float intensity = 1.0f;
public float smoothness = 0.5f;
public float reflectionScale = 1.0f;
public float fresnelPower = 5.0f;

[Header("Performance")]
public bool enableSSR = true;
public int raySteps = 32;
public float binarySearchIterations = 5;
public bool useBilateralFiltering = true;
public int bilateralFilterSampleCount = 8;

[Header("Quality Settings")]
public bool highQuality = true;
public int resolutionScale = 2; // 降低分辨率以提高性能
}

public Settings settings = new Settings();
private ScreenSpaceReflectionPass m_SSRPass;

public override void Create()
{
if (settings.enableSSR)
{
m_SSRPass = new ScreenSpaceReflectionPass(settings);
}
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.enableSSR && m_SSRPass != null)
{
renderer.EnqueuePass(m_SSRPass);
}
}

// 更新SSR设置
public void UpdateSSRSettings(Settings newSettings)
{
settings = newSettings;
if (m_SSRPass != null)
{
// 重新创建Pass以应用新设置
Create();
}
}

// 获取当前Pass
public ScreenSpaceReflectionPass GetPass()
{
return m_SSRPass;
}
}

19.10 自定义深度处理Renderer Feature

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
249
250
251
252
253
254
255
256
257
258
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 自定义深度处理渲染Pass
public class CustomDepthProcessingPass : ScriptableRenderPass
{
private Material m_DepthMaterial;
private ProfilingSampler m_ProfilingSampler;
private CustomDepthProcessingFeature.Settings m_Settings;

// 渲染纹理ID
private static readonly int k_DepthTextureId = Shader.PropertyToID("_CameraDepthTexture");
private static readonly int k_DepthOutputTextureId = Shader.PropertyToID("_CustomDepthTexture");
private static readonly int k_SourceTextureId = Shader.PropertyToID("_SourceTexture");

public CustomDepthProcessingPass(CustomDepthProcessingFeature.Settings settings)
{
m_Settings = settings;
m_ProfilingSampler = new ProfilingSampler("Custom Depth Processing Pass");

// 加载深度处理材质
if (settings.depthMaterial != null)
{
m_DepthMaterial = settings.depthMaterial;
}
else
{
var shader = Shader.Find("Hidden/CustomDepthProcessing");
if (shader != null)
{
m_DepthMaterial = new Material(shader);
}
}

renderPassEvent = settings.renderPassEvent;
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
if (m_DepthMaterial == null)
{
return;
}

// 配置输入:需要深度信息
ConfigureInput(ScriptableRenderPassInput.Depth);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (m_DepthMaterial == null)
{
return;
}

var cmd = CommandBufferPool.Get("Custom Depth Processing Pass");

using (new ProfilingScope(cmd, m_ProfilingSampler))
{
var cameraData = renderingData.cameraData;

// 设置材质参数
SetMaterialParameters(cameraData);

// 执行自定义深度处理
ExecuteDepthProcessing(cmd, cameraData);
}

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

private void SetMaterialParameters(CameraData cameraData)
{
if (m_DepthMaterial == null) return;

// 设置深度处理参数
m_DepthMaterial.SetFloat("_DepthScale", m_Settings.depthScale);
m_DepthMaterial.SetFloat("_DepthOffset", m_Settings.depthOffset);
m_DepthMaterial.SetFloat("_NearPlane", cameraData.camera.nearClipPlane);
m_DepthMaterial.SetFloat("_FarPlane", cameraData.camera.farClipPlane);
m_DepthMaterial.SetFloat("_Intensity", m_Settings.intensity);
m_DepthMaterial.SetFloat("_Smoothness", m_Settings.smoothness);
m_DepthMaterial.SetVector("_DepthRange", new Vector4(m_Settings.minDepth, m_Settings.maxDepth, 0, 0));

// 设置相机参数
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(cameraData.camera.projectionMatrix, true);
m_DepthMaterial.SetMatrix("_ProjectionMatrix", projectionMatrix);
m_DepthMaterial.SetMatrix("_InverseProjectionMatrix", projectionMatrix.inverse);
}

private void ExecuteDepthProcessing(CommandBuffer cmd, CameraData cameraData)
{
var descriptor = cameraData.cameraTargetDescriptor;

// 根据处理类型执行不同的深度处理
switch (m_Settings.processingType)
{
case CustomDepthProcessingFeature.DepthProcessingType.Visualization:
ExecuteDepthVisualization(cmd, cameraData, descriptor);
break;
case CustomDepthProcessingFeature.DepthProcessingType.Filtering:
ExecuteDepthFiltering(cmd, cameraData, descriptor);
break;
case CustomDepthProcessingFeature.DepthProcessingType.Optimization:
ExecuteDepthOptimization(cmd, cameraData, descriptor);
break;
case CustomDepthProcessingFeature.DepthProcessingType.Custom:
ExecuteCustomDepthProcessing(cmd, cameraData, descriptor);
break;
}
}

private void ExecuteDepthVisualization(CommandBuffer cmd, CameraData cameraData, RenderTextureDescriptor descriptor)
{
// 深度可视化处理
cmd.SetGlobalTexture(k_DepthTextureId, cameraData.renderer.cameraDepthTargetHandle.nameID);

// 创建输出纹理
var outputTexture = RTHandles.Alloc(descriptor);

// 执行深度可视化
cmd.Blit(cameraData.renderer.cameraDepthTargetHandle, outputTexture, m_DepthMaterial, 0); // Pass 0: Depth Visualization

// 如果需要,可以将结果输出到特定目标
if (m_Settings.outputToTexture)
{
cmd.SetGlobalTexture(k_DepthOutputTextureId, outputTexture.nameID);
}

outputTexture?.Release();
}

private void ExecuteDepthFiltering(CommandBuffer cmd, CameraData cameraData, RenderTextureDescriptor descriptor)
{
// 深度过滤处理
cmd.SetGlobalTexture(k_DepthTextureId, cameraData.renderer.cameraDepthTargetHandle.nameID);

// 创建临时纹理用于过滤处理
var tempTexture = RTHandles.Alloc(descriptor);

// 执行深度过滤
cmd.Blit(cameraData.renderer.cameraDepthTargetHandle, tempTexture, m_DepthMaterial, 1); // Pass 1: Depth Filtering

// 将过滤后的深度写回深度缓冲(如果需要)
if (m_Settings.writeFilteredDepth)
{
cmd.SetRenderTarget(cameraData.renderer.cameraDepthTargetHandle);
cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, m_DepthMaterial, 0, 2); // Pass 2: Write Filtered Depth
}

tempTexture?.Release();
}

private void ExecuteDepthOptimization(CommandBuffer cmd, CameraData cameraData, RenderTextureDescriptor descriptor)
{
// 深度优化处理
cmd.SetGlobalTexture(k_DepthTextureId, cameraData.renderer.cameraDepthTargetHandle.nameID);

// 执行深度优化算法
cmd.Blit(cameraData.renderer.cameraDepthTargetHandle, RenderTargetHandle.CameraTarget, m_DepthMaterial, 3); // Pass 3: Depth Optimization
}

private void ExecuteCustomDepthProcessing(CommandBuffer cmd, CameraData cameraData, RenderTextureDescriptor descriptor)
{
// 自定义深度处理
cmd.SetGlobalTexture(k_DepthTextureId, cameraData.renderer.cameraDepthTargetHandle.nameID);

// 根据自定义参数执行处理
cmd.Blit(cameraData.renderer.cameraDepthTargetHandle, RenderTargetHandle.CameraTarget, m_DepthMaterial);
}

public override void FrameCleanup(CommandBuffer cmd)
{
// 清理临时资源
}

public Material GetMaterial()
{
return m_DepthMaterial;
}
}

// 自定义深度处理Feature
public class CustomDepthProcessingFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public string effectName = "Custom Depth Processing";
public DepthProcessingType processingType = DepthProcessingType.Visualization;
public Material depthMaterial;
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingOpaques;

[Header("Depth Processing Parameters")]
public float depthScale = 1.0f;
public float depthOffset = 0.0f;
public float minDepth = 0.0f;
public float maxDepth = 100.0f;
public float intensity = 1.0f;
public float smoothness = 0.5f;

[Header("Processing Options")]
public bool outputToTexture = false;
public bool writeFilteredDepth = false;
public bool enableProcessing = true;

[Header("Performance")]
public bool useLowResolution = false;
public int resolutionScale = 1;
}

public enum DepthProcessingType
{
Visualization,
Filtering,
Optimization,
Custom
}

public Settings settings = new Settings();
private CustomDepthProcessingPass m_DepthProcessingPass;

public override void Create()
{
if (settings.enableProcessing)
{
m_DepthProcessingPass = new CustomDepthProcessingPass(settings);
}
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.enableProcessing && m_DepthProcessingPass != null)
{
renderer.EnqueuePass(m_DepthProcessingPass);
}
}

// 更新深度处理设置
public void UpdateDepthProcessingSettings(Settings newSettings)
{
settings = newSettings;
if (m_DepthProcessingPass != null)
{
// 重新创建Pass以应用新设置
Create();
}
}

// 获取当前Pass
public CustomDepthProcessingPass GetPass()
{
return m_DepthProcessingPass;
}
}

实践练习

19.11 练习1:全屏特效实现

目标:创建一个完整的全屏特效Renderer Feature

步骤

  1. 创建FullScreenEffectPass类
  2. 实现多种全屏效果(颜色调整、模糊、边缘检测等)
  3. 集成到Renderer Feature
  4. 测试效果参数调整
  5. 优化性能表现

实现要点

  • 多Pass渲染技术
  • 材质参数管理
  • 性能优化策略

19.12 练习2:描边效果实现

目标:实现基于深度和法线的描边效果

步骤

  1. 创建OutlineEffectPass类
  2. 实现深度和法线边缘检测
  3. 集成到Renderer Feature
  4. 测试不同参数效果
  5. 优化边缘检测算法

技术要点

  • 边缘检测算法
  • 深度/法线纹理使用
  • 性能优化

19.13 练习3:屏幕空间反射

目标:实现屏幕空间反射效果

步骤

  1. 创建ScreenSpaceReflectionPass类
  2. 实现射线追踪算法
  3. 集成到Renderer Feature
  4. 测试反射质量
  5. 优化性能表现

技术挑战

  • 射线追踪算法
  • 性能优化
  • 视觉质量平衡

19.14 练习4:深度处理系统

目标:创建自定义深度处理系统

步骤

  1. 创建CustomDepthProcessingPass类
  2. 实现多种深度处理模式
  3. 集成到Renderer Feature
  4. 测试不同处理效果
  5. 优化处理算法

功能特性

  • 深度可视化
  • 深度过滤
  • 深度优化

19.15 练习5:Feature性能优化

目标:优化多个Feature的性能

步骤

  1. 分析多个Feature的性能影响
  2. 实现Feature间的资源共享
  3. 优化执行顺序
  4. 测试性能提升
  5. 验证视觉效果

优化策略

  • 资源共享
  • 执行顺序优化
  • 条件执行

总结

第19章详细介绍了自定义Renderer Feature的实现方法,包括全屏特效、描边效果、屏幕空间反射和自定义深度处理等高级渲染技术。通过这些实践,我们学习了如何扩展URP渲染管线以实现自定义的视觉效果。

关键要点总结:

  1. Renderer Feature结构:ScriptableRendererFeature和ScriptableRenderPass的配合使用
  2. 全屏特效:基于材质的全屏后处理效果实现
  3. 描边效果:基于深度和法线的边缘检测技术
  4. 屏幕空间反射:复杂的射线追踪反射算法
  5. 深度处理:自定义深度信息处理和优化
  6. 性能优化:在视觉效果和性能间找到平衡

自定义Renderer Feature是URP扩展性的核心,允许开发者在不修改引擎源码的情况下添加复杂的渲染效果。理解这些技术对于创建高质量的视觉效果至关重要。