# Renderer 下一阶段:Unity 风格 Shader 体系正式化计划 日期:`2026-04-06` ## 1. 阶段结论 当前 renderer 主线已经可以阶段性收口,但 `Shader / Material / Pass` 体系还没有真正统一。 现在仓库里的 shader 体系处于一个过渡态: - 逻辑上已经有 `Shader -> Pass -> Variant` 模型 - authoring 外观上已经接近 Unity ShaderLab - 运行时已经能按 pass 和 backend variant 选择 shader - 三后端已经能稳定跑通 builtin shader 但它仍然不是 Unity 风格的正式体系,因为当前 authoring 仍然暴露了太多 backend 与 binding 细节: - `.shader` 文件里仍然显式写 `#pragma backend D3D12/OpenGL/Vulkan` - `.shader` 文件里仍然显式写 `Resources { name(type, set, binding) }` - material 仍然可以显式指定 `shaderPass` - shader 关键字、变体、include、pass state、SubShader 选择还没有真正成体系 所以这一阶段的主线不是继续做新渲染效果,而是把 shader 体系升级成: - **authoring 层完全按 Unity 风格书写** - **三后端编译路径统一收敛到 importer / compiler 层** - **runtime 只消费正式 shader artifact / material contract** 这一步不是 SRP 本身,但它是后续 SRP 能否成立的硬地基。 --- ## 2. 阶段目标 本阶段要达成的核心目标只有一件事: **把当前“伪 ShaderLab + 后端直连”的过渡体系,升级为“Unity 风格 authoring + 引擎内部统一 shader IR + 三后端编译产物”的正式体系。** 完成后应达到: 1. shader authoring 采用 Unity 风格 `.shader` 语法,不再要求作者显式写 backend variant 分发表。 2. 新语法不再要求作者手写 `set/binding` 级资源绑定表。 3. HLSL 成为 raster shader 的单一 authoring 语言源。 4. D3D12 / Vulkan / OpenGL 的差异退到 importer / compiler / artifact 层。 5. material 只管 property / keyword / texture / render state,不再负责点名 pass。 6. renderer 按 `LightMode` / pass contract 选 pass,而不是靠 material 的临时字符串兜底。 7. builtin shader 全部迁移到新体系并保持三后端回归稳定。 8. 保留 legacy shader 兼容层,避免一次性炸掉现有工程内容。 --- ## 3. 非目标 本阶段明确不做: - SRP 本体 - render graph - deferred / clustered - Shader Graph - Surface Shader - 完整 Unity ShaderLab 100% 全语法一次性覆盖 - volume 系统 - 更多新渲染效果 本阶段也不追求一口气把 Unity 的所有 authoring 特性补完,例如: - `Fallback` - `UsePass` - `CustomEditor` - `GrabPass` - tessellation / geometry / ray tracing authoring 这些可以后续补,但不应阻塞当前阶段把“统一 shader 体系”先做正确。 --- ## 4. 当前真实状态与根本问题 ### 4.1 当前已经具备的能力 当前工程已经具备: - `.shader` 文件解析能力 - `Shader` 的 pass / property / resource / variant 数据模型 - builtin shader 资产化 - material property / texture / render state / tag 载体 - renderer 中按 builtin pass metadata 选择可用 shader pass 这意味着当前不是从零开始设计。 ### 4.2 当前最根本的问题 当前最大的问题不是“没有 shader 体系”,而是“authoring 层和 runtime 层的边界还没收干净”。 具体表现为: 1. **authoring 层暴露后端差异** - `.shader` 文件直接写 `#pragma backend D3D12/OpenGL/Vulkan` - shader 作者必须知道后端与源码文件映射 2. **authoring 层暴露 RHI binding 细节** - `.shader` 文件显式写 `Resources { ConstantBuffer / Texture2D / Sampler, set, binding }` - 这更像 Vulkan/D3D12 binding 清单,不是 Unity 风格 shader authoring 3. **material 还带着临时 pass 选择职责** - `Material::SetShaderPass()` 仍然存在 - renderer 仍然优先吃 material 显式指定的 pass - 这会阻碍未来 RendererFeature / SRP 规范化 4. **关键字与变体体系缺失** - 没有正式的 `multi_compile / shader_feature` - 没有变体剥离与编译缓存策略 5. **include 与共享库体系缺失** - 没有正式的 shader include 搜索路径、预处理、公共库组织 6. **pass state 仍然不在 shader authoring 的统一语义内** - 诸如 `Cull / ZWrite / ZTest / Blend / ColorMask / Stencil` 还没有完整进入 shader authoring contract 7. **三后端仍然是物理三套源码直连** - 当前虽然“逻辑上一个 shader asset” - 但作者本质上还在维护三套 shader stage 文件 --- ## 5. 核心设计结论 ### 5.1 目标不是“看起来像 Unity”,而是“真正采用 Unity 风格 authoring 模型” 最终目标应当是: - 一个 `.shader` 文件描述一个逻辑 shader - shader 内部有 `Properties / SubShader / Pass / Tags / State / Program` - renderer 消费的是 import 后的统一 IR / artifact - backend 差异不暴露给 shader 作者 ### 5.2 HLSL 作为单一 authoring 语言源 本阶段必须明确: - **新体系下 raster shader 统一使用 HLSL authoring** - D3D12 直接编 HLSL - Vulkan 由 HLSL 编到 SPIR-V - OpenGL 由 HLSL 编到 SPIR-V,再转 GLSL 430 原因: - 如果 authoring 仍然保留 GLSL/HLSL 三套并行,永远不可能真正统一写法 - 只有 single-source authoring,才能接近 Unity 的真实体验 ### 5.3 backend 差异必须退到 importer / compiler 层 新 authoring 文件中不应再出现: - `#pragma backend ...` - backend 专属 stage 文件路径表 这些内容应由 importer 根据 target backend 生成产物。 ### 5.4 resource binding 不再由 shader 作者手写 `Resources(set,binding)` Unity 风格 shader authoring 不要求作者手写 descriptor set / binding。 因此新体系下应改为: - material 暴露属性来自 `Properties` - engine 内建 constant buffer / texture / sampler 来自约定与 reflection - importer 通过 HLSL reflection + 约定库推导 runtime resource layout 也就是说: - authoring 层写“语义” - importer 层生成“绑定布局” - runtime 层消费“绑定布局” ### 5.5 pass 选择必须回归 renderer,而不是 material 新体系中: - material 只绑定 shader 与 property / keyword / texture - renderer 按 `LightMode` 选 pass - `Material::shaderPass` 进入弃用与最终移除路径 这与 Unity 的 `ShaderTagId / LightMode` 思路对齐,也更利于未来 SRP。 ### 5.6 必须保留 legacy 兼容层 当前仓库已经有一批 builtin shader 和测试资产。 因此不能激进地“一刀切重做”,而应: - legacy `.shader` 继续可加载 - 新 Unity 风格 `.shader` 进入新 importer 路径 - builtin shader 分批迁移 - runtime 统一落在同一套 `Shader` / artifact / variant 模型上 --- ## 6. 目标架构 建议把 shader 体系正式分成 5 层。 ### 6.1 Authoring 层 职责: - 让开发者以 Unity 风格书写 shader 建议语法子集: - `Shader` - `Properties` - `SubShader` - `Pass` - `Tags` - `LOD` - `HLSLINCLUDE` - `HLSLPROGRAM` - `ENDHLSL` - `#pragma vertex` - `#pragma fragment` - `#pragma target` - `#pragma multi_compile` - `#pragma shader_feature` - `#pragma shader_feature_local` - `Cull` - `ZWrite` - `ZTest` - `Blend` - `ColorMask` - `Stencil` - `Offset` ### 6.2 Importer / Parser 层 职责: - 解析 Unity 风格 `.shader` - 生成统一的内部 `ShaderIR` 建议引入: - `ShaderAuthoringParser` - `ShaderIR` - `ShaderSubShaderIR` - `ShaderPassIR` - `ShaderKeywordDecl` - `ShaderPassStateDesc` - `ShaderProgramIR` ### 6.3 Compiler / Reflection 层 职责: - 编译 authoring 中的 HLSL - 为不同 backend 生成最终编译产物 - 生成 resource layout / constant layout / keyword variant metadata 建议技术路径: - D3D12:`DXC -> DXIL/DXBC` - Vulkan:`DXC -> SPIR-V` - OpenGL:`DXC -> SPIR-V -> SPIRV-Cross -> GLSL 430` 输出: - 每个 pass / stage / keyword-set / backend 的编译产物 - 反射出的 constant buffer / texture / sampler 布局 ### 6.4 Artifact 层 职责: - 保存运行时真正消费的 shader 产物 建议引入新版 artifact: - `xcshader2` 或继续升级现有 `xcshader` artifact 内容至少包含: - shader 名称 / guid - properties - subshader / pass tags - pass state - keyword declarations - keyword variant table - backend binaries / backend source payload - reflected resource layout - include 依赖与 hash ### 6.5 Runtime 层 职责: - renderer 根据 pass contract、keywords、backend 选择最终 variant - material 根据 property/texture 生成常量与资源绑定 - pipeline cache 根据 shader variant + render state 建 key --- ## 7. Unity 风格 authoring 范围定义 ### 7.1 第一阶段必须支持的语法 第一阶段建议正式支持: ```shaderlab Shader "XCEngine/Example/Lit" { Properties { _BaseColor ("Base Color", Color) = (1,1,1,1) _BaseMap ("Base Map", 2D) = "white" {} _Cutoff ("Alpha Cutoff", Range(0,1)) = 0.5 } HLSLINCLUDE #include "ShaderLibrary/Core.hlsl" ENDHLSL SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry" } LOD 200 Pass { Name "ForwardLit" Tags { "LightMode"="ForwardLit" } Cull Back ZWrite On ZTest LEqual Blend One Zero HLSLPROGRAM #pragma target 4.5 #pragma vertex Vert #pragma fragment Frag #pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS #pragma shader_feature_local _ XC_ALPHA_TEST ENDHLSL } } } ``` ### 7.2 第一阶段暂不支持的语法 第一阶段可暂缓: - `Fallback` - `UsePass` - `GrabPass` - `CustomEditor` - `Category` - `Dependency` - Surface Shader - CG fixed-function 时代遗留语义 这些需要列入兼容性说明,但不应阻塞首版落地。 --- ## 8. 统一的 shader library 与 include 体系 这一层是“写法统一”能否成立的关键。 ### 8.1 必须引入正式的 include 库 建议新增: - `engine/assets/shaderlib/ShaderLibrary/Core.hlsl` - `engine/assets/shaderlib/ShaderLibrary/Common.hlsl` - `engine/assets/shaderlib/ShaderLibrary/SpaceTransforms.hlsl` - `engine/assets/shaderlib/ShaderLibrary/Lighting.hlsl` - `engine/assets/shaderlib/ShaderLibrary/MaterialInput.hlsl` - `engine/assets/shaderlib/ShaderLibrary/Shadow.hlsl` 目标: - builtin shader 共用统一宏与公共函数 - authoring 层不再重复声明一堆 per-object / lighting 结构 ### 8.2 统一的内建常量组 建议统一为接近 Unity 的内建分组: - `UnityPerFrame` 或 `XCPerFrame` - `UnityPerCamera` 或 `XCPerCamera` - `UnityPerDraw` 或 `XCPerDraw` - `UnityPerMaterial` 或 `XCPerMaterial` 建议引擎内部最终保留 `XC*` 前缀实现名,但 authoring 宏层提供 Unity 风格别名。 ### 8.3 内建纹理/采样器由 include 库与 reflection 管理 例如: - 主纹理 - 阴影图 - 环境图 - sampler state 这些都不再由 `.shader` 手填 `Resources(set,binding)`。 --- ## 9. Material 体系同步改造 shader 统一如果不带 material 一起改,最后会停在半路。 ### 9.1 material 的职责边界 新体系里 material 负责: - 选择 shader - 保存 property override - 保存 texture override - 保存 keyword 开关 - 保存材质级 render queue / tag override(如保留) 新体系里 material 不再负责: - 指定 `shaderPass` - 硬编码 backend 资源名 - 猜测 shader 内的 descriptor set / binding ### 9.2 material constant buffer 正式化 必须建立: - `Properties` -> `UnityPerMaterial/XCPerMaterial` 布局 - importer 生成 property layout - material 按 layout 打包 GPU 常量 - layout/hash 进入 pipeline/material cache key ### 9.3 keyword 体系正式化 建议引入: - `global keywords` - `local keywords` - material keyword set - variant lookup key material 应持有: - `ShaderKeywordSet` renderer 运行时根据: - shader - pass - keyword set - backend 选择最终 shader variant。 --- ## 10. Renderer 运行时契约调整 ### 10.1 pass 选择规则统一为 `LightMode` renderer 选 pass 时只看: - 当前 pipeline 阶段需要的 `LightMode` - shader/subshader/pass 是否匹配 - backend 是否有有效 variant 例如: - 主几何:`ForwardLit` / `Unlit` - 阴影:`ShadowCaster` - 深度:`DepthOnly` - ObjectId:`ObjectId` - FinalOutput:`FinalColor` ### 10.2 material 显式 `shaderPass` 进入弃用路径 建议执行顺序: 1. 第一阶段保留字段,但标记为 legacy 2. runtime 优先按 pass contract / LightMode 选 pass 3. 只有 legacy 资产才允许 fallback 到 `shaderPass` 4. migration 完成后移除 `shaderPass` 主路径职责 ### 10.3 builtin pass metadata 继续保留,但收进 importer/runtime 当前基于 semantic 的 builtin pass binding 解析仍然有价值,但应改成: - authoring/IR 层表达语义 - compiler/reflection 层生成 binding plan - runtime 只消费 binding plan 而不是继续靠散落的名字匹配和 fallback。 --- ## 11. 三后端统一策略 ### 11.1 D3D12 目标: - 直接消费 HLSL 编译产物 - 反射得到 root signature / resource layout 所需元数据 第一阶段可继续沿用 `ps_5_0 / vs_5_0`,但建议同时规划升级: - 后续逐步转 `SM 6.x` ### 11.2 Vulkan 目标: - 统一吃由 HLSL 编到 SPIR-V 的产物 - 摆脱独立 `.vk.glsl` 长期维护 ### 11.3 OpenGL 目标: - 不再长期维护独立 `.glsl` authoring 文件 - importer 自动生成 OpenGL 目标 GLSL 430 这一步会是整个阶段最大的工程风险之一,但它是“写法统一”绕不开的核心点。 ### 11.4 迁移期间的策略 在新体系落稳前,允许: - legacy backend-specific variant 继续存在 - 新 Unity 风格 shader 走统一 HLSL single-source 路径 两套 importer 并行一段时间,最终再逐步淘汰 legacy path。 --- ## 12. 分阶段实施计划 ### Phase A:冻结目标与建立兼容边界 目标: - 明确“什么叫 Unity 风格 shader” - 明确 legacy 与新 authoring 的兼容边界 工作项: 1. 写清 Unity 风格支持子集。 2. 明确旧 `.shader` 的 legacy 模式规则。 3. 明确新 authoring 中禁止出现: - `#pragma backend` - `Resources(set,binding)` 4. 明确 material 中 `shaderPass` 的弃用策略。 完成标准: - 文档、命名、兼容边界全部写死 ### Phase B:建立新的 Shader Authoring Parser 与 IR 目标: - 新 `.shader` authoring 能导入到统一 `ShaderIR` 工作项: 1. 新增 parser,支持: - `Shader / Properties / SubShader / Pass / Tags` - `HLSLINCLUDE / HLSLPROGRAM` - `#pragma vertex / fragment / target / multi_compile / shader_feature` - pass state DSL 2. 生成 `ShaderIR` 3. 支持 include 依赖收集 4. 保留 legacy importer 完成标准: - authoring parser 单测齐全 - 可以把一个 Unity 风格 shader 解析成稳定 IR ### Phase C:建立单一 HLSL 编译链 目标: - 打通 `HLSL -> D3D12/Vulkan/OpenGL` 编译管线 工作项: 1. 接入 DXC 编译 HLSL。 2. Vulkan 产出 SPIR-V。 3. OpenGL 产出 GLSL 430。 4. 建立 reflection 数据抽取: - cbuffer - texture - sampler - entry point - keywords 5. 缓存编译产物与依赖 hash。 完成标准: - 一份 HLSL authoring 能生成三后端产物 - OpenGL 不再依赖手写 `.glsl` 作为新体系长期主路径 ### Phase D:Material 与 property/keyword/runtime binding 正式化 目标: - material 能正式驱动新 shader artifact 工作项: 1. 引入正式 property layout。 2. 引入 material keyword set。 3. 生成 `PerMaterial` 常量缓冲布局。 4. texture/sampler 绑定从 reflection/约定生成。 5. 让 material 运行时不再关心 `set/binding`。 完成标准: - material property / texture / keyword 真正接入 GPU 绑定链 ### Phase E:renderer pass 选择与 pipeline cache 收口 目标: - renderer 完全按 pass contract 驱动 shader 工作项: 1. 按 `LightMode` 选 pass。 2. `shaderPass` 降级为 legacy fallback。 3. pipeline cache key 引入: - shader artifact id - pass id - keyword variant id - render state 4. builtin pass 的 runtime contract 全部切到新 artifact。 完成标准: - renderer 主路径不再依赖 material 显式 pass 指定 ### Phase F:分批迁移 builtin shader 建议迁移顺序: 1. `unlit` 2. `forward-lit` 3. `depth-only` 4. `shadow-caster` 5. `object-id` 6. `skybox` 7. `color-scale-post-process` 8. `final-color` 完成标准: - builtin shader 全部有新 authoring 版本 - 旧版 backend 分发文件不再是长期主定义来源 ### Phase G:文档、测试、旧路径收口 目标: - 让新旧体系的边界最终收口 工作项: 1. 更新 `tests/TEST_SPEC.md` 中 shader/material 测试矩阵。 2. 增加 authoring parser / compiler / runtime 回归测试。 3. 更新开发文档。 4. 标记 legacy 路径弃用阶段。 完成标准: - 新体系文档、测试、builtin 迁移都完成 --- ## 13. 测试策略 ### 13.1 Parser / Importer 单测 必须覆盖: - Properties 解析 - SubShader / Pass / Tags 解析 - HLSLINCLUDE / HLSLPROGRAM 解析 - pragma 解析 - pass state 解析 - include 依赖收集 - legacy / 新 authoring 双路径兼容 ### 13.2 Compiler 单测 必须覆盖: - 单一 HLSL 源能生成三后端产物 - reflection 结果稳定 - keyword variant 正确展开 - 编译错误日志可读、可定位到 authoring 源文件 ### 13.3 Material 单测 必须覆盖: - property 默认值 - property override - texture binding - keyword set - 常量缓冲布局打包 ### 13.4 Rendering 单测 必须覆盖: - renderer 按 `LightMode` 选 pass - legacy `shaderPass` fallback 行为 - keyword variant 参与 pipeline cache key - final color / post-process / shadow / object-id 不回退 ### 13.5 集成测试 至少回归: - `material_state_scene` - `transparent_material_scene` - `camera_stack_scene` - `directional_shadow_scene` - `multi_light_scene` - `skybox_scene` - `post_process_scene` - `final_color_scene` - `object_id_scene` 要求: - 三后端全部跑通 - GT 不回退 --- ## 14. 风险与控制策略 ### 风险 1:OpenGL 是统一写法最难的一环 原因: - OpenGL 当前直接吃 GLSL - 统一 authoring 要求它改为编译链生成目标 GLSL 控制策略: - 先把 importer/IR 做好 - OpenGL 先走“生成 GLSL 文本产物”路径 - legacy OpenGL GLSL 文件在迁移期保留 fallback ### 风险 2:一次性追 full Unity 语法会把阶段拖爆 控制策略: - 明确 first-class 子集 - 先做 SRP 真正依赖的 authoring 基础 - 非关键语法延后 ### 风险 3:material / pass 迁移会破坏当前 builtin renderer 控制策略: - legacy runtime path 保留一段时间 - builtin shader 分批迁移 - 每迁移一个 shader 就跑对应 integration ### 风险 4:编译错误如果不可读,会极大拖慢落地 控制策略: - 必须做 authoring 源到 backend 编译日志的映射 - 错误日志要带 shader 名、pass 名、stage、backend、源文件行号 --- ## 15. 收口判定 满足下面条件时,本阶段可视为完成: 1. 新 `.shader` authoring 采用 Unity 风格子集。 2. 新体系 shader authoring 中不再出现 `#pragma backend`。 3. 新体系 shader authoring 中不再出现 `Resources(set,binding)`。 4. HLSL single-source 能生成 D3D12 / Vulkan / OpenGL 三后端产物。 5. material 已正式接入 property / keyword / texture binding runtime。 6. renderer 按 `LightMode` 正式选择 pass。 7. `shaderPass` 只剩 legacy fallback,不再是主路径。 8. builtin shader 已完成新体系迁移。 9. 三后端关键集成测试全部通过。 --- ## 16. 与后续 SRP 的承接关系 这一阶段完成后,才能真正自然地承接: - `RenderPipelineAsset` - `RenderPipeline` - `RendererFeature` - `ScriptableRenderPass` - C# 层对 shader/material/keyword 的控制 承接关系应当是: ```text Unity-style Shader Authoring -> Shader Importer / IR / Artifact -> Native Material & Pass Runtime -> Native Renderer Pass Contract -> C# SRP / RendererFeature ``` 也就是说: - 这一阶段不是 SRP - 但这是 SRP 成立前必须先做完的最后一层底座 --- ## 17. 一句话总结 当前 shader 体系不是没有,而是还停在“过渡态”。 下一阶段的正确方向不是继续堆更多 shader 功能,而是: - **把 `.shader` 的 authoring 真正统一成 Unity 风格** - **把 backend 差异与 binding 细节收回 importer / compiler 层** - **把 material / pass / variant/runtime contract 一次性做正式** 只有这样,后面的 SRP 才不会建立在一层伪统一的 shader 体系上。