主题
项目自定义 Shader 与 Technique
这份指南说明项目如何写入自定义 GLSL、用 Technique XML 应用它,并在 SCE Editor 中为明确的变体和平台生成项目资源。项目 res/ 仍由现有项目资源打包流程处理;Shader 特有的步骤只是先生成字节码,不是重新编译官方 shadercacheextra。
Urho 来源与源码接入约定
SCE 的 Shader 系统源自 Urho3D(Urho),保留了 Technique/pass、材质参数、纹理槽和 VS() / PS() 等设计,当前使用经过 SCE 修改的 bgfx Shader 编译链。AI 可以复用已有的 Urho 知识和 GLSL 效果算法,但应以本页约定及当前资源为准;原版 Urho GLSL、网页 GLSL 和 Shadertoy 源码都需要适配,不能直接假定兼容。无需重新学习 GLSL,也不需要为每种特效准备教程。
| 项目 | 当前约定 |
|---|---|
| 阶段入口 | 同一 .glsl 中写 void VS() 和 void PS();引擎拆分阶段并改为目标入口。阶段条件用 COMPILEVS / COMPILEPS,由编译链提供,不写入项目变体清单 |
| 顶点输入与阶段传递 | 文件开头先引用项目 .def.sc,再在阶段条件内写 $input / $output;.def.sc 声明类型、语义和 varying 默认值,VS 输出与 PS 输入需匹配 |
| 公共头 | Common/common.sh 提供 bgfx 基础;urho3d_compatibility.sh 提供 Urho 输入别名;uniforms.sh、samplers.sh、transform.sh 提供当前引擎参数、采样器和变换。文件名大小写保持一致 |
| 模型与相机变换 | iPos 对应 a_position,iTexCoord 对应 a_texcoord0;沿用 iModelMatrix、GetWorldPos(...)、GetClipPos(...),不要根据其他引擎经验自行猜矩阵乘法顺序 |
| 主纹理与乘色 | sDiffMap 对应材质 diffuse 槽(槽位 0);cMatDiffColor 对应材质参数 MatDiffColor。XML/脚本写参数名 MatDiffColor,不是 shader 别名 cMatDiffColor |
| 时间 | cElapsedTime 用于 VS,cElapsedTimePS 用于 PS;从对应阶段公共参数读取,不假定存在 Shadertoy 的 iTime 或 Unity 的 _Time |
| 精度 | hfloat / hvecN / hmatN 表示高精度,hvecN_init(...) 用于构造高精度向量;普通 float / vecN 在移动端可能降为 half/mediump。坐标、UV 及中间计算需要贯穿高精度 |
| 自定义参数 | 新增参数时同时核对 Shader 声明和材质绑定。当前公共头使用 u_ uniform 与 c 别名;不要只在材质中填写一个名字就假定 Shader 已声明并使用它 |
公共头由当前编辑器下载的 shaders 资源包提供。支持自动共享 include 的新版编辑器在项目预编译和 PIE 按需编译时,先搜索项目 res/Shaders/BLGL/,再搜索当前已挂载共享包的 BLGL/,无需将公共头复制进每个项目。项目同名文件优先;项目内没有的公共头从共享目录补齐。varying .def.sc 也按这一顺序解析。
共享包路径通常为 <编辑器安装目录>/Update/<当前更新域名>/Res/_m/shaders/<包版本>/shaders/BLGL/。查找参考源码或为独立编译器选择公共头时,通常可取当前更新域名下版本号最大的已下载目录(按数值比较);1.0 的 Shader 包已停止更新。编辑器自动编译仍使用实际挂载的共享包。公共头路径和内容参与项目本机缓存标识;更新后重载项目再编译。若包内没有 BLGL/,先更新编辑器资源;仅有 ShaderLab/ 的旧包不包含公共头。
当前下载包中的源码和公共头均可直接用文本编辑器或 AI 文件读取工具打开。例如,安装目录为 E:/WasmSCE、更新环境为 master 时,源码位于 E:/WasmSCE/update/editor-master.spark.xd.com/res/_m/shaders/<当前包版本>/shaders/BLGL/。AI 应先读取这里的实现和公共头,再编写项目 Shader。包版本随更新变化,不要固定版本号;项目编译日志中的 Shared shader include cache 会记录当前共享源码目录与本机编译缓存目录的对应关系,可据此确认实际使用的包。
旧版编辑器尚未自动追加共享目录时,可以临时将当前包的 BLGL/ 内容直接合并到 <project>/res/Shaders/BLGL/,保留相对路径、大小写和已有自定义文件。例如 Common/common.sh 放在项目 res/Shaders/BLGL/Common/common.sh,不要多嵌套一层目录。升级后,项目中保留的旧公共头仍会优先于共享包,需核对这些文件是否为有意覆盖。不要从其他 Urho 版本复制公共头。
当前 SCE/bgfx 链路使用 BLGL/ 下的 .glsl。包内 GLSL/、HLSL/ 分别对应 Urho 原生 OpenGL 和 Direct3D 后端,不能直接作为当前项目编译链的输入;即使目标是 DX11,也从 BLGL 源码生成 bgfx 产物。
使用内置 Shader 作为参考
共享包 BLGL/ 中的 .glsl 是引擎实际使用的实现,可以直接作为编写自定义 Shader 的参考。优先阅读当前包内与目标渲染路径接近的文件:
| 文件 | 适合参考的内容 |
|---|---|
Unlit.glsl | 基础纹理、材质乘色、顶点颜色与雾 |
LitSolid.glsl | 常规网格光照及相关 pass |
PBRLitSolid.glsl | PBR 材质与光照路径 |
UnlitMeshParticle.glsl | 网格粒子输入及特效宏 |
内置文件通常包含多种条件宏和渲染路径。借用时需一起核对对应 Technique/pass、defines、varying 和顶点输入,保留目标路径的契约;不要仅根据文件名认定它可以直接替换任意对象的材质。下面的最小示例则将源码、材质、Technique 和编译清单放在一起,便于首次接入。
最小示例:静态网格主纹理乘色
可复制的完整文件位于 TexturedUnlit 示例,包含 GLSL、varying、Technique、材质、纹理颜色空间配置及初始编译清单。将其中 res/ 合并到项目;已有编译清单时按条目合并 editor/shaders.precompile.json。
核心变换与采样如下(完整文件中的公共头、阶段声明和 varying 同样必需):
glsl
void VS()
{
hmat4 modelMatrix = iModelMatrix;
hvec3 worldPos = GetWorldPos(modelMatrix);
gl_Position = GetClipPos(worldPos);
vCustomUV = iTexCoord;
}
void PS()
{
vec4 sampleColor = texture2D(sDiffMap, vCustomUV);
#ifdef NOT_SUPPORT_SRGB
sampleColor.rgb = GammaToLinearSpace(sampleColor.rgb);
#endif
gl_FragColor = sampleColor * cMatDiffColor;
#if defined(GAMMA_IN_SHADERING) || defined(USEGAMMA)
gl_FragColor.rgb = LinearToGammaSpace(gl_FragColor.rgb);
#endif
}示例只要求网格有 Position 和 UV0,使用不透明 base pass,不计算光照、阴影或雾。它是编写静态网格效果的起点,不是所有对象的通用替换材质。需要骨骼、实例化、粒子、Urho2D 或全屏后处理时,应保留目标路径的输入、变换与 pass 契约,再移植效果算法;仅增加宏不能补齐缺少的顶点输入。示例清单中的空 defines 仅是初始组合,实际运行请求的画质、Gamma 等组合仍需扫描日志后补齐。
目录与命名
项目 Shader 源码、include 和 varying 定义放在:
text
<project>/res/Shaders/BLGL/
MyEffect.glsl
MyEffect.sh
varying_my_effect.def.sc
includes/...Technique XML 可以放在项目 res/ 下的最终资源路径,例如 res/Editor/Techniques/MyEffect.xml:
xml
<technique vs="MyEffect" ps="MyEffect">
<pass name="base" />
</technique>vs、ps、cs 是 Shader 名称,不要写 .glsl 后缀;名称应能在 res/Shaders/BLGL/ 下解析到源码。vsdefines、psdefines、csdefines 可以声明 Technique 固定宏。Technique 是渲染配方,不是 Shader 源码:它决定 pass、状态、阶段和 defines。
在 GameGraph 或其他客户端代码中,把 Technique 路径赋给材质;路径应使用项目资源逻辑路径,例如:
csharp
#if CLIENT
var material = Material.Load("res/Materials/MyMaterial.xml");
material.Technique("Editor/Techniques/MyEffect.xml");
mesh.Material(material);
#endif如果项目资源使用的是 .material 文件,优先在该文件中保存 Technique,再让运行时加载材质。不要把项目 Technique 伪装成 EngineRes/ 或 Editor/ 官方文件;本地试验应使用项目唯一路径。
Editor 调试
Windows Editor/PIE 在实际使用到某个项目 Shader 变体时可以按需编译。项目 Shader 的缓存按源码及 include 内容建立项目隔离的本机缓存;官方 Shader 缓存不会被项目源码替换。
修改 GLSL 或 include 后,先停止 PIE,再重新加载项目或确认下一局已重新加载 Shader 资源;仅停局重进如果继续命中旧资源,应调用 editor_reload_project,不要把旧画面当成 Shader 编译成功的证据。当前流程不提供运行中热更新。
选择、保存与编译
打开“工具 → 项目 Shader 预编译”,或者使用 Editor MCP:
shader_list_sources查看res/Shaders/BLGL中的源码。shader_get_plan读取editor/shaders.precompile.json。- 为每条组合指定
VS、PS或CS、完整 defines,以及editor-dx11、windows-dx11、android-essl、ios-metal平台。画质候选对应RENDER_QUALITY=0/1/2/3;其中3是最高/自适应,不是从1开始编号。SSAO、雾等常用选项之外,额外 defines 文本框可输入CLUSTER、CLUSTER_VS、FOGOFWAR、GAMMA_IN_SHADERING等实际宏。不要为所有宏做笛卡尔积,只登记实际会用到的组合。 - 用
shader_save_plan保存清单;带上上一次返回的expected_json时,外部文件发生变化会安全拒绝覆盖。 - 先调用
shader_compile_project(默认 dry-run)检查 job 列表;确认后传apply: true执行编译。编译器在 Editor DX11 中生成三类产物:Windows/Editor DX11、Android ESSL、iOS Metal。 - 编译成功后,产物原子替换
res/Shaders/BLGL/Compiled/;项目包随后按通常的res规则打包。失败时旧的完整产物保持不变。
Compiled 目录由编辑器写入 .sce-project-shader-cache 标记管理。不要手工删除或把官方缓存文件复制进来;如果发现目录没有该标记,先备份并让编辑器报告受控目录冲突。
示例:
powershell
.\ai\tools\Invoke-SceEditorMcp.ps1 -Tool shader_get_plan -Pretty
.\ai\tools\Invoke-SceEditorMcp.ps1 -Tool shader_compile_project -ArgumentsJson '{"platforms":["editor-dx11","android-essl"],"apply":false}' -Pretty
.\ai\tools\Invoke-SceEditorMcp.ps1 -Tool shader_compile_project -ArgumentsJson '{"apply":true}' -Pretty图形编辑器的编译入口要求当前项目已打开且 Editor 使用 DX11。Android/iOS 的目标字节码在 Windows 上交叉编译生成,不表示移动端允许在设备上现场编译。
无头模式编译
无头编辑器使用相同的 shader_* MCP 工具和 editor/shaders.precompile.json,通过配套的独立 ShaderCompile.exe 进程编译项目源码。编译本身无需登录或启动图形编辑器,Windows 编译环境仍需支持 DX11。请使用包含该功能的完整编辑器包,保留 ShaderCompile.exe 及其配套依赖。
启动时通过 --res-root 指定安装资源目录,并用可重复的 --shader-include-root 指明所需公共 Shader 包。公共包版本应来自该安装的实际资源记录,不由编译器猜测最新版本。例如:
powershell
& 'C:/SCE/version-2000/SCE.HeadlessEditor.exe' `
--editor-root 'C:/SCE/ShaderWork' --project 'C:/Projects/MyGame' `
--res-root 'C:/SCE/update/editor-master.spark.xd.com/res' `
--shader-include-root 'C:/SCE/update/editor-master.spark.xd.com/res/_m/shaders/<包版本>/shaders' `
--mcp-mode http --mcp-port 18720项目及依赖加载就绪后,按前述读取清单、保存、dry-run、apply: true 的顺序操作。支持相同的 Windows/Editor DX11、Android ESSL 和 iOS Metal 目标。项目源码优先于公共 include;也可传入直接包含 BLGL 头文件的目录。需要指定工具位置时用 --shader-compile-executable;--shader-compile-timeout-ms 控制编译等待上限,默认 300000 毫秒。
编译期间不能切换项目、构建、准备发布或换号。只有整个批次成功、源码和清单未变化且产物核对通过后,才替换 Compiled;失败、取消或超时保留原产物。若报告 cleanup_pending,应先完成宿主关闭清理。发布前执行所需平台的编译,随后按项目资源正常打包;编译操作不会自动上传或发布。
独立调用底层编译器
项目创作优先使用上述编辑器窗口或 MCP,它们会完成阶段处理、参数选择和产物写入。图形编辑器在自身进程内调用 SCE 适配版 shaderc 编译库;无头模式由配套 ShaderCompile.exe 的项目任务入口调用同一编译逻辑。
独立集成底层 bgfx::shaderc::CommandCompileShader 时,需要自行传入 include 路径;编译库不知道编辑器当前选中的共享包版本,也不会扫描 Update/。可重复传 -i,按项目优先、共享补缺的顺序排列。以下仅为路径参数片段,不是完整编译命令:
text
-i "D:/MyProject/res/Shaders/BLGL/"
-i "D:/SparkEditor/Update/<当前更新域名>/Res/_m/shaders/<包版本>/shaders/BLGL/"
--varyingdef "D:/MyProject/res/Shaders/BLGL/varying_my_effect.def.sc"--varyingdef 需要传入已经找到的具体文件路径;若 varying 来自共享包,应传共享文件的实际路径,而不是期待 -i 自动选择此参数。
| 底层参数 | SCE 适配版含义 |
|---|---|
-i | 物理 include 目录,可重复;路径含空格时作为一个参数传入 |
--varyingdef | 该阶段使用的 .def.sc 文件路径 |
-c | 已完成引擎阶段处理的源码字符串,不是源码文件路径 |
-s | 原始源码文件路径,用于编译器的源文件上下文 |
-f | 编译工作目录;这里不是上游 shaderc 常见的输入源码路径含义 |
-o | 输出字节码路径 |
--type、--platform、--profile、--define | 阶段、平台、目标配置及完整宏组合,须与引擎编译请求匹配 |
底层调用还需执行与引擎一致的 VS() / PS() 阶段拆分、入口转换和兼容头处理。仅将完整 .glsl 传给通用 GLSL/HLSL 编译器,不能保证生成引擎可加载的 bgfx 产物。
上述参数属于底层 shaderc 接口,不能直接套用到 ShaderCompile.exe 或 PrecompileShader.exe 的批处理入口。无头模式的项目任务参数和结果由宿主管理,项目创作应调用 shader_compile_project,无需手工构造内部任务文件。
从日志补齐变体
当运行时出现 Shader 缺失或编译失败,优先保留产生问题的 Editor/客户端日志,不要凭截图猜 defines。调用 shader_scan_missing_variants,或传入明确的 log_paths。它识别以下记录:
Compile Shader, vsDefines=..., psDefines=...Success Compile: Shader[...] Type[...] Defines[...]Failed Compile: Shader[...] Type[...] Defines[...]No Compile Result ...
返回的 shader、stage、defines、missing 和 evidence 是补清单的依据。Compile Shader 记录没有 Shader 名称时,不要擅自把它归给某个源码;结合 shader_validate_technique、材质或 Runtime material.probe 先确认来源。将实际失败组合补入清单后,重新 dry-run,再执行 apply: true。
工具也会识别 Could not find resource shaders/blgl/... 形式的共享 GLSL、.sh 或 .def.sc include 缺失,并在 missing_resources 中返回缺失路径、引用它的 Shader 和证据行。这类问题通常属于共享 Shader 资源包,不应直接复制成项目私有 Shader。
shader_validate_technique 只读检查 Technique XML 的阶段、defines 和缺失源码;Runtime MCP 的 material.probe / scenegraph.materials 用于确认运行时真正加载的 Technique 路径、pass、defines 和状态。两者分别回答“文件写了什么”和“客户端实际用了什么”,不能互相替代。
硬件 sRGB 与软件解码变体
颜色贴图声明 sRGB=true 后,当前客户端按设备和纹理格式检测硬件 sRGB 采样能力;格式支持时由 GPU 将采样颜色解码到线性空间。Android 的 ASTC5×5、ASTC6×6 也可以使用硬件 sRGB,不能仅凭平台或压缩格式要求软件解码。线性压缩纹理可用与硬件 sRGB 采样可用是两项独立能力。
NOT_SUPPORT_SRGB 用于需要软件 sRGB → 线性解码的像素 Shader 分支。自定义 Shader 必须实现对应转换,例如对需要解码的颜色样本调用 GammaToLinearSpace();仅添加宏不会自动补上转换。硬件已经解码的样本不要再次转换,Alpha 和法线、遮罩等线性数据保持原值。GAMMA_IN_SHADERING 则控制线性 → Gamma 输出,与输入解码独立。
移动端默认软件解码设置参考 ASTC5×5 的 sRGB 检测结果,并仅作用于启用此功能的 pass;场景粒子按实际 diffuse 贴图的颜色空间声明及格式能力选择。项目应以运行时请求的完整 defines 为准,通过日志扫描补齐普通变体和确实需要的 NOT_SUPPORT_SRGB 变体,不要在 Technique 中固定强加该宏。一台设备支持硬件 sRGB,仍不能据此删除其他设备、格式或旧客户端需要的软件变体。
原生启动日志中的 INFO 记录 [TextureCapabilities] format=... linear=... srgb=... 可用于定位:srgb=1 表示该格式检测到硬件 sRGB 采样支持,linear=1 只表示线性纹理可用。ASTC5×5/6×6 保留的线性纹理兼容兜底不会强制放行 sRGB;探测失败时可结合 [TextureProbe] 的 srgb、glError 和 keepLinearAstc 判断。纹理的 s_rgb 属性仅表示颜色空间声明,实际变体还应核对日志或 material.probe。
常见误区
- 项目只需要编译自己的 Shader;官方
shadercacheextra直接复用。 - 勾选平台只是清单选择;必须实际运行
shader_compile_project并检查成功结果。 - Technique 路径、Shader 名称和源码文件是三层不同概念;
.xml不等于.glsl。 - 看到旧画面先检查资源重载和日志,再判断 Shader 源码是否错误。
- 编译失败时不要替换
Compiled目录,也不要发布不完整的项目资源。