想象你走进了一间神奇的后厨。灶台上摆满透明储物格,装着洗好的食材、配好的酱料和各式刀具。你不需要背诵任何烹饪口诀,只要把食材从冷藏柜取出、在案台上切两刀、丢进锅里翻炒,再连一根管子把香气送进摆盘机——一道菜就上桌了。这就是 Unity 的 Shader Graph 可视化着色器编辑器。 传统上,决定 3D 物体表面长什么样的“烹饪配方”叫着色器(Shader),它用 HLSL 代码写成,像一页密密麻麻的菜谱。而 Shader Graph 把这页菜谱变成了一块可拖拽连线的图板:食材(纹理贴图)经过刀具(数学运算节点)处理,调味(颜色与属性)由你随手拨弄,最后摆上出菜台(主节点),一盘材质就做好了。你还会学到怎么配“预制料理包”(SubGraph 子图)、怎么做“分子料理级”的溶解特效,以及怎么调一碗晃动的清汤水面。
对 AI 从业者而言,Shader Graph 是理解“计算图”(Compute Graph)的最佳可视化工坊——你在节点连线上获得的直觉,能直接迁移到神经网络、程序化生成与 AI 渲染管线设计上。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
溶解效果 (Dissolve) 节点图 菲涅尔描边 (Fresnel) 节点图 水面效果 (Water) 节点图 Blackboard 属性面板 + SubGraph 复用
着色器基础
餐厅的后厨是一条流水线:冷藏库取出食材,切配台切丁,炒锅烹饪,摆盘台装盘,最后传菜口上桌。每个 3D 物体的显示过程也遵循同样的流水线:模型顶点坐标从本地空间出发,经过层层矩阵变换到达屏幕,光栅化器把三角形“切”成一个个像素丁,片元着色器决定每个像素的颜色,最终写入屏幕缓冲——后厨的传菜口。
传统上,控制这条“烹饪配比”的配方叫 Shader,它用 HLSL 语言手写,好比一份密麻的菜谱。而 Unity Shader Graph 把这页菜谱变成一块可视化图板:拖入节点、拉动连线,引擎自动生成背后的 HLSL 代码。Shader 大致分两类:顶点着色器决定物体形状怎么变形(比如旗帜摆动),片元着色器决定表面颜色与质感。Shader Graph 的定位正是让你把精力聚焦在片元着色这个“炒锅环节”上,其余流水线环节由 Unity 接管。
理解了这条流水线,你就明白 Shader Graph 中每个节点输出的是什么“食材状态”。接下来的所有节点操作,都是在为这盘“像素菜”增添风味。
- 本地坐标模型文件自带的原始顶点位置,类似食材刚从冷藏库取出时的状态。
- 模型变换把模型从自己的坐标系摆到世界场景的指定位置,相当于把食材放到对应的案台上。
- 视图变换把世界坐标系切换到摄像机视角,类似后厨的监控摄像头对准案台。
- 投影变换透视投影产生近大远小效果,相当于镜头把三维画面压缩成二维照片。
节点类型体系
好厨师靠的是十八般兵器:切丁的、拍蒜的、磨粉的、称重的,各有专职。Shader Graph 的节点库也是这样一套专业工具架,按用途分四大家族。
第一类是输入节点,它们是“食材本身”——纹理采样节点根据 UV 坐标从贴图中取出颜色,相当于从食材柜里拿菜;属性节点则读取你在黑板上定义的参数。第二类是数学运算节点,它们是“刀工手法”:Add 是把两种食材拼盘,Multiply 是让某种调料成倍生效,Lerp(线性插值)则是把两份食材按比例混合进同一口锅。第三类是纹理采样节点,相当于“香料柜”,任何需要读取贴图细节的地方都要经过它。第四类是程序化生成节点,比如噪声(Noise)节点,不依赖任何贴图就能生成随机波浪纹理——这就像自动料理机,撒盐的颗粒分布都不用你动手。
掌握这四类节点,就像厨师记熟了刀工与火候。你不需要记每一个节点,只需要知道“这件武器属于哪一类、解决什么问题”,就足以在需要时快速找到它。
- 起始值插值的起点,对应 Shader Graph 的输入 A。
- 变化量B 减去 A 得到从 A 到 B 的“距离”,代表需要混合的总量。
- 按比例缩放乘以 T,按百分比截取这段距离,T 越大越靠近 B。
- 回到起点加 A 把缩放后的偏移拼回原始基准,最终得到 A 与 B 之间的某个中间值。
属性与黑板
后厨里最顺手的布局,是把常用食材挂在眼前。Shader Graph 的 Blackboard(黑板)面板就是这张“冰箱门材料清单”。你可以在黑板上定义属性,比如一个 Float 属性叫“流速”,一个 Color 属性叫“液体颜色”,然后把它们拖进图里接到任意节点上。
属性定义的本质,是让你把“需要在外部调整的参数”从复杂的节点连线中抽离出来。类型选择很关键:Float 是量杯(单个数值)、Int 是整粒盐(整数)、Color 是颜料管(四通道颜色)、Texture2D 是食材袋(贴图)、Vector2/3/4 是复合调料盒(多维向量)。更重要的概念是“暴露”:只有勾选了 Exposed 的属性才会出现在材质 Inspector 面板上,相当于把调料罐放在顾客能看见的前台;而不暴露的属性则是后厨私藏,只有你改图时才能动。
冰箱里放哪些食材、放在哪层,决定了这道菜能多快做出来、多灵活调整口味。合理规划 Blackboard 上的属性,是 Shader Graph 设计的第一课。
- 纹理色从贴图上采到的原始颜色,相当于食材本来的色泽。
- 属性色黑板中定义的 Color 属性值,相当于你调好的酱汁。
- 逐通道相乘RGB 三个通道分别相乘,红色通道乘红色通道、绿色乘绿色,最终得到调制后的颜色。
主节点输出
所有处理完的食材,最终都要端上摆盘台。Shader Graph 的摆盘台是主节点(Master Node),你所有连线的终点都汇聚到它的各个端口,由它决定这盘菜的最终外观与“上桌规格”。
PBR 主节点是最常用的摆盘台,它模拟真实物理光照。Base Color 端口接纹理采样或颜色属性,决定菜的主色;Metallic 端口控制金属度,0 是陶器、1 是黄金,中间值则是合金质感;Smoothness 端口控制光滑度,影响高光的锐利程度,抛光不锈钢与哑光塑料的差别就在这里;Normal 端口接法线贴图,让表面在不多加模型面的前提下产生凹凸感。如果做水面、玻璃等半透明材质,还要关心 Alpha 端口与 Surface Type 的透明混合设置。
摆盘台决定了菜品给人的第一印象——金属拉丝、哑光陶瓷还是玻璃质感,全看这一桌“端口师傅”如何配合。调通主节点,材质就成功了一半。
- 金属度材质面板上的 Metallic 值,范围 0 到 1。
- 非金属占比1 减 M 得到非金属部分的比例,决定基础色显示多少。
- 基础色贡献基础色乘以非金属占比,得到你看到的漫反射颜色部分。
- 金属色贡献金属反射色乘以 M,与基础色叠加得到最终表面颜色。
子图复用机制
餐厅做畅销菜时,绝不会每次炒菜都从切葱剥蒜开始。提前把“葱姜蒜爆香”封装成一个料理包,用时拆袋下锅。SubGraph(子图)就是 Shader Graph 的预制菜料理包。
创建方法很简单:在 Project 窗口右键 Create → Shader Graph → Sub Graph,就会打开一个只有输入输出口的子图编辑区。你可以在里面搭建任意节点逻辑,然后通过创建端口(Graph Input/Graph Output)把这个子图封装成一个可被外部引用的自定义节点。端口配置就像料理包上的说明标签:输入端口注明需要哪些参数,输出端口标明会得到什么结果。团队共享时,把 SubGraph 文件放进版本库,美术和程序拉下来就能直接使用,整个团队就像共用同一个中央厨房的配方库。
SubGraph 让复杂效果变成模块化积木,改动一处封装,所有使用它的材质同步更新,这是手写 HLSL 时代难以想象的团队协作体验。
- 输入传参最上游数据 x 进入第一个子图,相当于食材进入料理包的第一步加工。
- 一级加工子图 f 对 x 做处理,输出中间结果。
- 二级加工外层子图 g 再处理 f 的结果,最终输出成品,这就是多层复用与嵌套。
溶解效果实战
怪猎、魂系、二次元 ARPG 里常见这样的仪式感:角色死亡时像被能量腐蚀一般一点点消失,边缘还闪着光。这道“会消失的菜”用 Shader Graph 只需五个节点就能搭出来。
核心思路是“用噪声决定哪些像素被裁掉”。传统做法是一行行写 HLSL 的 clip 语句,而 Shader Graph 用四条连线解决:先用 Noise 节点生成一张程序化噪声(相当于给食材撒盐,保证腐蚀边缘不规则);再用 Step 节点把噪声按阈值切分成 0 和 1(超过阈值的区域判定为“已溶解”);接入 PBR 主节点的 Alpha Clip Threshold 端口,把这些区域彻底裁掉,就得到了腐蚀空洞;最后想加边缘光,就把噪声值与阈值取差,用 Lerp 混合出高亮颜色加到 Emission 上。
一盘“会消失的菜”,靠的不是魔法,而是噪声、阈值、裁剪三位师傅的分工协作。学会了这套配方,你还可以把 Step 换成 Smoothstep,让边缘带渐变过渡,气质立刻从“切割”变成“融化”。
- 输入噪声Noise 节点输出的连续随机值,范围 0 到 1,模拟腐蚀的随机分布。
- 阈值判断将噪声与 threshold 参数比较,分界处就是溶解边缘。
- 离散输出比较结果映射为 1 或 0,作为主节点 Alpha Clip 的裁剪信号。
水面效果实战
最后做一道“会晃动的清汤凝胶”:看起来像果冻,表面却有水的波光与倒影。Shader Graph 做水面效果,是可视化编程最有成就感的场景之一。
水面的四个关键配方:第一,UV 流动——让 UV 坐标随时间朝某个方向偏移,使纹理不断“流动”,产生水流方向感,相当于轻轻晃动汤碗。第二,法线扰动——采样两张不同平铺密度的法线贴图,用 Lerp 混合并归一化,让波浪细节更丰富,这是水波细腻度的来源。第三,菲涅尔效应——视线越贴近水面,反射越强。用 Fresnel Effect 节点接收法线与视线方向,输出一个边缘亮、中心暗的系数,作为反射强度的混合权重。第四,深度混合——用 Scene Depth 节点采样背后物体的深度,与水面自身深度做差,实现近岸浅水区颜色渐变。
水面效果不再是美术玄学,而被 UV、法线、菲涅尔、深度四个配方参数精确掌控。你调出的每一碗“清汤凝胶”,都是 Shader Graph 计算图思想的完美应用。
- 夹角余弦视线方向与表面法线夹角的余弦值,垂直看时为 1,斜看时趋近 0。
- 边缘增强因子1 减去余弦后取五次方,让掠射角处的反射快速增强,呈现平滑过渡。
- 基础反射率F0 是垂直观察时的基础反射强度,水的 F0 约为 0.02,非常低。
- 最终反射强度基础反射率与增强项的叠加,得到当前视角下实际反射强度 F。
AI 应用场景
游戏美术即时调参
阶段:效果迭代与调参
解决的问题:传统工作流中,程序改一行 Shader 代码,美术要等待重新编译才能看到效果,沟通成本极高。Shader Graph 让美术直接在图板上操作,属性暴露后还可以在材质面板实时拖动滑块,所见即所得。
具体案例:某 RPG 团队的场景美术制作岩浆材质,在 Shader Graph 中暴露了“流速”、“发光强度”、“颜色偏移”三个 Float 属性,不写一行代码,一上午完成五个风格版本迭代,再把最终版交给程序接入游戏状态。
独立团队的 SubGraph 资产库
阶段:资产复用与管理
解决的问题:重复的节点逻辑在多项目间手工复制粘贴,修改一处要同步十几个材质球,维护成本极高。把通用逻辑封装成 SubGraph,修改一处自动同步所有引用材质的版本更新。
具体案例:一个三人休闲游戏团队,把脏迹、裂痕、水渍、锈蚀四类通用贴图逻辑封装成 SubGraph 资产库放入 Unity Package,新项目直接拉取引用。美术只需换贴图和调参数,就能产出风格统一的世界环境。
AI 辅助节点连接推荐
阶段:方案推荐与生成
解决的问题:新手面对上百个节点不知道从何下手,AI 可以分析自然语言描述并推荐节点连接方案,把“我要角色消失时带能量边缘”翻译成具体的 Step+Lerp+Noise+Alpha Clip 图建议。
具体案例:某 AI-for-Creative 工具团队在 Unity 编辑器侧构建了节点推荐服务:开发者用中文描述效果目标,大模型输出 Shader Graph 节点序列与连线建议,开发者一键按图连线,几分钟完成传统方式半小时的效果原型。
Python 演示
# -*- coding: utf-8 -*-"""Mini Shader-Graph style compute graph in Python用于演示 Shader Graph 的节点计算图求值思想""" import random class Node(object): """图中节点基类: 通过 inputs 连接上游节点""" def __init__(self, name): self.name = name self.inputs = [] def connect(self, upstream): """将 upstream 的输出接入当前节点的输入""" self.inputs.append(upstream) return self def value(self): raise NotImplementedError class ConstNode(Node): """常量节点: 相当于 Shader Graph 的 Float / Color 属性""" def __init__(self, name, val): Node.__init__(self, name) self.val = val def value(self): return self.val class NoiseNode(Node): """噪声节点: 输出 [0,1) 随机噪声, 模拟溶解效果的噪声纹理""" def __init__(self, name, seed): Node.__init__(self, name) random.seed(seed) def value(self): return random.random() class StepNode(Node): """Step 节点: 按阈值把连续值离散为 0 或 1""" def __init__(self, name): Node.__init__(self, name) def value(self): edge = self.inputs[0].value() sample = self.inputs[1].value() return 1.0 if sample >= edge else 0.0 class LerpNode(Node): """Lerp 节点: 按系数 t 在两个输入间线性插值""" def __init__(self, name): Node.__init__(self, name) def value(self): a = self.inputs[0].value() b = self.inputs[1].value() t = self.inputs[2].value() return a + (b - a) * t def main(): # 构建溶解边缘发光的计算图: noise -> step -> lerp noise = NoiseNode("noise", seed=42) threshold = ConstNode("threshold", 0.62) step = StepNode("step") step.connect(threshold).connect(noise) dark = ConstNode("dark", 0.15) bright = ConstNode("bright", 0.95) glow = LerpNode("edgeGlow") glow.connect(dark).connect(bright).connect(step) brightness = glow.value() print("dissolve brightness = {:.3f}".format(brightness)) print("step output = {:.0f}".format(step.value())) if __name__ == "__main__": main()
class Node(object):定义节点基类,相当于 Shader Graph 中所有节点的公共模板self.inputs = []每个节点记录上游接入列表,对应图板上的输入连线插口def connect(self, upstream):把上游节点接入当前节点输入,返回 self 以便链式连续class NoiseNode(Node):模拟噪声节点,用随机数生成[0,1)的分布class StepNode(Node):模拟 Step 阈值节点,把连续值离散成 0/1 开关step.connect(threshold).connect(noise)先接阈值再接噪声,对应图板上两条连线glow.connect(dark).connect(bright).connect(step)Lerp 三输入依次接好,构成完整图brightness = glow.value()从下游节点触发递归求值,所有上游沿连线计算return a + (b - a) * tLerp 节点核心插值公式,与 Shader Graph 中 Lerp 节点逻辑一致
常见误区
误区“Shader Graph 都是拖拖拽拽,不用学向量、矩阵和三角函数,会连线就行。”
正解每一个节点背后都是数学运算。Lerp 是线性插值,Noise 是随机函数,Fresnel 是余弦的幂运算。不懂数学,遇到“边缘怎么亮”“波纹怎么密”就只能瞎猜参数。数学决定你调参时的方向感,可视化只帮你省去语法障碍。
为何常见:节点封装了底层公式,界面看起来像积木搭建,让新手误以为没有公式存在。实际上节点参数名称起的越“友好”,背后数学被伪装得越深。
误区“反正连线不花钱,把水面、溶解、随机条纹全做进一个 Shader Graph,用开关切换,多方便。”
正解单图巨大必然导致连线缠绕、性能开销上升、维护噩梦。正确做法是抽离 SubGraph,把通用逻辑封装成料理包。一个材质只做一件事,复杂效果通过子图嵌套组合,性能和可读性双收。
为何常见:新手做出第一个效果后有“把它做大”的冲动,却忽略了图作为模块化资产的本质——和写代码一样,高内聚低耦合同样适用。
误区“把所有中间值都暴露到材质面板,方便策划和美术随时调。”
正解暴露属性会同步到材质面板并占用额外开销,暴露太多不但让调参者眼花缭乱,还容易把关键参数改坏。应该只暴露面向用户的关键调节项(如流速、颜色、阈值),中间传参用内部常量或私有属性锁定,保持面板干净。
为何常见:参数多会带来“可控感”,但忽略了一个事实:控制面板和使用者的认知负担成正比,过多的暴露是技术债。
误区“我加个 Alpha Clip Threshold 节点再连个数字进去,怎么没出溶解效果?Shader Graph 是不是有问题?”
正解溶解的核心是“不规则的裁剪”,需要 Noise 生成随机分布、Step 按阈值切分、再送到 Alpha Clip。单纯接一个固定数字,只会让物体整个消失。溶解是一个组合配方,不是单一节点——这正是计算图组合思维的体现。
为何常见:教程视频最终只展示成品连接好的图,观众把功劳归于某一个“看起来最关键的节点”,忽略了背后的辅助组合。
面试题
Q1. Shader Graph 最核心的定位是什么?
- A.让美术不写代码也能制作材质← 正确答案
- B.替代所有 C# 游戏逻辑
- C.提高模型面数上限
- D.自动生成游戏场景
答案:A。Shader Graph 是可视化着色器编辑工具,核心价值是让美术与初级开发者通过节点连线制作材质,无需手写 HLSL 代码。它不处理游戏逻辑,也不提升模型精度,更不是场景生成器。就像后厨图板让不会写菜谱的人也能配出一盘菜。
Q2. Shader Graph 的 Blackboard 面板主要用于?
- A.调试 GPU 性能
- B.定义与存储属性参数← 正确答案
- C.搜索节点库
- D.查看渲染统计
答案:B。Blackboard(黑板)是属性定义面板,用来创建 Float、Color、Texture2D 等参数,类似冰箱门上的食材清单。它不是调试工具,也不是节点搜索框或统计面板。
Q3. PBR 主节点中,控制“金属质感强度”的端口是哪个?
- A.Base Color
- B.Metallic← 正确答案
- C.Normal
- D.Emission
答案:B。Metallic 端口控制金属度,0 为绝缘体、1 为金属导体。Base Color 决定基础色,Normal 控制法线凹凸,Emission 控制自发光。就像摆盘台上的师傅各司其职。
Q4. Lerp(A, B, T) 在 T 等于 1 时输出什么?
- A.完全输出 A
- B.完全输出 B← 正确答案
- C.输出 A 和 B 的平均值
- D.输出 A 减 B
答案:B。Lerp 插值公式为 A + (B−A)×T。当 T=1 时,(B−A)×1 把结果推到 B,所以完全输出 B。T=0 时输出 A,T=0.5 才是平均值。就像混合汤汁时比例旋钮旋到底就全是一种味道。
Q5. SubGraph 子图的核心价值是什么?
- A.加快光照烘焙速度
- B.封装可复用的节点逻辑← 正确答案
- C.替代材质球
- D.压缩纹理内存占用
答案:B。SubGraph 把常用节点逻辑封装成预制料理包,便于复用、共享和维护。它不参与光照烘焙,也不能替代材质球或压缩纹理,只解决逻辑复用的组织问题。
Q6. 下列哪组节点类型都属于 Shader Graph 节点体系?
- A.数学运算、纹理采样、程序化生成← 正确答案
- B.数据库查询、网络请求、日志打印
- C.碰撞检测、动画混合、音频播放
- D.文件读写、内存分配、线程调度
答案:A。Shader Graph 节点分为输入、数学运算、纹理采样、程序化生成等类别,与渲染相关。数据库、网络、碰撞、音频、文件系统都不是着色器节点的职责范围,正如后厨不会出现修车工具。
Q7. 以下哪些端口属于 PBR 主节点?
- A.Metallic 金属度← 正确答案
- B.Smoothness 光滑度← 正确答案
- C.Transparent 透明度
- D.Normal 法线← 正确答案
答案:ABD。PBR 主节点包含 Base Color、Metallic、Smoothness、Normal、Emission、Alpha 等端口,覆盖物理光照所需的材质属性。Transparent 不是端口名称,而是 Surface Type 的一项选项,用于开启透明混合模式。
Q8. 关于“暴露属性”(Exposed)的说法,正确的是?
- A.暴露后可在材质面板调节← 正确答案
- B.暴露越多渲染性能越好
- C.关键参数暴露便于快捷调优← 正确答案
- D.所有节点输入都必须暴露
答案:AC。Exposed 属性出现在材质 Inspector 面板,方便调参。但暴露数量与性能不成正比,反而会增加管理负担;也不是所有节点输入都需暴露,中间值应该内部锁定。这如同前台摆太多调料罐,客人反而无从下手。
Q9. 溶解效果中 Step 节点起什么作用?
- A.平滑噪声边缘
- B.按阈值生成离散裁剪信号← 正确答案
- C.旋转 UV 方向
- D.计算法线光照
答案:B。Step 将噪声值与阈值比较,输出 0 或 1,作为 Alpha Clip 的裁剪信号。它不会平滑边缘(那是 Smoothstep 的事),也不处理 UV 旋转或光照计算,只做冷静的阈值判定。
Q10. 关于菲涅尔效应的说法,正确的有哪些?
- A.视线越贴近表面反射越强← 正确答案
- B.垂直观察水面看到的水底更清楚← 正确答案
- C.折射率随温度线性变化
- D.斜视玻璃时镜面感更明显← 正确答案
答案:ABD。菲涅尔效应描述掠射角越大反射越强的光学现象:斜视水面或玻璃时反射明显、看不清内部,垂直观察时反射弱、能看到内部。它与折射率温度变化无关。这就是为什么你站在湖边斜看对岸山影,比低头看脚边水面更清晰。