「Custom SRP」:相机系统与工程架构
系列收束篇。前七篇笔记从管线骨架一路走到后处理输出——这些都是单相机内部的故事。本篇拉远视角,关注更宏观的工程层:多相机如何编排、相机级配置如何注入、Shader 关键字如何收敛、调试工具如何串联。这些不是某个具体的渲染技术,但它们决定一个 Custom SRP 项目能否长期维护、能否多人协作、能否在生产环境中稳定运行。
TL;DR
- 多相机栈:Base Camera 渲染主场景到中间 RT、Overlay Camera 在同一 RT 上叠加(UI、武器、第一人称视角)。Render Graph 的资源声明让跨相机 RT 复用变得自然。
- Per-Camera 配置注入:通过
CustomRenderPipelineCamera组件在每个 Camera GameObject 上挂载独立设置——RenderingLayerMask、Final Blend、PostFX 开关、Render Scale 都能 per-camera 覆盖。 - Shader 关键字收敛是工程命脉:原始 144 排列经过 3.2.0 简化合并后压到 18 排列、Shader 编译时间下降 87.5%。
shader_feature与multi_compile的精确划分决定 Player 包体大小。 - RP Settings 架构演进:从 Asset 内联字段 → 独立 ScriptableObject 集合,分离关注点让大型项目的 RP 配置可以多人协作、按系统独立 review。
- 三套调试工具串联:Frame Debugger 看命令流、Render Graph Viewer 看资源依赖、RenderDoc 看硬件层细节。每套工具都对应一类问题域,知道何时用哪个是高效调试的前提。
1. 多相机渲染
1.1 渲染栈的设计动机
游戏中常见的相机叠加场景:
- 第一人称游戏:主相机渲染场景、武器相机渲染近距离武器(避免武器穿墙)
- UI 系统:主相机 + UI 相机分离 PostFX——UI 不希望被 Bloom 弄糊
- 小地图 / 后视镜:从其他角度渲染场景到 RT、最终覆盖到主屏角落
- 过场动画:剧情时切换到电影相机、保持游戏 UI 渲染
这些场景都需要 多个 Camera 协同工作。Custom SRP 通过两种相机角色实现:
| 角色 | 行为 | 用途 |
|---|---|---|
| Base Camera | 渲染完整场景(含天空盒、PostFX) | 主视角、主场景 |
| Overlay Camera | 在已有 RT 上叠加渲染 | UI、武器、特效层 |
1.2 相机渲染顺序
RenderPipeline.Render(context, cameras) 接收的相机列表已经被引擎按 Camera.depth 排序——depth 小的先渲染,大的后渲染。这意味着:
1 | |
相机间的 RT 复用通过 CameraSettings.copyColor 或 CameraSettings.copyDepth 显式声明——下一个相机是否需要前一个相机的 color/depth 作为输入。
1.3 Camera Settings 字段总览
CameraSettings 是 per-camera 的配置容器:
1 | |
每个字段都对应一类 per-camera 决策:
- copyColor / copyDepth:是否生成可被后续相机或 Soft Particles 采样的副本
- renderingLayerMask:相机只渲染哪些 Rendering Layer(Note 4 §5)
- maskLights:是否对光源也应用 layer mask(用于 per-camera 独立打光)
- renderScale / renderScaleMode:相机分辨率倍率,三种模式让相机可以继承、相乘或独占覆盖全局设置
- overridePostFX:是否使用相机专属的 PostFX 配置(小地图相机不需要 Bloom)
- finalBlendMode:Overlay 相机叠加到中间 RT 时的混合方程
1.4 CustomRenderPipelineCamera 组件
让 CameraSettings 落到具体相机上的方式是挂载 CustomRenderPipelineCamera 组件:
1 | |
CameraRenderer 读取这个组件:
1 | |
没挂组件的相机使用 RP Asset 中的全局默认设置——这种 fallback 设计让 Scene View / Reflection Probe 等隐式相机也能工作。
1.5 Final Blend Mode 与叠加渲染
Overlay 相机的核心机制:最终输出阶段使用自定义 Blend 模式,让本相机渲染结果与 RT 中已有内容混合。
1 | |
不同 BlendMode 组合的常见用途:
| Source | Destination | 效果 | 用途 |
|---|---|---|---|
| One | Zero | 完全覆盖 | Base 相机、不透明覆盖 |
| SrcAlpha | OneMinusSrcAlpha | 标准 alpha 混合 | 半透明 UI、武器 overlay |
| One | One | 加性混合 | 发光特效叠加 |
| One | OneMinusSrcAlpha | Premultiplied Alpha 叠加 | 已 PMA 处理的 UI 层 |
PostFXPass 与 FinalPass 的最终 Blit 都使用这个 BlendMode——这是 Overlay 相机能与已有内容融合的根本机制。
⚠️ Overlay Camera 的 Depth Clear 陷阱:Final Blend Mode 处理的是颜色叠加,但深度缓冲(Depth Buffer)也需要单独处理——否则会出现典型的”3D 模型穿透 UI”现象。当纯 UI 相机以 Overlay 模式叠加到 Base 相机的 RT 上时,如果继续沿用 Base 相机写入的 Depth Attachment,UI 元素的 ZTest 会与 3D 场景的深度发生交叉判定:UI 元素的 NDC.z(通常很小,靠近近平面)会被场景中靠近相机的 3D 模型的深度反向遮挡,导致原本应该浮在最上层的血条、十字准星、对话框被场景对象”穿透”挡住。修复方式有两种:(1) Overlay 相机录制时显式触发一次
ClearDepth(CommandBuffer.ClearRenderTarget(true, false, …));(2) 在 Render Graph 中不绑定 Base 相机的 Depth Attachment,让 Overlay Pass 使用自己的全新深度缓冲(或干脆 ZTest Always 完全不依赖深度)。Custom SRP 通过CameraSettings区分 Base / Overlay 角色——Overlay 相机的 SetupPass 应该清除继承的深度。这是真实项目中”UI 突然被场景挡住、查半天找不到原因”的标准答案。
1.6 Scene View 与 Reflection Probe 相机
Custom SRP 在编辑器中还需要处理几种隐式相机:
- Scene View:编辑器场景视图——开发者每秒看到的画面
- Reflection Probe:实时反射探针——每帧从 6 个面渲染场景到 cubemap
- Material Preview:材质球预览
- Manual Render:用户调用
Camera.Render()主动触发
这些相机通常没有 CustomRenderPipelineCamera 组件——使用全局默认设置。Scene View 的特殊处理:
1 | |
EmitWorldGeometryForSceneView 让 UI 元素(Canvas)在 Scene View 中也能可见——否则 UI 只在 Game View 中渲染。Reflection Probe 的处理则需要避开 PostFX、避开 Render Scale——这些会让反射结果失真。
2. Shader 关键字管理
2.1 关键字爆炸的成本
每个 multi_compile 行会让 Shader 编译出 2 个或更多的 variant。Custom SRP 的 Lit Shader 完整 keyword 列表:
1 | |
排列组合数:2 × 2 × 2 × 3 × 2 × 3 × 2 × 3 × 2⁶ = ~13800 variant——单一 Shader 在最坏情况下可能编译万级 variant。
每个 variant 都意味着:
- 编译时间:单 variant 编译约 100-500 ms。13800 variant × 200 ms ≈ 46 分钟。修改 Shader 后等编译完是开发体验的灾难
- Player 包体:每 variant 在 build 中都占用空间,移动端项目尤其敏感
- 运行时加载:首次切换到新 variant 时引擎需要 JIT 编译或从磁盘加载——产生明显的卡顿尖峰
关键字数量与项目质量正相关、与构建效率负相关——找到收敛点是 TA 的核心工作。
2.2 multi_compile vs shader_feature
两种 pragma 的根本区别:
| Pragma | 编译时机 | 运行时启用方式 | 包体行为 |
|---|---|---|---|
| multi_compile | 编译期生成所有 variant | Shader.EnableKeyword 全局 / material.EnableKeyword per-material |
所有 variant 都打入 Player 包 |
| shader_feature | 编译期生成所有 variant | 仅 material.EnableKeyword per-material |
Player 包仅保留实际被材质使用的 variant |
关键洞察:shader_feature 在开发期与 multi_compile 行为一致(编辑器中所有 variant 都可用),但 build 时引擎会扫描所有材质实际启用的 keyword 组合,只把这些组合打包——unused variant 被自动剔除。
实践规则:
- Code-driven 关键字(由 C# 代码全局开关,如阴影质量、FXAA 等级、Lightmap)→
multi_compile - Material-driven 关键字(由材质 inspector 勾选,如透明度模式、Normal Map 启用)→
shader_feature
误用 multi_compile 替代 shader_feature 会让 Player 包体翻倍(Material 实际只用其中几个 variant,但所有 variant 都被打包)。
2.3 收敛策略一:质量等级合并
最有效的关键字收敛是合并多档质量为统一全局质量等级。Note 5 §3.2 已经详细介绍:
合并前(4 个独立 PCF 模式):
1 | |
→ 4 × 4 = 16 排列
合并后(统一 Filter Quality 三档):
1 | |
→ 3 排列(5.3× 减少)
这种合并的代价:方向光与其他光不能再独立配置质量。但实际项目中两者通常协调一致——独立配置的灵活性收益小于关键字爆炸的工程成本。
2.4 收敛策略二:枚举值代替关键字
某些”配置型”参数可以从关键字改为运行时 uniform 变量。比如 Cascade Blend 从两个关键字(Soft / Dither)改为一个 _CascadeBlendMode uniform:
1 | |
代价:一次 if-else 分支判断(GPU 上稍微低效)。收益:消除一组关键字。当分支两侧的代码量都很小,且分支判断频率低时,运行时分支比 variant 更划算。
2.5 收敛策略三:Stripping 工具链
即使经过收敛,大型项目仍可能有几千 variant。Unity 提供 IPreprocessShaders 接口让构建期主动剔除未使用的 variant:
1 | |
典型 stripping 规则:
- 移动端 build 不需要的高质量 keyword(_SHADOW_FILTER_HIGH)
- 桌面端 build 不需要的低端 keyword(FXAA_QUALITY_LOW)
- 项目从未启用的特性(DirectionalLightmap)
精细的 stripping 能让 build 时 variant 数从 5000+ 降到 500-。这是大型项目 production-ready 的必经之路。
💡 配置感知 Stripping(Configuration-Aware Stripping):上述基础 stripping 还是按 Build Target 平台粗筛——但工业界更高级的做法是 直接读取项目的 RP Settings 来驱动剔除。在
OnProcessShader回调中通过GraphicsSettings.currentRenderPipeline拿到当前的CustomRenderPipelineAsset,进而读取ShadowSettings.filterQuality、PostFXSettings.fxaaQuality等具体配置——如果项目实际只配置了最大 Medium 等级的阴影,那_SHADOW_FILTER_HIGH关键字对应的所有 variant 都可以在 build 期被自动剔除。
1
2
3
4
5
6
7
8
9
10
11
12
13public void OnProcessShader(Shader shader, ShaderSnippetData snippet,
IList<ShaderCompilerData> data)
{
var rpAsset = GraphicsSettings.currentRenderPipeline as CustomRenderPipelineAsset;
var maxShadowQuality = rpAsset.Settings.shadows.maxFilterQuality;
for (int i = data.Count - 1; i >= 0; i--)
{
var keywords = data[i].shaderKeywordSet;
if (keywords.IsEnabled(highShadowKeyword) && maxShadowQuality < High)
data.RemoveAt(i);
}
}这种”修改 Settings → 自动剔除对应 variant“的闭环让美术 / TA 调整 RP 配置时,build 体积与编译时间自动跟随收缩——不需要手动维护 stripping 规则与配置之间的一致性。这是顶级工程架构的标志:配置即真相、构建感知配置。HDRP 与 URP 都内建了类似机制(
ShaderStripping接口家族),Custom SRP 项目应当在中后期建立类似的自动化层。
2.6 Shader Variant Collection
为了避免运行时首次切换 variant 的卡顿,可以预先 warm up:
1 | |
WarmUp() 强制编译并加载列表中所有 variant,把卡顿前置到 Loading Screen 阶段。Custom SRP 项目通常在游戏启动时 warm up 所有可能用到的 Lit Shader variant——首次场景加载多 1-2 秒,但游戏运行时再无切换卡顿。
3. RP Settings 架构演进
3.1 内联字段的局限
最初的 RP Asset 把所有配置内联在一个文件中:
1 | |
随着特性增加,字段数量爆炸。问题:
- Inspector 滚动疲劳:开发者在 30+ 字段中找特定设置变得困难
- Git diff 噪音:任何字段修改都会触发 Asset 文件 diff,多人协作时 conflict 频繁
- 职责混淆:阴影设置与后处理设置在同一文件中,难以独立 review
3.2 拆分为独立 ScriptableObject
3.2.0 简化版本把所有设置拆为独立 ScriptableObject 集合:
1 | |
或更进一步——每个子系统一个独立 Asset:
1 | |
这种粒度的好处:
- 独立调整:阴影美术调 Shadow 不会触碰 PostFX
- 配置变体:低端机型用 LowEnd 后处理 Asset、旗舰机型用 Default
- Git 友好:每个文件只在对应职责修改时 diff
- 运行时切换:高端 / 低端配置可以在 quality preset 中动态切换
3.3 Quality Settings 集成
Unity 的 Quality Settings 系统可以为不同质量等级(Low / Medium / High / Ultra)分别绑定不同的 RP Asset:
1 | |
玩家在游戏内切换质量等级时,引擎自动切换到对应的 RP Asset——所有渲染配置(阴影分辨率、PostFX 强度、Forward+ Tile 大小、LUT 精度)一键全切。这是大型项目支持广覆盖硬件的标准做法。
4. 调试工具链
Custom SRP 的调试需要在三套工具间灵活切换。每套工具对应一类问题域。
4.1 Frame Debugger:命令流诊断
Window / Analysis / Frame Debugger——展示帧内所有 GPU 命令的提交序列。
适用问题:
- 某个 Pass 是否实际被执行
- 某次 Draw Call 使用的 Material / Shader Variant
- SRP Batcher 是否生效(”SRP Batch” 节点存在)
- ClearRenderTarget 是否在预期时机
- 中间 RT 内容查看(每个节点可单步调试)
局限:
- 不显示 Render Graph 的资源依赖
- 不显示原生 GPU 层级行为(tile memory、warp occupancy 等)
- 大量节点(数百 Draw Call)时翻找困难
实践:当某个对象画错了,第一反应是 Frame Debugger 找到对应 Draw Call → 检查 Shader Variant → 检查输入纹理。
4.2 Render Graph Viewer:资源依赖诊断
Window / Analysis / Render Graph Viewer——展示 Pass 之间的资源读写依赖图。
适用问题:
- 某个 Pass 是否被引擎裁剪
- 哪些 Raster Pass 被合并为单个 native render pass(蓝色横条标记)
- 资源生命周期与 RT 复用情况
- 资源依赖是否符合预期(Soft Particles 是否正确读到 Depth Copy)
局限:
- 需要先开启相机的 debug data 收集(性能开销)
- 编辑器外不可用
实践:当怀疑 Pass 合并优化没生效,或 RT 分配过多时,第一时间打开 Render Graph Viewer 看资源图。Note 1 §7 描述的 TBR 合并优化是否生效完全靠这个工具验证。
4.3 RenderDoc:硬件层诊断
外部工具——抓取整个帧的原生 API 调用序列,包含 D3D12 / Vulkan 层级的所有细节。
适用问题:
- Compute Shader 的 thread group 实际执行行为
- Tile memory 占用与 Bandwidth 消耗
- GPU 端 barrier / sync 时机
- Driver 层面是否产生意外的 PSO 切换
局限:
- 学习曲线陡峭,需要熟悉图形 API 细节
- 抓帧可能改变性能行为(典型放慢 5-10×)
- 不直接对应 Unity 的高层概念
实践:当 Frame Debugger 与 Render Graph Viewer 都看不出问题、但性能就是不对时,RenderDoc 是最后的 fallback。也是 Note 4 §4.8 描述的 Compute Shader 移动端兼容性问题的主要诊断工具。
4.4 三工具协作模式
flowchart TD
A[发现性能或视觉问题] --> B{问题类型?}
B -->|画面错了| C[Frame Debugger
找对应 Draw Call]
B -->|Pass 被意外裁剪| D[Render Graph Viewer
检查资源依赖]
B -->|性能掉帧但找不到原因| E[RenderDoc
抓帧硬件分析]
C --> F[检查 Shader Variant
检查输入纹理]
D --> G[检查资源声明
检查 Pass 顺序]
E --> H[检查 GPR 占用
检查 Bandwidth]
F --> I{解决?}
G --> I
H --> I
I -->|否| J[换一种工具]
J --> B
style A fill:#fff3e0,stroke:#f57c00
style I fill:#e8f5e9,stroke:#388e3c
工程实践:
- 日常开发:Frame Debugger 是主力工具,应该熟练到反射式使用
- 架构调试:Render Graph Viewer 是 RP 重构期的必备工具
- 性能优化:RenderDoc 是最后阶段的精细化工具
4.5 Profiling Sampler 的命名艺术
每个 Pass 的 ProfilingSampler 名字直接出现在 Frame Debugger 和 Profiler 的 Hierarchy 中——好的命名让调试效率倍增:
1 | |
命名规范建议:
- 以名词短语开头:标识”是什么”而不是”做什么”
- 保持稳定:不在循环中拼接 index(”Cascade 0” / “Cascade 1” 在 Profiler 中会被聚合,反而失去对比能力)
- 跨 Pass 一致:同类工作用同一前缀(”Shadow / …”、”PostFX / …”)
4.6 ProfilingSampler 缓存陷阱
ProfilingSampler 通常缓存为 static readonly 字段:
1 | |
但相机的 sampler 不能 static——它依赖 Camera.name 动态生成:
1 | |
这里的 ??= 缓存有一个 editor-only 陷阱:相机改名后缓存的 Sampler 仍带旧名字,Frame Debugger 显示错误。修复方式是 OnEnable 清缓存:
1 | |
这是 Note 1 §5.3 提到过的细节——在工程层面再次强调,因为这是真实项目中容易被遗漏的开发体验细节。
5. RenderingLayer:跨系统的对象-光源过滤
RenderingLayer 是贯穿多个子系统的 32 位掩码——Note 4 §5 介绍了它在光源系统中的角色。在工程层级它的应用更广:
5.1 应用场景
| 场景 | 实现方式 |
|---|---|
| Per-Camera 光源屏蔽 | cameraSettings.maskLights = true + renderingLayerMask |
| 第一人称武器独立打光 | 武器对象 + 武器灯光独占 layer |
| 动态/静态对象分组 | 静态对象一组、动态对象一组,用于 GI 或阴影策略差异 |
| UI 与场景分离渲染 | UI 对象一组,过滤掉所有场景光 |
5.2 数据流
1 | |
三层独立但协同——对象的 layer 决定它被哪些 light 照亮、相机的 layer 决定它渲染哪些对象、光源的 layer 决定它影响哪些对象。
5.3 Unity 6 的 RenderingLayerMask 类型
Unity 6 引入了官方的 RenderingLayerMask 类型替代裸 uint:
1 | |
这个类型在 Inspector 中渲染为下拉多选 UI(类似 LayerMask),用户可以勾选预先定义的命名 layer。命名通过 Project Settings 配置:
1 | |
实践:早期项目用 LayerMask,重构到 Unity 6 时迁移到 RenderingLayerMask——这是 4.0.0 与之后版本的重要 API 变化。
6. TA Takeaway
6.1 关键字管理是项目长期健康的命脉
关键字数量与项目质量正相关、与构建效率负相关——这是 RP 工程层最核心的 trade-off。具体数字:
1 | |
每升一级,工程纪律的要求显著提高。项目早期就应该制定关键字预算——而不是在 build 时间从 5 分钟涨到 50 分钟时才被迫处理。
6.2 RP Settings 拆分是规模化的前提
单文件 Settings 在小项目能用、中型项目难受、大型项目不可用。拆分为独立 ScriptableObject 集合的工程红利:
- 代码所有权清晰:阴影系统的 owner 维护 ShadowSettings、后处理的 owner 维护 PostFXSettings
- Code Review 粒度合理:每个 PR 只触碰相关 Settings 文件
- 配置变体管理:通过 Asset Variant 而非 #if 条件编译实现多平台分支
- 运行时切换效率:直接交换 Settings 引用而不是重新解析整个 Asset
6.3 调试工具链的内功修炼
Frame Debugger / Render Graph Viewer / RenderDoc 三套工具的熟练度直接决定 TA 的工作效率。新手到老手的成长曲线:
1 | |
每个层级都对应不同的问题域。TA 的核心能力之一是知道何时用哪个工具——这个判断本身就需要长期实践积累。
6.4 多相机栈的常见误用
多相机栈的强大常带来误用诱惑。常见反模式:
- 滥用 Overlay 相机:每个 UI 元素一个独立相机——每相机都是完整 culling + Pass 调度,开销大。正确做法是单 UI 相机 + Canvas 排序
- Camera depth 链式依赖:相机 A 渲染结果被相机 B 读取、B 又被 C 读取——失去并行机会,调试困难
- 混用 Game View 与 Reflection Probe 配置:Reflection Probe 不应该有 PostFX、不应该有 FXAA、不应该有 Render Scale——但默认设置常常忘记关闭
- Scene View 不一致:编辑器 Scene View 用了与 Game View 不同的 PostFX——美术看到的画面与玩家看到的不一样
工程纪律:相机数量应该 ≤ 3(Base + 武器 + UI),超出说明架构有问题。每加一个相机需要专门 review 必要性。
6.5 实践原则
- CustomRenderPipelineCamera 是相机的标配:默认给每个 Camera 挂上,避免 fallback 逻辑分支爆炸
- PostFX 在 Reflection Probe / Material Preview 上必须关闭:否则反射结果与编辑器显示都失真
- 关键字预算项目早期就定:等到 build 慢得受不了再改,重构成本极高
- shader_feature 比 multi_compile 优先:除非确定关键字需要 C# 全局开关
- Settings 拆分阈值是 ~10 字段:超过这个数应该考虑独立 ScriptableObject
- Frame Debugger 应该是肌肉记忆:开发过程中遇到任何渲染问题,第一反应打开它
- ProfilingSampler 名字精心设计:调试时 5 倍效率
- RenderingLayerMask 是 Unity 6 的标准:新项目直接用,老项目重构时迁移
系列收束
至此,Custom SRP 的 8 篇笔记完整覆盖了从管线骨架到工程架构的所有核心系统:
1 | |
每篇都遵循同一节奏:TL;DR 速览 → 系统全景 → 子模块展开 → TA Takeaway → API 速查。每篇内部又通过 ⚠️ 工程陷阱、💡 工程见解、📱 移动端视角三类 blockquote 标识不同维度的工程价值。
这套笔记的目标读者是已经熟悉基础渲染概念、希望深入理解现代 Unity Custom SRP 工业实现的开发者。它不是教程的搬运——Catlike 教程的代码细节本身极其充分,读者应该作为权威参考;这套笔记是对这些代码细节的提炼、归类、连接、上升——把 33 篇分散的渐进式实现重组为 8 个对应渲染引擎核心系统的知识模块,让读者能:
- 快速定位:遇到具体问题时知道翻哪一篇
- 横向连接:理解光照、阴影、GI 之间的数据契约(Surface / Light / BRDF / GI 四个 struct 是核心枢纽)
- 工程认知:超越教程层面,建立 production 级别的硬件意识、关键字预算意识、调试方法论
愿这套笔记能成为读者深入现代渲染管线工程实践的可靠参考。
What’s Next:基于这套底座可以走向哪里
这套笔记完成的不只是 Custom SRP 的知识梳理——它搭建了一个具有高度扩展性的现代管线底座。Render Graph 的资源声明模型、Compute Shader 的 LUT 范式、Forward+ 的屏幕空间分块剔除框架都不是孤立的实现,而是为承载更复杂的渲染技术准备的工程脚手架。基于这套底座,下面几类高级特性可以极其平滑地接入:
GPU 驱动的渲染管线(GPU-Driven Rendering)
当前的几何提交链路(Note 2)仍然是 CPU 端的 Cull → 提交 RendererList → GPU 绘制。GPU-Driven 模式把 Cull 整个搬到 GPU:
- 所有 Mesh 的 InstanceData 打包到 StructuredBuffer(Note 4 的数据传递模式直接复用)
- Compute Shader 在 GPU 端做视锥剔除、遮挡剔除、LOD 选择(与 Forward+ Tile 剔除同构)
DrawProceduralIndirect+DispatchIndirect让 GPU 自己决定画什么、画多少
这是 UE5 Nanite、Frostbite GPU-Driven、Activision 的 Geometry Streaming 的核心范式。Render Graph 的资源声明模型天然支持 indirect buffer 作为 RendererList 输入——本套底座只需扩展 Pass 实现即可接入。
体积散射与体积光(Volumetric Scattering)
体积雾、上帝光、舞台烟雾等效果需要在 3D 空间中沿光线步进求积分。现代实现有两条主流路径:
- 3D Volume Texture + Compute Shader:在屏幕空间分 froxel(frustum voxel)网格,每帧 Compute Shader 填充每个 froxel 的 in-scattering 与 transmittance(Note 5 的 Atlas + Note 7 的 LUT 烘焙思想结合)
- Ray Marching Pass:在主着色阶段对每像素步进采样体积纹理(Note 4 Forward+ Tile 数据可以加速光源剔除)
Custom SRP 已经具备了 3D 纹理(Color LUT)、Compute Shader(LUT 生成)、屏幕空间分块(Forward+)三大基础设施——接入 Volumetric 主要是新增一个 ComputePass + 一个 Sampling Pass,不需要改动核心架构。
屏幕空间反射 SSR
Note 6 §4.3 提到 Box Projection 失效时的兜底方案就是 SSR。SSR 的实现需要:
- 屏幕空间深度纹理(Note 7 已有 CopyAttachmentsPass 的 depth copy)
- HDR 颜色作为反射源(Note 7 的中间 HDR RT)
- 每像素光线步进(典型 16-32 步)+ Hi-Z 加速
所有依赖资源都已在本套管线中存在——SSR 是一个标准的 Raster Pass,输入两张已有 RT,输出反射颜色,最终在 PostFXPass 之前加性混合到主图。
程序化内容生成(PCG)渲染
PCG(Procedural Content Generation)地形、植被、城市的实时渲染需要把”运行时生成的几何数据”无缝接入渲染管线:
- 程序生成的 Mesh 数据存入 ComputeBuffer(与 Forward+ 的 OtherLightData 同构)
- 通过 GPU-Driven 路径自动剔除与提交
- 光照、阴影、GI 接入复用现有所有系统
随着 UE5 PCG、Unity Splines、Houdini Engine 等工具普及,PCG 接入是下一代游戏管线的标配。Custom SRP 的现代框架天然支持——这是把数据(StructuredBuffer)与提交(RendererList / DrawProceduralIndirect)解耦的红利。
Cluster Forward / Visibility Buffer
Forward+ 的 2D Tile 剔除(Note 4 §4)可以扩展为 3D Cluster——不仅按屏幕分块,还按深度分层。这能进一步降低高密度光源场景下的每像素光源测试数量。再进一步是 Visibility Buffer 路径——Pass 1 写入 instance-id + primitive-id(Note 1 的 Render Graph 资源声明无缝支持),Pass 2 在像素 shader 中按 ID 重建几何属性进行光照。这是 UE5 Nanite 的关键技术之一。
收束
每一条扩展路径都不是”从零搭建”——而是在本套底座上叠加新的 Pass。Render Graph 的声明式架构让这些扩展互相隔离、可测试、可回退。这正是现代 SRP 设计的终极工程价值:渲染特性可以独立演进、累加贡献,而不会让管线代码退化为难以维护的状态机。
笔记到这里画上句号——但这套架构能承载的故事才刚刚开始。
关键 API 速查
CSHARP
1 | |
1 | |