20 KiB
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 + 三后端编译产物”的正式体系。
完成后应达到:
- shader authoring 采用 Unity 风格
.shader语法,不再要求作者显式写 backend variant 分发表。 - 新语法不再要求作者手写
set/binding级资源绑定表。 - HLSL 成为 raster shader 的单一 authoring 语言源。
- D3D12 / Vulkan / OpenGL 的差异退到 importer / compiler / artifact 层。
- material 只管 property / keyword / texture / render state,不再负责点名 pass。
- renderer 按
LightMode/ pass contract 选 pass,而不是靠 material 的临时字符串兜底。 - builtin shader 全部迁移到新体系并保持三后端回归稳定。
- 保留 legacy shader 兼容层,避免一次性炸掉现有工程内容。
3. 非目标
本阶段明确不做:
- SRP 本体
- render graph
- deferred / clustered
- Shader Graph
- Surface Shader
- 完整 Unity ShaderLab 100% 全语法一次性覆盖
- volume 系统
- 更多新渲染效果
本阶段也不追求一口气把 Unity 的所有 authoring 特性补完,例如:
FallbackUsePassCustomEditorGrabPass- 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 层的边界还没收干净”。
具体表现为:
-
authoring 层暴露后端差异
.shader文件直接写#pragma backend D3D12/OpenGL/Vulkan- shader 作者必须知道后端与源码文件映射
-
authoring 层暴露 RHI binding 细节
.shader文件显式写Resources { ConstantBuffer / Texture2D / Sampler, set, binding }- 这更像 Vulkan/D3D12 binding 清单,不是 Unity 风格 shader authoring
-
material 还带着临时 pass 选择职责
Material::SetShaderPass()仍然存在- renderer 仍然优先吃 material 显式指定的 pass
- 这会阻碍未来 RendererFeature / SRP 规范化
-
关键字与变体体系缺失
- 没有正式的
multi_compile / shader_feature - 没有变体剥离与编译缓存策略
- 没有正式的
-
include 与共享库体系缺失
- 没有正式的 shader include 搜索路径、预处理、公共库组织
-
pass state 仍然不在 shader authoring 的统一语义内
- 诸如
Cull / ZWrite / ZTest / Blend / ColorMask / Stencil还没有完整进入 shader authoring contract
- 诸如
-
三后端仍然是物理三套源码直连
- 当前虽然“逻辑上一个 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
建议语法子集:
ShaderPropertiesSubShaderPassTagsLODHLSLINCLUDEHLSLPROGRAMENDHLSL#pragma vertex#pragma fragment#pragma target#pragma multi_compile#pragma shader_feature#pragma shader_feature_localCullZWriteZTestBlendColorMaskStencilOffset
6.2 Importer / Parser 层
职责:
- 解析 Unity 风格
.shader - 生成统一的内部
ShaderIR
建议引入:
ShaderAuthoringParserShaderIRShaderSubShaderIRShaderPassIRShaderKeywordDeclShaderPassStateDescShaderProgramIR
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 第一阶段必须支持的语法
第一阶段建议正式支持:
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 第一阶段暂不支持的语法
第一阶段可暂缓:
FallbackUsePassGrabPassCustomEditorCategoryDependency- Surface Shader
- CG fixed-function 时代遗留语义
这些需要列入兼容性说明,但不应阻塞首版落地。
8. 统一的 shader library 与 include 体系
这一层是“写法统一”能否成立的关键。
8.1 必须引入正式的 include 库
建议新增:
engine/assets/shaderlib/ShaderLibrary/Core.hlslengine/assets/shaderlib/ShaderLibrary/Common.hlslengine/assets/shaderlib/ShaderLibrary/SpaceTransforms.hlslengine/assets/shaderlib/ShaderLibrary/Lighting.hlslengine/assets/shaderlib/ShaderLibrary/MaterialInput.hlslengine/assets/shaderlib/ShaderLibrary/Shadow.hlsl
目标:
- builtin shader 共用统一宏与公共函数
- authoring 层不再重复声明一堆 per-object / lighting 结构
8.2 统一的内建常量组
建议统一为接近 Unity 的内建分组:
UnityPerFrame或XCPerFrameUnityPerCamera或XCPerCameraUnityPerDraw或XCPerDrawUnityPerMaterial或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 keywordslocal 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 进入弃用路径
建议执行顺序:
- 第一阶段保留字段,但标记为 legacy
- runtime 优先按 pass contract / LightMode 选 pass
- 只有 legacy 资产才允许 fallback 到
shaderPass - 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
目标:
- 不再长期维护独立
.glslauthoring 文件 - 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 的兼容边界
工作项:
- 写清 Unity 风格支持子集。
- 明确旧
.shader的 legacy 模式规则。 - 明确新 authoring 中禁止出现:
#pragma backendResources(set,binding)
- 明确 material 中
shaderPass的弃用策略。
完成标准:
- 文档、命名、兼容边界全部写死
Phase B:建立新的 Shader Authoring Parser 与 IR
目标:
- 新
.shaderauthoring 能导入到统一ShaderIR
工作项:
- 新增 parser,支持:
Shader / Properties / SubShader / Pass / TagsHLSLINCLUDE / HLSLPROGRAM#pragma vertex / fragment / target / multi_compile / shader_feature- pass state DSL
- 生成
ShaderIR - 支持 include 依赖收集
- 保留 legacy importer
完成标准:
- authoring parser 单测齐全
- 可以把一个 Unity 风格 shader 解析成稳定 IR
Phase C:建立单一 HLSL 编译链
目标:
- 打通
HLSL -> D3D12/Vulkan/OpenGL编译管线
工作项:
- 接入 DXC 编译 HLSL。
- Vulkan 产出 SPIR-V。
- OpenGL 产出 GLSL 430。
- 建立 reflection 数据抽取:
- cbuffer
- texture
- sampler
- entry point
- keywords
- 缓存编译产物与依赖 hash。
完成标准:
- 一份 HLSL authoring 能生成三后端产物
- OpenGL 不再依赖手写
.glsl作为新体系长期主路径
Phase D:Material 与 property/keyword/runtime binding 正式化
目标:
- material 能正式驱动新 shader artifact
工作项:
- 引入正式 property layout。
- 引入 material keyword set。
- 生成
PerMaterial常量缓冲布局。 - texture/sampler 绑定从 reflection/约定生成。
- 让 material 运行时不再关心
set/binding。
完成标准:
- material property / texture / keyword 真正接入 GPU 绑定链
Phase E:renderer pass 选择与 pipeline cache 收口
目标:
- renderer 完全按 pass contract 驱动 shader
工作项:
- 按
LightMode选 pass。 shaderPass降级为 legacy fallback。- pipeline cache key 引入:
- shader artifact id
- pass id
- keyword variant id
- render state
- builtin pass 的 runtime contract 全部切到新 artifact。
完成标准:
- renderer 主路径不再依赖 material 显式 pass 指定
Phase F:分批迁移 builtin shader
建议迁移顺序:
unlitforward-litdepth-onlyshadow-casterobject-idskyboxcolor-scale-post-processfinal-color
完成标准:
- builtin shader 全部有新 authoring 版本
- 旧版 backend 分发文件不再是长期主定义来源
Phase G:文档、测试、旧路径收口
目标:
- 让新旧体系的边界最终收口
工作项:
- 更新
tests/TEST_SPEC.md中 shader/material 测试矩阵。 - 增加 authoring parser / compiler / runtime 回归测试。
- 更新开发文档。
- 标记 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
shaderPassfallback 行为 - keyword variant 参与 pipeline cache key
- final color / post-process / shadow / object-id 不回退
13.5 集成测试
至少回归:
material_state_scenetransparent_material_scenecamera_stack_scenedirectional_shadow_scenemulti_light_sceneskybox_scenepost_process_scenefinal_color_sceneobject_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. 收口判定
满足下面条件时,本阶段可视为完成:
- 新
.shaderauthoring 采用 Unity 风格子集。 - 新体系 shader authoring 中不再出现
#pragma backend。 - 新体系 shader authoring 中不再出现
Resources(set,binding)。 - HLSL single-source 能生成 D3D12 / Vulkan / OpenGL 三后端产物。
- material 已正式接入 property / keyword / texture binding runtime。
- renderer 按
LightMode正式选择 pass。 shaderPass只剩 legacy fallback,不再是主路径。- builtin shader 已完成新体系迁移。
- 三后端关键集成测试全部通过。
16. 与后续 SRP 的承接关系
这一阶段完成后,才能真正自然地承接:
RenderPipelineAssetRenderPipelineRendererFeatureScriptableRenderPass- C# 层对 shader/material/keyword 的控制
承接关系应当是:
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 体系上。