From b68e5141549b2a2cfab94f045a38194208076e7b Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 6 Apr 2026 17:22:09 +0800 Subject: [PATCH] docs: add unity-style shader formalization plan --- ..._Unity风格Shader体系正式化计划_2026-04-06.md | 841 ++++++++++++++++++ 1 file changed, 841 insertions(+) create mode 100644 docs/plan/Renderer下一阶段_Unity风格Shader体系正式化计划_2026-04-06.md diff --git a/docs/plan/Renderer下一阶段_Unity风格Shader体系正式化计划_2026-04-06.md b/docs/plan/Renderer下一阶段_Unity风格Shader体系正式化计划_2026-04-06.md new file mode 100644 index 00000000..0eaf6fc4 --- /dev/null +++ b/docs/plan/Renderer下一阶段_Unity风格Shader体系正式化计划_2026-04-06.md @@ -0,0 +1,841 @@ +# 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 体系上。