本节摘要:保存 Shader Graph 时,编辑器把节点图翻译成 HLSL 代码再编译成 GPU 可执行程序。本节带你看懂生成代码的骨架结构,建立"节点 = 函数片段"的映射直觉,并解释为什么改图要重新保存、而改属性值不用。
有人会说:"既然可视化工具就是为了不写代码,为什么还要看代码?"因为这个映射关系是你后面所有疑难杂症的诊断器。图连不出来报错时,错误信息给的是代码行号;性能出问题时,分析器报的是函数名;跨管线移植出问题时,差异藏在管线宏里。不需要会写,只需要能认——这一节的目标就是"认得出"。
保存 1.2 节的图后,展开项目窗口里图资产旁边的小三角,你会看到 Unity 自动生成的材质文件。图资产本身是一个描述节点连接的序列化文件,真正的着色器代码在编译产物里。想看代码,选中图资产后在检查器里能看到对应的着色器引用;更实用的办法是记住这个等价关系,然后直接读下面这段——它是渐变图编译结果的核心片段,做了简化注释:
// 每个顶点执行一次:把模型空间位置变换到裁剪空间 Attributes vert(Attributes input) { VertexDescription desc = VertexDescriptionFunction(input); // Vertex 块的内容 output.positionCS = TransformObjectToHClip(desc.Position); // 变换到裁剪空间 output.uv = input.uv; // UV 原样传下去等插值 return output; } // 每个像素执行一次:算出这个像素的最终颜色 half4 frag(Varyings input) : SV_Target { FragmentDescription desc = FragmentDescriptionFunction(input); // Fragment 块的内容 return desc.BaseColor; // 渐变图里就是 Lerp 的结果 }
看出结构了吗?你在 Shader Graph 里连的所有东西,最终被装进两个函数:VertexDescriptionFunction 与 FragmentDescriptionFunction。你在画布上拖的每个节点,展开后就是函数体里的几行算式。比如渐变图里那个 Lerp,展开后大致是 lerp(A, B, T)——节点名、HLSL 函数名、图形学教材里的公式,三者是同一个东西的三张脸。
编译分四步。第一步是拓扑排序:编辑器从主栈块的每个输入端口往回走,把依赖的节点排成"先算谁后算谁"的顺序,这也解释了为什么图里不能存在循环连线——代码里没法表达"先有结果才有输入"的逻辑。第二步是代码生成:每个节点按模板展开成 HLSL 片段,端口连线就是变量传递。第三步是管线适配:同样的片段被套上 URP 或 HDRP 的宏与辅助函数,生成对应变体。第四步是真正的着色器编译,产物交给驱动程序在 GPU 上运行。
这套流程带来两个实际推论。推论一:改图必须重新保存,因为图变了函数体才变;改属性值不用重新保存,比如把材质面板上的颜色从红改成蓝,那只是给函数传了不同的参数,编译产物不变——这就是 Blackboard 属性设计的意义,第 2 章展开。推论二:图里每个节点至少对应一段真实算式,不存在"免费的节点",某节点声称免费,只是它的算式足够便宜。

有了映射直觉,报错信息就不再是天书。编译错误最常见的三类:语法类(极少见,多半是 Custom Function 里写错了,第 7 章细说)、类型类("cannot implicitly convert from float4 to float2",回到图里检查连线,插一个 Truncate 或 Split 就好)、管线类("undeclared identifier",通常是用了当前管线不支持的节点或块,回到 1.1 的管线检查)。看报错先看类型关键词,再回图里找对应节点,这个回路练十次就熟了。
三类报错各给一个真实样例,见到时能直接对号。语法类:函数未定义、括号不配对,多来自 Custom Function 的签名写错,错误信息带行号。类型类:cannot implicitly convert from float to float4,意思是把一个标量直接连到了四维颜色的端口——回图里给这根线插一个适合的转换节点(比如 Combine 补齐分量)。管线类:undeclared identifier,某个变量或宏在当前管线里不存在,常见于跨管线复制的图——用到 HDRP 专属接口的节点在 URP 里就会这样报。三类错误的处理路径完全不同,先分类再动手,比逐个连线检查快得多。
生成的着色器代码由 Unity 统一管理在库缓存里,你不需要也无法直接编辑它们。有两个衍生知识点:其一,删除图资产时,生成的材质与着色器不会自动消失,需要手动清理,否则项目里会残留死引用;其二,图文件的版本冲突解决后,建议手动触发一次全量重编译(右键图资产选重新导入),确保缓存与文件一致——修图后"看起来改了但效果没变"的灵异现象,一半是这个原因。
可以但不建议常看。旧版本提供查看生成代码的入口,新版把它藏得更深,且生成的代码被宏与条件编译包裹,可读性一般。更实用的替代是理解映射关系:图里的每个节点在代码里有唯一对应的算式片段,报错信息里的变量名与函数名能反向定位到节点。真要逐行研究生成代码的场合(深度性能分析、跨管线排查),再用导出工具把它落成文件慢慢读。
这一节建立的"节点即函数片段"的映射,在 7.2 节会变成实打实的能力:Custom Function 节点要求你按函数签名写 HLSL——那时你会发现,所谓在图里写代码,不过是把你现在"认得出"的函数亲手写出来。1.4 节的读懂,是 7.2 节的写出的一半路程。
到这里,第 1 章的"是什么"已经全部铺垫完。下一章我们进入编辑器内部,把界面五区、Blackboard 属性和主栈块这三件日常工具磨熟——它们是后面所有案例章的操作台面。