第17章 后处理源码分析

第17章 后处理源码分析

理论讲解

17.1 PostProcessPass执行流程

PostProcessPass是URP中负责执行后处理效果的核心渲染通道,它在渲染管线的后期阶段对整个屏幕应用各种视觉效果。

PostProcessPass的主要功能:

  1. 效果应用:将各种后处理效果应用到渲染结果
  2. 资源管理:管理后处理所需的纹理和缓冲区
  3. 性能优化:通过合批和优化减少渲染开销
  4. 兼容性处理:确保效果在不同平台上的兼容性

PostProcessPass执行流程:

  1. 配置阶段:设置渲染目标和渲染状态
  2. 效果收集:收集当前相机需要应用的后处理效果
  3. 渲染执行:按顺序应用各种后处理效果
  4. 结果输出:将处理后的结果输出到最终目标

后处理执行时机:

  • 渲染完成后:在所有不透明和透明物体渲染完成后
  • 相机渲染结束前:在相机渲染结束之前应用
  • 多相机支持:每个相机可以独立应用后处理

17.2 Volume Framework源码

Volume Framework是URP中用于管理后处理效果参数的系统,它通过体积对象来控制效果的强度和范围。

Volume Framework核心组件:

  1. Volume:定义效果影响的3D空间区域
  2. Volume Profile:包含后处理效果参数的配置文件
  3. Volume Component:具体的后处理效果实现
  4. Volume Stack:管理多个Volume的混合

Volume Framework工作原理:

  1. 空间查询:确定相机位置与Volume的相交关系
  2. 参数混合:根据距离和权重混合多个Volume的参数
  3. 效果应用:将混合后的参数应用到后处理Pass
  4. 动态更新:实时更新参数变化

Volume的类型:

  • Global Volume:影响整个场景的全局效果
  • Local Volume:影响特定区域的局部效果
  • Trigger Volume:基于触发器的动态效果

17.3 各后处理效果的Shader实现

URP中的后处理效果通过专门的Shader实现,这些Shader运行在屏幕空间上。

主要后处理效果:

  1. Bloom:模拟光线溢出效果
  2. Color Grading:颜色分级和色调调整
  3. Tonemapping:HDR到LDR的转换
  4. Depth of Field:景深效果
  5. Motion Blur:运动模糊
  6. Vignette:暗角效果
  7. Chromatic Aberration:色差效果

Shader实现特点:

  • 全屏处理:对整个屏幕进行像素级处理
  • 多Pass优化:通过多个Pass实现复杂效果
  • 性能考虑:优化算法以提高执行效率
  • 精度控制:平衡视觉效果和性能

17.4 Uber Post Processing合批

Uber Post Processing是URP中的一种优化技术,通过将多个后处理效果合并在一个Pass中执行来减少渲染开销。

合批机制:

  1. 效果分析:分析当前需要应用的效果
  2. Pass合并:将兼容的效果合并到一个Pass
  3. Shader变体:生成包含多个效果的Shader变体
  4. 参数传递:统一传递所有效果所需的参数

合批优势:

  • 减少Draw Call:降低渲染批次数量
  • 优化带宽:减少纹理读写操作
  • 提升性能:提高渲染效率
  • 简化流程:统一后处理执行流程

合批限制:

  • 兼容性检查:确保效果之间不冲突
  • 资源限制:考虑Shader复杂度和参数数量
  • 平台差异:不同平台的合批策略可能不同

17.5 RenderTexture管理与复用

后处理系统需要大量临时纹理来存储中间结果,有效的纹理管理对性能至关重要。

纹理管理策略:

  1. 对象池:使用纹理对象池减少分配开销
  2. 生命周期:精确控制纹理的创建和销毁
  3. 格式优化:选择合适的纹理格式
  4. 分辨率管理:根据需要调整纹理分辨率

纹理复用机制:

  • 多帧复用:在多帧间复用纹理
  • 效果间复用:不同效果间共享纹理
  • 内存优化:减少内存碎片
  • 异步释放:异步释放不再使用的纹理

17.6 Blit操作优化

Blit操作是后处理中常用的纹理复制和处理操作,优化Blit操作对性能有重要影响。

Blit操作类型:

  1. 纹理复制:将纹理内容复制到另一个纹理
  2. 格式转换:在复制过程中转换纹理格式
  3. 缩放处理:在复制过程中进行缩放
  4. 效果应用:在复制过程中应用效果

Blit优化策略:

  • 批处理:将多个Blit操作合并
  • 异步执行:使用异步Blit减少等待时间
  • 内存对齐:优化内存访问模式
  • GPU优化:利用GPU特性提高效率

代码示例

17.7 自定义后处理Pass

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
268
269
270
271
272
273
274
275
276
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomPostProcessPass : ScriptableRenderPass
{
[System.Serializable]
public class Settings
{
public bool active = true;
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
public Material material;
public string shaderName = "Hidden/CustomPostProcess";

[Header("Effect Parameters")]
public float intensity = 1.0f;
public float threshold = 0.5f;
public float softKnee = 0.5f;
public Color color = Color.white;
public float blurRadius = 1.0f;
public int blurIterations = 1;
public bool useDithering = false;
}

private Settings m_Settings;
private ProfilingSampler m_ProfilingSampler;
private RTHandle m_Source;
private RTHandle m_Destination;
private RTHandle m_TemporaryColorTexture;
private RTHandle m_BlurTexture1;
private RTHandle m_BlurTexture2;

// 后处理效果的参数ID
private static readonly int k_SourceTexId = Shader.PropertyToID("_SourceTex");
private static readonly int k_DestinationTexId = Shader.PropertyToID("_DestinationTex");
private static readonly int k_IntensityId = Shader.PropertyToID("_Intensity");
private static readonly int k_ThresholdId = Shader.PropertyToID("_Threshold");
private static readonly int k_SoftKneeId = Shader.PropertyToID("_SoftKnee");
private static readonly int k_ColorId = Shader.PropertyToID("_Color");
private static readonly int k_BlurRadiusId = Shader.PropertyToID("_BlurRadius");
private static readonly int k_TexelSizeId = Shader.PropertyToID("_TexelSize");

public CustomPostProcessPass(Settings settings)
{
m_Settings = settings;
m_ProfilingSampler = new ProfilingSampler("Custom Post Process Pass");

// 设置渲染事件
renderPassEvent = settings.renderPassEvent;
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
if (!m_Settings.active || m_Settings.material == null)
{
return;
}

// 配置渲染目标
ConfigureInput(ScriptableRenderPassInput.Color);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (!m_Settings.active || m_Settings.material == null)
{
return;
}

var cmd = CommandBufferPool.Get("Custom Post Process Pass");

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_SourceTexId, descriptor);
cmd.Blit(source, k_SourceTexId);
m_Source = RTHandles.Alloc(k_SourceTexId);
}
else
{
m_Source = source;
}

// 执行后处理效果
ExecutePostProcess(cmd, cameraData);

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

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 ExecutePostProcess(CommandBuffer cmd, CameraData cameraData)
{
// 设置材质参数
m_Settings.material.SetFloat(k_IntensityId, m_Settings.intensity);
m_Settings.material.SetFloat(k_ThresholdId, m_Settings.threshold);
m_Settings.material.SetFloat(k_SoftKneeId, m_Settings.softKnee);
m_Settings.material.SetColor(k_ColorId, m_Settings.color);
m_Settings.material.SetFloat(k_BlurRadiusId, m_Settings.blurRadius);

// 获取目标纹理
var descriptor = cameraData.cameraTargetDescriptor;
m_Destination = RTHandles.Alloc(
descriptor.width,
descriptor.height,
descriptor.colorFormat,
descriptor.depthBufferBits,
FilterMode.Bilinear,
TextureWrapMode.Clamp,
1,
0,
RenderTextureMemoryless.None,
descriptor.graphicsFormat,
true,
false
);

// 执行后处理效果
if (m_Settings.blurIterations > 0)
{
// 执行模糊效果
ExecuteBlurEffect(cmd, descriptor);
}
else
{
// 直接应用效果
cmd.Blit(m_Source, m_Destination, m_Settings.material);
}
}

private void ExecuteBlurEffect(CommandBuffer cmd, RenderTextureDescriptor descriptor)
{
// 创建临时纹理用于模糊处理
var blurDescriptor = descriptor;
blurDescriptor.width = descriptor.width / 2; // 降采样以提高性能
blurDescriptor.height = descriptor.height / 2;

m_BlurTexture1 = RTHandles.Alloc(blurDescriptor);
m_BlurTexture2 = RTHandles.Alloc(blurDescriptor);

// 降采样源纹理
cmd.Blit(m_Source, m_BlurTexture1);

// 执行多次模糊迭代
for (int i = 0; i < m_Settings.blurIterations; i++)
{
// 水平模糊
m_Settings.material.SetVector(k_TexelSizeId, new Vector4(1.0f / m_BlurTexture1.rt.width, 0, 0, 0));
cmd.Blit(m_BlurTexture1, m_BlurTexture2, m_Settings.material, 0); // 假设Pass 0是水平模糊

// 垂直模糊
m_Settings.material.SetVector(k_TexelSizeId, new Vector4(0, 1.0f / m_BlurTexture2.rt.height, 0, 0));
cmd.Blit(m_BlurTexture2, m_BlurTexture1, m_Settings.material, 1); // 假设Pass 1是垂直模糊
}

// 将模糊结果放大并应用到最终目标
cmd.Blit(m_BlurTexture1, m_Destination, m_Settings.material, 2); // 假设Pass 2是最终合成

// 释放临时纹理
m_BlurTexture1?.Release();
m_BlurTexture2?.Release();
}

public override void FrameCleanup(CommandBuffer cmd)
{
if (m_TemporaryColorTexture != null)
{
m_TemporaryColorTexture?.Release();
m_TemporaryColorTexture = null;
}

if (m_Destination != null)
{
m_Destination?.Release();
m_Destination = null;
}

if (m_Source != null && m_Source != RTHandles.CameraTarget)
{
m_Source?.Release();
m_Source = null;
}
}

// 获取后处理设置
public Settings GetSettings()
{
return m_Settings;
}

// 更新后处理参数
public void UpdateSettings(Settings newSettings)
{
m_Settings = newSettings;
}

// 检查是否激活
public bool IsActive()
{
return m_Settings.active && m_Settings.material != null;
}
}

// 自定义后处理Feature
public class CustomPostProcessFeature : ScriptableRendererFeature
{
[System.Serializable]
public class CustomPostProcessSettings
{
public CustomPostProcessPass.Settings passSettings = new CustomPostProcessPass.Settings();
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
}

public CustomPostProcessSettings settings = new CustomPostProcessSettings();
private CustomPostProcessPass m_CustomPostProcessPass;

public override void Create()
{
settings.passSettings.renderPassEvent = settings.renderPassEvent;

m_CustomPostProcessPass = new CustomPostProcessPass(settings.passSettings);

// 如果没有提供材质,尝试加载默认材质
if (settings.passSettings.material == null)
{
var shader = Shader.Find(settings.passSettings.shaderName);
if (shader != null)
{
settings.passSettings.material = new Material(shader);
}
}
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (renderingData.cameraData.postProcessEnabled && m_CustomPostProcessPass.IsActive())
{
renderer.EnqueuePass(m_CustomPostProcessPass);
}
}

// 更新后处理设置
public void UpdateSettings(CustomPostProcessPass.Settings newSettings)
{
if (m_CustomPostProcessPass != null)
{
m_CustomPostProcessPass.UpdateSettings(newSettings);
}
}
}

17.8 Volume组件系统

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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

// 自定义Volume组件基类
[Serializable, VolumeComponentMenu("Custom/CustomEffect")]
public class CustomEffect : VolumeComponent, IPostProcessComponent
{
[Tooltip("Controls the overall intensity of the effect.")]
public ClampedFloatParameter intensity = new ClampedFloatParameter(0f, 0f, 1f);

[Tooltip("Threshold value for the effect.")]
public ClampedFloatParameter threshold = new ClampedFloatParameter(0.5f, 0f, 1f);

[Tooltip("Soft knee value for smooth transitions.")]
public ClampedFloatParameter softKnee = new ClampedFloatParameter(0.5f, 0f, 1f);

[Tooltip("Color tint for the effect.")]
public ColorParameter color = new ColorParameter(Color.white, true, false, true);

[Tooltip("Blur radius for the effect.")]
public ClampedFloatParameter blurRadius = new ClampedFloatParameter(1.0f, 0f, 10f);

[Tooltip("Number of blur iterations.")]
public ClampedIntParameter blurIterations = new ClampedIntParameter(1, 0, 5);

[Tooltip("Enable dithering for the effect.")]
public BoolParameter useDithering = new BoolParameter(false);

public bool IsActive() => intensity.value > 0f;

public bool IsTileCompatible() => false;
}

// Volume系统管理器
public class VolumeSystemManager : MonoBehaviour
{
[Header("Volume Settings")]
public bool enableVolumeSystem = true;
public bool logVolumeData = true;

[Header("Performance Settings")]
public float volumeUpdateInterval = 0.1f;
public int maxVolumeQueriesPerFrame = 10;
public bool enableAsyncVolumeProcessing = true;

private VolumeStack m_GlobalVolumeStack;
private List<Volume> m_LocalVolumes = new List<Volume>();
private Dictionary<Camera, VolumeStack> m_CameraVolumeStacks = new Dictionary<Camera, VolumeStack>();
private float m_LastVolumeUpdate = 0f;
private int m_VolumeQueriesThisFrame = 0;

[System.Serializable]
public class VolumeAnalysisData
{
public int totalVolumes;
public int globalVolumes;
public int localVolumes;
public int activeComponents;
public float averageIntensity;
public float maxDistance;
public float minDistance;
public bool hasOverlappingVolumes;
public System.DateTime lastUpdate;
}

[System.Serializable]
public class VolumePerformanceData
{
public int volumeQueriesPerSecond;
public float volumeUpdateTimeMS;
public int activeVolumeComponents;
public float memoryUsageKB;
public bool isOptimal;
}

public VolumeAnalysisData analysisData = new VolumeAnalysisData();
public VolumePerformanceData performanceData = new VolumePerformanceData();

void Start()
{
if (enableVolumeSystem)
{
InitializeVolumeSystem();
}
}

void Update()
{
if (enableVolumeSystem && Time.time - m_LastVolumeUpdate >= volumeUpdateInterval)
{
UpdateVolumeSystem();
m_LastVolumeUpdate = Time.time;
}
}

private void InitializeVolumeSystem()
{
// 创建全局Volume Stack
m_GlobalVolumeStack = VolumeManager.instance.CreateStack();

// 查找场景中的所有Volume
FindAllVolumes();

if (logVolumeData)
{
Debug.Log($"[VolumeSystem] Initialized with {m_LocalVolumes.Count} local volumes");
}
}

private void FindAllVolumes()
{
m_LocalVolumes.Clear();

// 查找场景中的所有Volume组件
var volumes = FindObjectsOfType<Volume>();
foreach (var volume in volumes)
{
if (volume != null && volume.profile != null)
{
m_LocalVolumes.Add(volume);
}
}

// 分析Volume数据
AnalyzeVolumeData();
}

private void UpdateVolumeSystem()
{
// 更新所有Volume
UpdateAllVolumes();

// 分析性能数据
AnalyzePerformanceData();

if (logVolumeData)
{
LogVolumePerformance();
}
}

private void UpdateAllVolumes()
{
m_VolumeQueriesThisFrame = 0;

// 更新相机特定的Volume Stack
var cameras = FindObjectsOfType<Camera>();
foreach (var camera in cameras)
{
if (camera != null)
{
UpdateCameraVolumeStack(camera);
}
}

// 重置帧计数
m_VolumeQueriesThisFrame = 0;
}

private void UpdateCameraVolumeStack(Camera camera)
{
if (!m_CameraVolumeStacks.ContainsKey(camera))
{
m_CameraVolumeStacks[camera] = VolumeManager.instance.CreateStack();
}

var stack = m_CameraVolumeStacks[camera];

// 清空当前Stack
stack.Reset();

// 添加全局Volume
var globalVolumes = FindObjectsOfType<Volume>();
foreach (var volume in globalVolumes)
{
if (volume != null && volume.isGlobal)
{
stack.AddVolume(volume);
}
}

// 添加局部Volume(基于相机位置)
foreach (var volume in m_LocalVolumes)
{
if (volume != null && !volume.isGlobal && IsCameraInVolumeRange(camera, volume))
{
stack.AddVolume(volume);
}
}

m_VolumeQueriesThisFrame++;
}

private bool IsCameraInVolumeRange(Camera camera, Volume volume)
{
if (volume == null || camera == null) return false;

// 计算相机到Volume的距离
float distance = Vector3.Distance(camera.transform.position, volume.transform.position);

// 使用Volume的bounds来判断是否在范围内
var bounds = volume.GetComponent<Collider>()?.bounds;
if (bounds != null)
{
return bounds.Contains(camera.transform.position);
}

// 如果没有Collider,使用一个默认范围
return distance < 10.0f; // 默认10单位范围
}

private void AnalyzeVolumeData()
{
analysisData.totalVolumes = m_LocalVolumes.Count;
analysisData.localVolumes = m_LocalVolumes.Count;
analysisData.globalVolumes = 0;
analysisData.activeComponents = 0;
analysisData.averageIntensity = 0f;
analysisData.maxDistance = 0f;
analysisData.minDistance = float.MaxValue;
analysisData.hasOverlappingVolumes = false;
analysisData.lastUpdate = System.DateTime.Now;

float intensitySum = 0f;

foreach (var volume in m_LocalVolumes)
{
if (volume != null && volume.profile != null)
{
// 计算激活的组件数量
var components = volume.profile.components;
foreach (var component in components)
{
if (component != null && component.IsActive())
{
analysisData.activeComponents++;
}
}

// 计算平均强度
var customEffect = volume.profile.GetComponent<CustomEffect>();
if (customEffect != null)
{
intensitySum += customEffect.intensity.value;
}
}
}

if (analysisData.activeComponents > 0)
{
analysisData.averageIntensity = intensitySum / analysisData.activeComponents;
}

// 检查是否有重叠的Volume
for (int i = 0; i < m_LocalVolumes.Count; i++)
{
for (int j = i + 1; j < m_LocalVolumes.Count; j++)
{
if (DoVolumesOverlap(m_LocalVolumes[i], m_LocalVolumes[j]))
{
analysisData.hasOverlappingVolumes = true;
break;
}
}
if (analysisData.hasOverlappingVolumes) break;
}
}

private bool DoVolumesOverlap(Volume volume1, Volume volume2)
{
if (volume1 == null || volume2 == null) return false;

var collider1 = volume1.GetComponent<Collider>();
var collider2 = volume2.GetComponent<Collider>();

if (collider1 != null && collider2 != null)
{
// 简化的重叠检测
var bounds1 = collider1.bounds;
var bounds2 = collider2.bounds;

return bounds1.Intersects(bounds2);
}

return false;
}

private void AnalyzePerformanceData()
{
performanceData.volumeQueriesPerSecond = Mathf.RoundToInt(analysisData.totalVolumes / volumeUpdateInterval);
performanceData.activeVolumeComponents = analysisData.activeComponents;
performanceData.memoryUsageKB = analysisData.totalVolumes * 4; // 简化估算
performanceData.isOptimal = performanceData.volumeQueriesPerSecond < 100; // 假设阈值
}

private void LogVolumePerformance()
{
var log = new System.Text.StringBuilder();
log.AppendLine("=== Volume System Performance ===");
log.AppendLine($"Total Volumes: {analysisData.totalVolumes}");
log.AppendLine($"Active Components: {analysisData.activeComponents}");
log.AppendLine($"Average Intensity: {analysisData.averageIntensity:F3}");
log.AppendLine($"Has Overlapping: {analysisData.hasOverlappingVolumes}");
log.AppendLine($"Volume Queries/s: {performanceData.volumeQueriesPerSecond}");
log.AppendLine($"Memory Usage: {performanceData.memoryUsageKB:F1} KB");
log.AppendLine($"Is Optimal: {performanceData.isOptimal}");

Debug.Log(log.ToString());
}

// 获取当前相机的Volume Stack
public VolumeStack GetVolumeStackForCamera(Camera camera)
{
if (m_CameraVolumeStacks.ContainsKey(camera))
{
return m_CameraVolumeStacks[camera];
}

return m_GlobalVolumeStack;
}

// 获取Volume分析数据
public VolumeAnalysisData GetVolumeAnalysisData()
{
return analysisData;
}

// 获取Volume性能数据
public VolumePerformanceData GetVolumePerformanceData()
{
return performanceData;
}

// 手动添加Volume到系统
public void AddVolume(Volume volume)
{
if (volume != null && !m_LocalVolumes.Contains(volume))
{
m_LocalVolumes.Add(volume);
AnalyzeVolumeData();
}
}

// 手动移除Volume
public void RemoveVolume(Volume volume)
{
if (volume != null)
{
m_LocalVolumes.Remove(volume);
AnalyzeVolumeData();
}
}

// 更新Volume参数
public void UpdateVolumeParameters(Volume volume, float intensity, float threshold, Color color)
{
if (volume != null && volume.profile != null)
{
var customEffect = volume.profile.GetComponent<CustomEffect>();
if (customEffect != null)
{
customEffect.intensity.value = intensity;
customEffect.threshold.value = threshold;
customEffect.color.value = color;
}
}
}

// 获取所有Volume列表
public List<Volume> GetAllVolumes()
{
return new List<Volume>(m_LocalVolumes);
}

// 获取激活的Volume组件数量
public int GetActiveVolumeComponentsCount()
{
return analysisData.activeComponents;
}

// 获取Volume统计信息
public string GetVolumeStats()
{
var stats = new System.Text.StringBuilder();
stats.AppendLine("=== Volume Statistics ===");
stats.AppendLine($"Total Volumes: {analysisData.totalVolumes}");
stats.AppendLine($"Global Volumes: {analysisData.globalVolumes}");
stats.AppendLine($"Local Volumes: {analysisData.localVolumes}");
stats.AppendLine($"Active Components: {analysisData.activeComponents}");
stats.AppendLine($"Average Intensity: {analysisData.averageIntensity:F3}");
stats.AppendLine($"Has Overlaps: {analysisData.hasOverlappingVolumes}");
stats.AppendLine($"Last Update: {analysisData.lastUpdate}");

return stats.ToString();
}

void OnDestroy()
{
// 清理Volume Stack
if (m_GlobalVolumeStack != null)
{
VolumeManager.instance.DestroyStack(m_GlobalVolumeStack);
}

foreach (var kvp in m_CameraVolumeStacks)
{
if (kvp.Value != null)
{
VolumeManager.instance.DestroyStack(kvp.Value);
}
}

m_CameraVolumeStacks.Clear();
}
}

17.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
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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class PostProcessResourceManager : MonoBehaviour
{
[Header("Resource Management Settings")]
public bool enableResourceManagement = true;
public bool enableTexturePooling = true;
public bool enableAsyncRelease = true;
public bool logResourceData = true;

[Header("Pool Configuration")]
public int initialPoolSize = 4;
public int maxPoolSize = 16;
public float textureLifetime = 5.0f;
public float cleanupInterval = 1.0f;

[Header("Memory Settings")]
public float memoryBudgetMB = 256.0f;
public float memoryWarningThreshold = 0.8f;
public bool enableMemoryOptimization = true;

private Dictionary<string, Queue<RTHandle>> m_TexturePool = new Dictionary<string, Queue<RTHandle>>();
private Dictionary<RTHandle, float> m_TextureUsageTime = new Dictionary<RTHandle, float>();
private List<RTHandle> m_ActiveTextures = new List<RTHandle>();
private float m_LastCleanup = 0f;
private float m_CurrentMemoryUsage = 0f;

[System.Serializable]
public class ResourceAnalysisData
{
public int pooledTextures;
public int activeTextures;
public int totalTextures;
public float memoryUsageMB;
public float memoryBudgetMB;
public float memoryUsagePercentage;
public int texturesInUse;
public int texturesInPool;
public float averageTextureLifetime;
public bool memoryBudgetExceeded;
public System.DateTime lastUpdate;
}

[System.Serializable]
public class ResourcePerformanceData
{
public int allocationCount;
public int deallocationCount;
public int poolHitCount;
public int poolMissCount;
public float poolHitRate;
public float allocationTimeMS;
public float deallocationTimeMS;
public bool isOptimal;
}

public ResourceAnalysisData analysisData = new ResourceAnalysisData();
public ResourcePerformanceData performanceData = new ResourcePerformanceData();

void Start()
{
if (enableResourceManagement)
{
InitializeResourceManagement();
}
}

void Update()
{
if (enableResourceManagement && Time.time - m_LastCleanup >= cleanupInterval)
{
CleanupUnusedResources();
m_LastCleanup = Time.time;
}
}

private void InitializeResourceManagement()
{
// 初始化纹理池
InitializeTexturePools();

if (logResourceData)
{
Debug.Log("[PostProcessResourceManager] Initialized resource management system");
}
}

private void InitializeTexturePools()
{
// 预创建一些常用尺寸的纹理
var commonSizes = new[]
{
new Vector2Int(256, 256),
new Vector2Int(512, 512),
new Vector2Int(1024, 1024),
new Vector2Int(2048, 2048)
};

foreach (var size in commonSizes)
{
string poolKey = GetTexturePoolKey(size.x, size.y, RenderTextureFormat.ARGB32);
if (!m_TexturePool.ContainsKey(poolKey))
{
m_TexturePool[poolKey] = new Queue<RTHandle>();

// 预分配一些纹理到池中
for (int i = 0; i < initialPoolSize; i++)
{
var texture = CreateTexture(size.x, size.y, RenderTextureFormat.ARGB32);
if (texture != null)
{
m_TexturePool[poolKey].Enqueue(texture);
m_CurrentMemoryUsage += CalculateTextureMemory(size.x, size.y, RenderTextureFormat.ARGB32);
}
}
}
}

UpdateAnalysisData();
}

private string GetTexturePoolKey(int width, int height, RenderTextureFormat format)
{
return $"{width}x{height}_{format}";
}

private RTHandle CreateTexture(int width, int height, RenderTextureFormat format)
{
try
{
var descriptor = new RenderTextureDescriptor
{
width = width,
height = height,
colorFormat = format,
depthBufferBits = 0,
msaaSamples = 1,
volumeDepth = 1,
mipCount = 1,
enableRandomWrite = false,
useMipMap = false,
autoGenerateMips = false,
sRGB = false
};

return RTHandles.Alloc(descriptor);
}
catch (System.Exception ex)
{
if (logResourceData)
{
Debug.LogError($"[PostProcessResourceManager] Failed to create texture: {ex.Message}");
}
return null;
}
}

private float CalculateTextureMemory(int width, int height, RenderTextureFormat format)
{
int bytesPerPixel = GetFormatBytes(format);
int totalBytes = width * height * bytesPerPixel;
return totalBytes / (1024.0f * 1024.0f); // 转换为MB
}

private int GetFormatBytes(RenderTextureFormat format)
{
switch (format)
{
case RenderTextureFormat.ARGB32:
case RenderTextureFormat.RGB111110Float:
return 4;
case RenderTextureFormat.ARGBHalf:
case RenderTextureFormat.Depth:
return 2;
case RenderTextureFormat.ARGBFloat:
return 16;
default:
return 4; // 默认值
}
}

// 从池中获取纹理
public RTHandle GetTexture(int width, int height, RenderTextureFormat format = RenderTextureFormat.ARGB32)
{
string poolKey = GetTexturePoolKey(width, height, format);
RTHandle texture = null;

if (enableTexturePooling && m_TexturePool.ContainsKey(poolKey) && m_TexturePool[poolKey].Count > 0)
{
// 从池中取出纹理
texture = m_TexturePool[poolKey].Dequeue();
performanceData.poolHitCount++;
}
else
{
// 创建新纹理
texture = CreateTexture(width, height, format);
performanceData.poolMissCount++;

if (texture != null)
{
m_CurrentMemoryUsage += CalculateTextureMemory(width, height, format);
}
}

performanceData.allocationCount++;

if (texture != null)
{
m_ActiveTextures.Add(texture);
m_TextureUsageTime[texture] = Time.time;
}

if (logResourceData && texture == null)
{
Debug.LogWarning($"[PostProcessResourceManager] Failed to get texture: {width}x{height}, Format: {format}");
}

UpdateAnalysisData();
return texture;
}

// 释放纹理回池
public void ReleaseTexture(RTHandle texture)
{
if (texture == null) return;

// 从活动列表中移除
m_ActiveTextures.Remove(texture);
m_TextureUsageTime.Remove(texture);

// 计算释放的内存
var desc = texture.rt.descriptor;
float releasedMemory = CalculateTextureMemory(desc.width, desc.height, desc.colorFormat);
m_CurrentMemoryUsage -= releasedMemory;

if (enableTexturePooling)
{
// 尝试找到合适的池
string poolKey = GetTexturePoolKey(desc.width, desc.height, desc.colorFormat);

if (!m_TexturePool.ContainsKey(poolKey))
{
m_TexturePool[poolKey] = new Queue<RTHandle>();
}

// 检查池大小限制
if (m_TexturePool[poolKey].Count < maxPoolSize)
{
m_TexturePool[poolKey].Enqueue(texture);
}
else
{
// 池已满,直接释放纹理
if (enableAsyncRelease)
{
StartCoroutine(AsyncReleaseTexture(texture));
}
else
{
texture?.Release();
}
}
}
else
{
// 不使用池,直接释放
if (enableAsyncRelease)
{
StartCoroutine(AsyncReleaseTexture(texture));
}
else
{
texture?.Release();
}
}

performanceData.deallocationCount++;
UpdateAnalysisData();
}

private System.Collections.IEnumerator AsyncReleaseTexture(RTHandle texture)
{
yield return new WaitForEndOfFrame();
texture?.Release();
}

// 清理未使用的资源
private void CleanupUnusedResources()
{
var texturesToRelease = new List<RTHandle>();

// 查找超时的纹理
foreach (var kvp in m_TextureUsageTime)
{
if (Time.time - kvp.Value > textureLifetime)
{
texturesToRelease.Add(kvp.Key);
}
}

// 释放超时的纹理
foreach (var texture in texturesToRelease)
{
if (m_ActiveTextures.Contains(texture))
{
ReleaseTexture(texture);
}
}

// 如果内存使用超过预算,执行内存优化
if (m_CurrentMemoryUsage > memoryBudgetMB * memoryWarningThreshold && enableMemoryOptimization)
{
OptimizeMemoryUsage();
}

UpdateAnalysisData();
}

private void OptimizeMemoryUsage()
{
// 清空纹理池以释放内存
foreach (var pool in m_TexturePool.Values)
{
while (pool.Count > 0)
{
var texture = pool.Dequeue();
texture?.Release();
m_CurrentMemoryUsage -= CalculateTextureMemory(
texture.rt.descriptor.width,
texture.rt.descriptor.height,
texture.rt.descriptor.colorFormat);
}
}

if (logResourceData)
{
Debug.LogWarning($"[PostProcessResourceManager] Memory optimization performed. Current usage: {m_CurrentMemoryUsage:F2}MB");
}
}

private void UpdateAnalysisData()
{
analysisData.pooledTextures = 0;
analysisData.activeTextures = m_ActiveTextures.Count;

foreach (var pool in m_TexturePool.Values)
{
analysisData.pooledTextures += pool.Count;
}

analysisData.totalTextures = analysisData.pooledTextures + analysisData.activeTextures;
analysisData.memoryUsageMB = m_CurrentMemoryUsage;
analysisData.memoryBudgetMB = memoryBudgetMB;
analysisData.memoryUsagePercentage = (m_CurrentMemoryUsage / memoryBudgetMB) * 100f;
analysisData.texturesInUse = analysisData.activeTextures;
analysisData.texturesInPool = analysisData.pooledTextures;
analysisData.memoryBudgetExceeded = m_CurrentMemoryUsage > memoryBudgetMB;
analysisData.lastUpdate = System.DateTime.Now;

// 计算平均纹理生命周期
float totalLifetime = 0f;
int count = 0;
foreach (var kvp in m_TextureUsageTime)
{
totalLifetime += Time.time - kvp.Value;
count++;
}
analysisData.averageTextureLifetime = count > 0 ? totalLifetime / count : 0f;

// 更新性能数据
performanceData.poolHitRate = performanceData.allocationCount > 0 ?
(float)performanceData.poolHitCount / performanceData.allocationCount : 0f;
performanceData.isOptimal = performanceData.poolHitRate > 0.7f; // 假设70%命中率是理想的
}

// 获取资源分析数据
public ResourceAnalysisData GetResourceAnalysisData()
{
return analysisData;
}

// 获取资源性能数据
public ResourcePerformanceData GetResourcePerformanceData()
{
return performanceData;
}

// 获取纹理池统计
public string GetTexturePoolStats()
{
var stats = new System.Text.StringBuilder();
stats.AppendLine("=== Texture Pool Statistics ===");

foreach (var kvp in m_TexturePool)
{
stats.AppendLine($"{kvp.Key}: {kvp.Value.Count} textures in pool");
}

stats.AppendLine($"Active Textures: {analysisData.activeTextures}");
stats.AppendLine($"Pooled Textures: {analysisData.pooledTextures}");
stats.AppendLine($"Total Memory: {analysisData.memoryUsageMB:F2} MB");
stats.AppendLine($"Pool Hit Rate: {performanceData.poolHitRate:F2}");

return stats.ToString();
}

// 获取资源统计信息
public string GetResourceStats()
{
var stats = new System.Text.StringBuilder();
stats.AppendLine("=== Resource Statistics ===");
stats.AppendLine($"Total Textures: {analysisData.totalTextures}");
stats.AppendLine($"Active Textures: {analysisData.activeTextures}");
stats.AppendLine($"Pooled Textures: {analysisData.pooledTextures}");
stats.AppendLine($"Memory Usage: {analysisData.memoryUsageMB:F2} MB / {analysisData.memoryBudgetMB:F2} MB");
stats.AppendLine($"Memory Usage: {analysisData.memoryUsagePercentage:F1}%");
stats.AppendLine($"Pool Hit Rate: {performanceData.poolHitRate:F2}");
stats.AppendLine($"Allocation Count: {performanceData.allocationCount}");
stats.AppendLine($"Deallocation Count: {performanceData.deallocationCount}");
stats.AppendLine($"Is Memory Budget Exceeded: {analysisData.memoryBudgetExceeded}");
stats.AppendLine($"Is Optimal: {performanceData.isOptimal}");

return stats.ToString();
}

// 强制清理所有资源
public void ForceCleanupAllResources()
{
// 释放所有活动纹理
foreach (var texture in m_ActiveTextures)
{
texture?.Release();
}
m_ActiveTextures.Clear();
m_TextureUsageTime.Clear();

// 清空纹理池
foreach (var pool in m_TexturePool.Values)
{
while (pool.Count > 0)
{
var texture = pool.Dequeue();
texture?.Release();
}
}

m_CurrentMemoryUsage = 0f;

if (logResourceData)
{
Debug.Log("[PostProcessResourceManager] All resources cleaned up");
}
}

// 获取当前内存使用情况
public float GetCurrentMemoryUsage()
{
return m_CurrentMemoryUsage;
}

// 获取内存预算
public float GetMemoryBudget()
{
return memoryBudgetMB;
}

// 检查内存是否超预算
public bool IsMemoryBudgetExceeded()
{
return analysisData.memoryBudgetExceeded;
}

void OnDestroy()
{
ForceCleanupAllResources();
}
}

实践练习

17.10 练习1:自定义后处理Pass

目标:创建一个完整的自定义后处理Pass

步骤

  1. 创建CustomPostProcessPass类
  2. 实现后处理效果逻辑
  3. 集成到Renderer Feature
  4. 测试效果参数调整
  5. 优化性能表现

实现要点

  • 正确的渲染流程
  • 参数传递机制
  • 性能优化策略

17.11 练习2:Volume系统扩展

目标:扩展Volume系统功能

步骤

  1. 创建自定义Volume组件
  2. 实现参数混合逻辑
  3. 集成到Volume框架
  4. 测试多Volume混合
  5. 验证性能表现

扩展功能

  • 自定义效果参数
  • 动态参数调整
  • 性能监控

17.12 练习3:纹理资源池

目标:实现高效的纹理资源池

步骤

  1. 设计纹理池架构
  2. 实现获取和释放逻辑
  3. 添加内存管理功能
  4. 测试性能提升
  5. 验证内存使用

池化策略

  • 多尺寸支持
  • 生命周期管理
  • 内存优化

17.13 练习4:后处理合批优化

目标:实现后处理效果合批

步骤

  1. 分析效果兼容性
  2. 实现合批逻辑
  3. 生成复合Shader
  4. 测试性能提升
  5. 验证视觉效果

合批策略

  • 效果分析
  • 参数合并
  • 性能评估

17.14 练习5:综合后处理系统

目标:创建完整的后处理系统

步骤

  1. 集成所有后处理组件
  2. 实现统一管理接口
  3. 添加性能监控
  4. 测试各种效果
  5. 优化整体性能

系统特性

  • 模块化设计
  • 性能优化
  • 易用性

总结

第17章深入分析了URP中后处理系统的源码实现,包括PostProcessPass的执行流程、Volume Framework的参数管理系统、各后处理效果的Shader实现、Uber Post Processing的合批机制以及RenderTexture的管理与优化。

关键要点总结:

  1. PostProcessPass:后处理效果的核心执行单元
  2. Volume系统:参数管理和效果混合框架
  3. Shader实现:屏幕空间效果的具体实现
  4. 合批优化:通过合并Pass减少渲染开销
  5. 资源管理:纹理池和内存优化策略

后处理系统是URP中实现高级视觉效果的重要组成部分,理解其内部实现有助于更好地利用和扩展后处理功能。有效的资源管理和性能优化是实现高质量后处理效果的关键。

下一章将详细解析URP的Shader Library,深入了解底层着色器函数的实现原理。