关于 scenario-unity-shaders
scenario-unity-shaders 面向使用 Unity 6.3 的技术美术和着色工程师,指导着色器编写与验证。重点包括目标平台编译、渲染通道一致性、变体预算、首次使用表现和 GPU 成本测量。
主要功能
- 01手写 URP HLSL 工作流
- 02基于模板编辑 Shader Graph
- 03目标 API 着色器编译检查
- 04变体剥离与预算审计
- 05PSO 预热与首次使用检查
- 06GPU 成本测量
适用场景
- 01编写卡通、水面或溶解着色器
- 02检查各渲染阶段的着色器通道
- 03减少发布的着色器变体
- 04测量全屏效果成本和预热情况
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-unity-shaders安装后在 Claude Code 中输入/scenario-unity-shaders确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/ut_shaders.pyscripts/AgentKit/Shaders/ComputeJobs.csGpuCostJobs.csPsoJobs.csRendererFeatureJobs.csShaderGraphJobs.csShaderJobs.csShaderLab.csVariantJobs.csscripts/Runtime/AgentFullscreenFeature.csAgentPsoProbe.csscripts/Shaders/AgentCompute.computeAgentCustomLighting.hlslAgentDissolve.shaderAgentEdgeOutline.shaderAgentGrayscale.shaderAgentLit.shaderAgentNoise.hlslAgentPasses.hlslAgentToon.shaderAgentWater.shader详细介绍
Unity 着色器(技术美术、着色)
在 Unity 6.3 中,专业着色效果必须经得起验证:能为目标图形 API 编译、所有通道结果一致、剥离后统计变体数量、展示首次使用时的预热效果、以 GPU 毫秒为单位测量开销,并通过带数值的捕获画面检查外观。没有鼠标的 Agent 要通过文本实现这些目标:手写 URP HLSL 是主要路径;Shader Graph 则通过现有图表和公开属性驱动,并在 Custom Function 节点后使用由 Agent 编写的 .hlsl;新建图表连线需要 GUI 操作(references/gui-paths.md)。目标环境:6000.3.21f1、URP 17.3、Shader Graph 17.3、Apple Silicon 上的 Metal。如果此处提到的同级技能不在可用技能中,请让用户安装(npx skills add scenario-labs/skills --skill <name>);若无人值守,请根据工具架构继续操作并标明这一缺口。
必需背景: scenario-unity-expert(通道、审查流程、6.3 陷阱)。其工具包(ut_env、ut_run、ut_review、AgentKit)仅导入,不复制。
状态(2026-09-24,盲评重构后): 此处所有流程均已在 Unity 6000.3.21f1 中运行:tests/code/unity-shaders/run_all.sh --live(73 项实时检查,包括开发播放器 PSO 验证、32 项离线检查,全部通过;证据见 archive/tests/unity-shaders/)。
立场(专家增量)
- 优先使用文本,图形按模板制作。 Shader Graph 没有循环,照明节点也几乎没有,因此灯光和循环都写在 HLSL 中(Daniel Ilett,F8bAI6dIrto [00:07:13]);它唯一的优势是能生成深度和阴影通道(bH--RU6qyTw [00:38:24]),而
AgentPasses.hlsl也让手写着色器具备了这一能力。使用图形时,复制一个图形,设置其公开引用,并将变化逻辑放进 File 模式的.hlsl文件中。Graph Settings 会绑定每个通道:启用 Alpha Clipping 且不启用 Allow Material Override,会将#define _ALPHATEST_ON 1写入全部 8 个生成的通道,而且没有布尔关键字能将其移除(已观察已安装的UniversalTarget.cs)。 - 每个通道都必须保持一致。 位移和
clip()也会在 ShadowCaster、DepthOnly 和 DepthNormals 中运行(6.3 Manual;Ilett [00:37:52])。缺少 DepthOnly 通道会导致不透明着色器在 Depth Priming 下不可见(已观察)。DepthNormals 会写入原始世界空间法线:Manual 中的normal * 0.5 + 0.5示例在 SSAO 和轮廓线中读取结果有误(已观察)。 - 阅读已安装的渲染管线,而不是教程。 从已安装的
Lit.shader复制关键字(Ilett [00:14:51];ut_shaders.urp_lit_keywords()):6.3 有_CLUSTER_LIGHT_LOOP和 4 个软阴影变体。LIGHT_LOOP_BEGIN会跳过方向光,因此自定义循环在 Forward+ 下需要一个方向光预循环;Unity 自带的 Custom Lighting 示例缺少这一环节(已观察)。 - 变体预算要在剥离之后计算。 材质切换使用
shader_feature,仅运行时 C# 切换使用multi_compile,dynamic_branch要配合if测试,绝不能用#if(6.3 Manual)。先启用 Strict Shader Variant Matching,再查看构建日志中的漏斗统计(Carotenuto,Unity 博客,2024 年)。只有构建中某个材质启用了shader_feature(或_local)变体,该变体才会随包发布(已观察到 0 与 6 的差异):绝不要在运行时 C# 中切换这类变体。Unity 6 Shader Build Settings 可以排除或重新设置没有代码的关键字类型,但对于用#if测试的关键字,dynamic_branch覆盖设置会被静默忽略,URP 的软阴影和 SSAO 集也包括在内(已观察)。 - 证明首次使用时已完成预热。 在开发版播放器中,针对每种图形 API 和平台各采集一份
GraphicsStateCollection,在加载画面期间预热,并展示游戏帧中Shader.CreateGPUProgram采样数为 0、日志上传数为 0(6.3 Manual;已观察到 Metal 上从 14 降至 0)。 - 成本看 GPU 时间,而不是指令数。 循环按最大迭代次数计,float4 的成本约为 float 的 4 倍,超越函数会消耗很多周期(Ben Cloward,E82XxlXMJs4 [00:15:44])。此处测得:对 float4 执行 64 次
sin的成本约为 float 的 3 倍,是乘加循环的 10 倍或更多。只有设备分析器显示受 ALU 限制时,half 才有优势(Freya Holmér,kfM-yu0iQBk [01:20:26]):桌面端 PlatformDefault 会将half编译为float。 - Render Graph 结构。
RecordRenderGraph只声明内容,不记录内容;先 blit 一次到新纹理,再设置resourceData.cameraColor = destination;在AddRenderPasses中进行条件判断;拒绝使用后备缓冲区(6.3 文档;Unity 抖动示例,U8PygjYAF7A [00:09:06] [00:10:10])。 - 一个标量场,一个阈值,两种颜色;先观察,再测量。 卡通着色、水、溶解、轮廓线和抖动都会对 0..1 值进行阈值处理(视觉摘要);水的深度遮罩也能让岸边法线更平缓(gRq-IdShxpU [00:06:12])。让发光边缘与裁剪测试错开(Brackeys,taMp1g1pBeE [00:05:31])。将中间结果输出为颜色(Freya [01:57:25]),然后断言投影探针处的像素。
首先明确
- 平台和图形 API:Metal、Vulkan、DX12、GLES 3.x、WebGL 2 或 WebGPU。计算功能运行于 Metal、Vulkan、DX11/12 和 GLES 3.1+(GLES 3.1 保证支持 4 个缓冲区),不支持 GLES 3.0 或 WebGL 2;Web 端通过 WebGPU 使用该功能(6.3 中为实验性功能)。GPU Resident Drawer 和 STP 完全不支持 GLES。
- 各渲染器的渲染路径(Forward、Forward+、Deferred+):决定光照循环代码; Shader Graph 自定义光照仅适用于 Forward 和 Forward+。
- 各质量级别的 URP 资源:Depth 和 Opaque Texture、软阴影、SSAO。模板中的
Mobile_RPAsset默认关闭了 Depth Texture:渐隐水面需要开启此选项,或使用烘焙的岸边遮罩。 - 风格和作者:卡通渲染或 PBR;由美术人员编辑图形(保留图形文件,由 Agent 维护
.hlsl), 或仅编写代码。 - 每个对象的值:材质实例、
MeshRenderer/SkinnedMeshRenderer.SetShaderUserValue(6.3,兼容 SRP Batcher),或MaterialPropertyBlock(会破坏 SRP Batcher)。 - 预算:效果的 GPU 毫秒数、每个着色器的变体数、过度绘制、首次使用时的卡顿。未明确说明时的默认值:
URP 17.3 Forward+、模板中的 PC 和移动设备级别、HLSL,着色器位于
Assets/AgentShaders/。
工作流
- 安装并盘点。
ut_shaders.install(P);RendererFeatureJobs.ListFeatures(检查每个 质量级别、资源、渲染器和功能);lint_shader_file、shadergraph_summary(目标、属性、Graph Settings 检查结果)、lint_project_keywords。GATE:计划中列出管线信息;列出 lint 错误。 - 编写。 从
scripts/Shaders/开始:AgentLit、AgentToon、AgentWater、AgentDissolve、AgentEdgeOutline、AgentGrayscale、AgentCompute、共享文件AgentPasses.hlsl(AGENT_CLIP/AGENT_DISPLACE)和AgentNoise.hlsl。图形路线:ShaderGraphJobs.ImportPackageSample、DuplicateGraph、shadergraph_set_target_settings、GeneratedCode、AgentCustomLighting.hlsl。GATE:lint 错误为零。 - 针对目标编译。
ShaderJobs.ValidateShaders(ShaderUtil.GetShaderMessages,并在 Metal 上对每个通道执行CompileVariant)。GATE:错误为零,且四个不透明通道均存在。 - 进行基准测试并检查效果。
SetupMaterials、ShaderLab.BuildScene、ShaderLab.Shots(每次 启动进行多次拍摄:材质、渲染器、功能和 URP 更改;将探针投影到像素)。GATE:ut_review标记无异常,已打开联系表,探针数值符合预期(阴影比开阔地面暗,溶解值为 1 时阴影消失,额外光源会改变受光侧)。 - 全屏效果。 使用
RendererFeatureJobs.AddFeature和AgentFullscreenFeature,或零代码的FullScreenPassRendererFeature。GATE:开启和关闭时的捕获结果差异符合预期;无后备缓冲区错误。 - 变体。
VariantJobs.StrippingSettings(开启严格匹配)、针对发货材质执行BuildShaderBundle、parse_variant_funnel、variant_budget_check、剥离规则或KeywordOverrides(Shader Build Settings)、PostProcessingAudit(保留 Assets/ 下每个 VolumeProfile 的后处理变体)。 GATE:每个着色器的发货变体数量低于预算;记录器总数与 Unity 的“After scriptable stripping”一致; 覆盖项已清除,或已明确选择提交。 - 首次使用卡顿。
PsoJobs.BuildPsoScene、ut_run.build(development=True)、ut_shaders.run_pso_player追踪,分别执行冷启动和热启动;PsoJobs.InspectCollection;将StreamingAssets/PSO/<platform>_<api>.graphicsstate随产品一起发布,由AgentPsoProbe读取。GATE:pso_gate无异常(冷启动按标记和日志均显示有上传,热启动则均不显示)。 - 成本。 使用
GpuCostJobs.MeasureFullscreenCost执行 A/B 测试、交错测试,关闭异步编译 (半精度测试使用precision_model)。GATE:耗时改善超过噪声范围;设备数据取自开发版播放器(scenario-unity-performance)。 - 交付。 着色器、材质、功能资源、每种 API 对应的
.graphicsstate,以及证据包(Handoffs)。
数值
| 数值 | 对应情境 | 来源 |
|---|---|---|
| 7,688 个完整变体;每个阶段 4,046 个;设置后 62 个;实际发布 13 个 | 在 macOS AssetBundle 构建中,使用模板的 URP 资源时的 AgentKit/Lit | 实测 |
| 13 个降至 11 个 | 在 Shader Build Settings 中排除软阴影组后,实际发布的 AgentLit 变体数量 | 实测 |
| 433 个升至 577 个 | 当 Assets/ 下有一个未使用的 Bloom HQ + dirt 配置文件时的 UberPost 变体数量 | 实测 |
| 14 个降至 0 个;1.8 ms 升至 2.4 ms | 游戏过程中首次使用时 GPU 程序的上传次数,冷启动与预热后对比;macOS 播放器中一帧内预热 26 至 27 个 PSO | 实测 |
| 0.007 至 0.033 / 0.29 至 0.42 / 0.89 至 1.66 ms | 单次 1080p 全屏绘制:64 次乘加运算 / 64 次 sin / 64 次 float4 sin,M5 Max;在同一次运行中对比 | 实测 |
| 0.92 至 1.00 / 0.77 至 0.89 | half4 与 float4 sin 耗时之比,PlatformDefault / Unified 精度模型,M5 Max(噪声约 10%) | 实测 |
| 超过 128 个关键字,预留 4 个 | 每个着色器:编译速度会变慢 | 6.3 手册 |
| 2000 / 2450 / 3000 | Geometry / AlphaTest / Transparent 队列 | Cyanilux |
| 64x1x1、8x8x1 | 计算着色器线程组;向上取整分块调度,并进行边界检查 | 6.3 手册,[新增] |
| (1.2, 0.35, 0.03) 与 (4, 1.1, 0.15) | HDR 溶解边缘在 Neutral 色调映射下保持橙色与褪成奶油色的对比 | 实测 |
| 0.2126、0.7152、0.0722 | 线性颜色上的 Rec. 709 亮度 | 6.3 手册 |
质量门槛
- 可测量: lint 和
lint_project_keywords均无错误;目标 API 的ValidateShaders无错误;兼容 SRP Batcher(渲染后读取);探针(阴影亮度低于开放地面亮度的 0.75 倍;溶解阴影在 1 时恢复;Forward 和 Forward+ 中额外光源会改变受光侧;水面法线在岸边比深水处更平缓);功能开关前后的差异;实际发布的变体数在预算内且保持稳定;没有多余的 VolumeProfile;每个 API 的pso_gate均通过;GPU 毫秒数 A/B 对比;计算着色器 GPU 结果与 CPU 一致(最大误差低于 1e-5)。 - 视觉: 已打开对照图集;卡通明暗色带清晰;水面色调变化、平静的岸边、泡沫、运动效果;溶解边缘保持色调;描边仅出现在轮廓上;没有洋红色。
常见错误
| 错误 | 表现 | 修复 |
|---|---|---|
| 没有 DepthOnly 通道 | 强制使用 Depth Priming 时物体消失 | AgentDepthOnlyVertex/Fragment |
| 只在前向通道中裁剪 | 溶解后的物体仍投下完整阴影 | 所有通道共用 AGENT_CLIP |
DepthNormals 返回 n * 0.5 + 0.5 | SSAO 和轮廓错误 | 返回原始法线 |
LIGHT_LOOP_BEGIN 前没有定向光预循环 | 在 Forward+ 下,第二盏定向光无法照亮任何物体 | 先循环 URP_FP_DIRECTIONAL_LIGHTS_COUNT |
_FORWARD_PLUS、SHADOWS_SCREEN、ComputeScreenPos(自 URP 11 起已弃用) | 编译警告,阴影分支无效 | _CLUSTER_LIGHT_LOOP、GetMainLight(shadowCoord)、GetNormalizedScreenSpaceUV(positionCS) |
通过运行时 C# 切换 shader_feature_local(溶解“命中时”) | 玩家版本中缺少变体:显示粉色或使用最接近的匹配项 | 替换材质,或使用 multi_compile_local;lint_project_keywords |
| 用布尔关键字跳过 Shader Graph Alpha Clipping | 所有通道中的整个物体始终进行 Alpha 测试 | 允许材质覆盖(_ALPHATEST_ON 关键字)或使用第二个不透明材质 |
对通过 #if 测试的关键字使用 dynamic_branch 类型覆盖 | 变体数量不变 | 使用 if() 测试,不带阶段后缀声明关键字 |
在带有 _fragment 后缀的关键字上使用 if (KW) | “已知关键字,但未在此通道中声明” | 为所有阶段声明该关键字 |
| 多余的 VolumeProfile 资源(示例、测试) | 为场景未使用的效果生成后处理变体 | 将它们移出 Assets/;使用 PostProcessingAudit |
| 每个 API 共用一个 PSO 集合,或没有经过验证的集合 | 设备上首次使用时出现卡顿 | 按 API 跟踪,对比冷启动与热启动运行 |
| URP 资源上关闭了 Depth Texture | 水变成不透明泡沫;SSAO 仍请求深度时画面被隐藏 | 在每个渲染它的 URP 资源上启用;关闭 SSAO 进行测试 |
| 某个 Renderer 缺少此功能 | 某个质量级别下“效果不起作用” | 在各质量级别运行 ListFeatures |
| 在渲染后注入 | 没有绘制内容,出现后备缓冲区错误 | 渲染后处理之后 |
| HDR 边缘颜色为 4 或更高 | 自发光边缘褪成乳白色 | 设为 1 到 1.5,让 Bloom 承载辉光 |
| 根据指令数判断成本,或只在桌面端减半 | “优化过的”着色器速度并未提升 | 使用 MeasureFullscreenCost;在设备上减半 |
属性位于 UnityPerMaterial 之外 | SRP Batcher 关闭,数值串扰 | 在 HLSLINCLUDE 中使用一个 CBUFFER |
交接
- 接收:来自 scenario-unity-rendering-lighting:各质量级别的 URP 资源、渲染路径、Depth/Opaque Texture、后处理堆栈、色调映射、Default Volume Profile。来自 scenario-unity-vfx:粒子着色器需求(混合模式、软粒子、翻页书);scenario-unity-vfx 负责 VFX Graph、Particle System 和特效预算。来自 scenario-unity-world-building:水面和地形表面。来自 scenario-unity-architecture:驱动材质的游戏玩法代码。
- 交付:向 scenario-unity-vfx 和 scenario-unity-world-building 提供着色器、附有说明文档的材质引用、功能资源。向 scenario-unity-performance 提供 GPU 毫秒数表、变体筛选漏斗、包含设备捕获冷/热证明的
.graphicsstate文件。向 scenario-unity-pipeline-automation 提供变体预算和 CI 的 PSO 门禁。向 scenario-unity-mobile 和 scenario-unity-web 提供 API 限制(GLES 3.1 支持计算着色器,GLES 3.0 和 WebGL 2 均不支持)、需在设备上分析的半精度、移动端 Depth Texture 开销、平铺式 GPU 上的 Alpha 测试开销。 - **资料包:**路径、
ValidateShadersJSON、联系表、探针、筛选漏斗、PSO 运行记录、开销表、已验证和假设。
Unity 6.3 说明
- 仅支持 Render Graph:已移除 Compatibility Mode(仅为移植保留
URP_COMPATIBILITY_MODE,并将在 6.4 中移除);AddBlitPass返回一个 builder(returnBuilder: true);After Rendering 始终位于最终 blit 之后(6.2+)。 _CLUSTER_LIGHT_LOOP取代_FORWARD_PLUS(6.1);软阴影有_LOW/_MEDIUM/_HIGH变体;URP 的Fog.hlsl中,雾声明为dynamic_branch。- Shader Build Settings:
EditorGraphicsSettings.Get/SetShaderBuildSettings(当前生效的设置;构建配置文件可以覆盖)。GraphicsStateCollection仍处于实验阶段;追踪需要开发版播放器;在 Metal 上,CreateGraphicsGraphicsPipelineImpl不存在。Log Shader Compilation 会打印Uploaded shader variant to the GPU driver: ...。 - URP 会在每次构建前将所有覆盖项重新添加到 Default Volume Profile,因此大多数后处理变体族都会始终随构建发布。
SetShaderUserValue(uint)存在于MeshRenderer和SkinnedMeshRenderer上,而非Renderer上;着色器读取unity_RendererUserValue。- Shader Graph 17.3:模板、通过 Unlit 加 Keep Lighting Variants 实现自定义光照;反射 HLSL 节点需要 17.5,Preprocessor Directives 将在 6.6 中推出。WebGPU 从 6.6 起受支持;Built-in RP 从 6.5 起弃用。
参考资料
references/procedures.md:P1 至 P19,可复制的代码及实时测试和结果。references/expert-notes.md:各位专家的原则、观察结果和分歧。references/critique.md:宣布工作完成前使用的评估标准。references/gui-paths.md:在 GUI 中执行相同的流程。references/sources.md:来源、凭据、时间戳、修订历史。scripts/ut_shaders.py:安装、代码检查、变体和 PSO 辅助工具、Shader Graph 读取器和编辑器、像素探针。scripts/AgentKit/Shaders/:ShaderJobs、ShaderLab、RendererFeatureJobs、VariantJobs、PsoJobs、ComputeJobs、GpuCostJobs、ShaderGraphJobs。scripts/Runtime/:AgentFullscreenFeature.cs、AgentPsoProbe.cs(运行时程序集);scripts/Shaders/(模板)。