1. 项目概述:为什么选择ShaderGraph做火焰?
在游戏和实时渲染的世界里,火焰效果一直是个让人又爱又恨的课题。爱的是它那无与伦比的视觉冲击力和氛围塑造能力,恨的是传统Shader代码的门槛,让很多美术和初级开发者望而却步。过去,想要一个会动的、有体积感的火焰,你得去啃那些晦涩的HLSL或CG代码,跟各种噪声函数、时间变量和混合模式较劲。但现在,Unity的ShaderGraph彻底改变了这个局面。
ShaderGraph是一个基于节点的可视化着色器编辑器,它把复杂的着色器编程变成了“连连看”游戏。你不需要写一行代码,只需要通过连接不同的节点,就能创造出从简单到复杂的各种材质效果。这对于实现像火焰这样动态、有机的效果来说,简直是天作之合。你可以实时看到每一次调整对最终效果的影响,迭代速度飞快。
这个教程的目标非常明确:我们不依赖任何第三方插件或资产,纯粹使用ShaderGraph内置节点,从一张白纸开始,一步步搭建出一个完整的、会动态摇曳的火焰效果。我会把每个节点的作用、参数设置的逻辑、以及为什么要这样连接都讲清楚。最终,你不仅能得到一个可以直接用在项目里的火苗Shader,更能彻底理解其背后的原理,做到举一反三,未来自己创作烟雾、能量盾、魔法轨迹等效果都不在话下。
2. 核心思路拆解:火焰的视觉构成与实现逻辑
在动手连接节点之前,我们必须先想清楚:一个看起来真实的火焰,到底是由哪些视觉元素构成的?我们不能只想着“做一个火焰”,而要把这个目标拆解成ShaderGraph能够处理的几个核心层。
2.1 火焰的视觉层次分析
仔细观察真实的火焰,你会发现它并非一个均质的物体。从内到外,大致可以分为三个层次:
- 核心高温区:火焰最内部、温度最高的部分。这里通常呈现亮白色或明黄色,是火焰最“实”的部分,也是主要的光源。
- 主体燃烧区:这是火焰的主要体积部分,颜色从内到外由黄渐变为橙红。这个区域具有复杂、不断变化的不规则形状,是动态效果表现的关键。
- 外围辉光与烟雾:火焰边缘与空气接触的部分,颜色较暗(深红、暗橙),并伴有半透明的辉光和若隐若现的上升烟雾,这层赋予了火焰的“柔软”感和体积感。
在Shader中,我们无法真正模拟物理燃烧,而是通过模拟这些视觉层次来“欺骗”眼睛。我们的策略是:用噪声纹理来模拟火焰形状的不规则性和动态变化,用颜色渐变来区分温度区域,用透明度来塑造体积和边缘虚化效果。
2.2 ShaderGraph实现路径规划
基于以上分析,我们的ShaderGraph构建路径就清晰了:
- 形状基础(Mask):首先,我们需要一个基础的火焰形状。一个简单的锥形(通过UV坐标的V通道渐变获得)可以作为起点,但这太规整了。我们需要用噪声(Noise)来扭曲这个锥形的边缘,使其变得不规则、有机。
- 动态变化(Animation):火焰是活的,它在跳动、摇曳。我们需要让噪声“动”起来。通过将时间(Time)节点与噪声的UV输入相结合,让噪声图案随时间滚动和演变,从而驱动火焰形状的动态变化。
- 颜色映射(Color Ramp):有了动态的形状Mask(一个在0到1之间变化的值),我们需要将它映射到火焰的颜色上。内部(值接近1)是亮白色/黄色,中部是橙色,外部(值接近0)是暗红色甚至透明。这里会用到
Sample Gradient节点。 - 边缘处理与辉光(Edge & Glow):为了让火焰看起来不是生硬的剪纸,我们需要处理边缘。通常会让火焰的底部更实、顶部更虚,并且在外围添加一层半透明的辉光。这可以通过对Mask进行幂运算(Power)或使用菲涅尔(Fresnel)效果的变体来实现。
整个流程可以概括为:“基础形状 + 动态噪声” 生成灰度Mask -> “Mask + 时间” 驱动动态 -> “Mask通过渐变映射为颜色” -> “对Mask进行后期处理以优化边缘”。接下来,我们就进入ShaderGraph,将这些思路转化为具体的节点网络。
3. 核心节点图详解与分步搭建
现在打开Unity,创建一个新的ShaderGraph(URP或Built-in管线均可,节点名称可能略有差异,但核心逻辑相通)。我们将按照流程,分模块搭建这个火焰效果。
3.1 创建基础形状与噪声扭曲
首先,我们需要一个基础的火焰形状。最常用的方法是利用纹理坐标(UV)的V方向(从下到上)创建一个渐变。
- 创建一个
Texture Coordinate节点,获取模型的UV。 - 创建一个
Split节点,将UV的U和V通道分开。我们主要需要V通道。 - 创建一个
One Minus节点,连接V通道。因为通常UV的V坐标0在底部,1在顶部。对于火焰,我们希望底部值大(白色,代表火焰根部),顶部值小(黑色,代表火焰尖端)。One Minus节点将进行 (1 - V) 的计算,实现这个反转。 - 此时,你得到一个从下到上由白变黑的渐变条。但这只是一个平滑的三角形。我们需要让它变得不规则。拖入一个
Simple Noise或Gradient Noise节点。Gradient Noise(梯度噪声)产生的噪声图案更柔和、有机,更适合火焰。 - 噪声节点需要UV输入。我们不能直接使用原始UV,否则噪声会贴在模型表面静止不动。我们将
Split后的U和V通道与一个Time节点输出相加后再输入给噪声。具体操作:- 创建
Time节点。 - 创建两个
Add节点。 - 将
Split的 U 通道与Time的Time输出连接到一个Add节点。这将使噪声在水平方向滚动。 - 将
Split的 V 通道与Time的Time输出连接到另一个Add节点。为了使垂直方向的滚动与水平方向不同,产生更自然的变化,可以在连接前将Time乘以一个系数(如0.7),使用Multiply节点。 - 将两个
Add节点的输出,通过一个Vector 2节点组合起来,作为噪声的UV输入。
- 创建
- 调整噪声的
Scale(缩放)参数,比如设为5。这个值越大,噪声细节越多,火焰的“火苗”越细碎;值越小,火焰形状越整体、平滑。初次可以设置为3-8之间尝试。 - 现在,我们有了一个动态的噪声图。接下来,用它来扭曲基础形状。创建一个
Add节点,将第3步得到的One Minus V与第5步得到的Noise输出相加。噪声值有正有负,相加操作会使基础形状的边界发生随机的凹凸变化。 - 此时相加的结果可能超出[0,1]范围,我们需要用
Clamp节点或Saturate节点将其限制回[0,1]。Saturate节点更常用,它就是 clamp 到 0 和 1。
注意:噪声的强度决定了扭曲的幅度。如果你觉得火焰形状太破碎或太规整,可以先用一个
Multiply节点将噪声输出乘以一个强度系数(如0.3),然后再与基础形状相加。这样能更方便地控制噪声的影响程度。
至此,我们得到了一个动态的、不规则的火焰形状遮罩(Mask),我们将其输出命名为Fire Mask。这个值在火焰“体内”接近1,在“体外”接近0。
3.2 构建动态颜色与透明度
有了灰度Mask,上色就简单了。火焰的颜色变化本质上是根据Mask的强度(可理解为“温度”或“密度”)来映射的。
- 创建一个
Sample Gradient节点。这是一个功能强大的节点,允许你定义一个从0到1的渐变条,并依据输入值输出对应的颜色。 - 将上一步得到的
Fire Mask连接到Sample Gradient的Time输入端口(注意:此处的Time不是时间,而是采样位置,是一个命名上的小坑)。 - 双击
Sample Gradient节点,编辑渐变条。这是塑造火焰视觉风格的关键一步:- 最右侧(1的位置):代表火焰最核心、最热的部分。设置为亮黄色(RGB约 255, 255, 200)甚至白色(255, 255, 255)。Alpha值设为1(完全不透明)。
- 中间偏右(0.6-0.8的位置):代表火焰主体。设置为橙色(RGB约 255, 150, 50)。
- 中间(0.3-0.5的位置):代表火焰外围。设置为深红色(RGB约 150, 50, 0)。
- 最左侧(0的位置):代表火焰以外或最稀薄的部分。设置为纯黑色(0, 0, 0),并且将Alpha值设为0(完全透明)。这是保证火焰有清晰边界的关键。
- 在深红色到黑色之间,可以设置一个很短的过渡,Alpha从半透明降到0。
Sample Gradient节点的Color输出就是我们最终需要的火焰颜色。将其连接到主节点(Master Node或Fragment Node)的Base Color上。
但是,直接这样连接,你会发现火焰没有透明的边缘。这是因为我们只定义了颜色,透明度(Alpha)信息来自渐变条的Alpha通道,并且需要正确传递给渲染管线。 5. 将Sample Gradient节点的Alpha输出,连接到主节点的Alpha输入端口。 6. 最关键的一步:在ShaderGraph的Graph Inspector中,找到Graph Settings,将Surface Type设置为Transparent,并将Blend Mode设置为Alpha(对于大多数火焰效果,Alpha混合已足够,如果你想火焰叠加部分更亮,可以尝试Additive模式)。不进行这一步,透明度将不会生效。
现在,你应该能看到一个具有基本颜色渐变和透明边缘的静态火焰了。但它还不会动,因为我们的Mask是动态的,颜色映射依赖于Mask。
3.3 优化形状与添加细节扰动
基础的动态火焰已经成型,但可能看起来还有点单薄或不够“跳跃”。我们可以通过添加更多层次的噪声来增加细节。
- 二次噪声扰动:复制一份之前的噪声生成链路(Time加UV,连接给一个新的
Gradient Noise节点),但这次使用不同的参数。例如,将Scale设为更大的值(如15),代表更细小的高频细节。再将Time乘以另一个系数(如1.3),使其滚动速度与第一层噪声不同。 - 将这层高频噪声乘以一个很小的系数(如0.15),然后与之前得到的
Fire Mask再次相加。这相当于在火焰的大形态上,叠加了一层细微的、快速跳动的“火苗”细节,让火焰看起来更生动。 - 形状塑形:我们最初的
One Minus V是一个线性渐变,这导致火焰从底到顶是均匀衰减的。真实的火焰底部通常更“结实”,顶部更“飘散”。我们可以通过一个Power(幂)节点来重塑这个渐变。将One Minus V连接到一个Power节点,设置Power参数为大于1的值(如1.5或2)。这意味着低值区域(顶部)会被压得更低,而高值区域(底部)相对保持较高,从而让火焰底部更宽更实,顶部更尖更透。 - 顶部消散:为了让火焰顶端更自然地消失,可以创建一个基于V坐标的顶部遮罩。用
Split得到的原始 V 通道(未经One Minus),连接到一个Power节点(指数设为3-5),这会得到一个从底部0到顶部1急剧变化的遮罩。然后用一个Multiply节点,将这个顶部遮罩与最终的Fire Mask相乘。这样,越靠近顶部,Mask值被乘得越小,火焰就逐渐消散了。
经过这些优化,你的火焰Mask会更具表现力。记得每次调整后,都要用Saturate节点确保值在有效范围内。
3.4 核心节点图汇总与参数参考
将以上所有步骤整合,你的ShaderGraph节点图核心部分应包含以下关键链路:
[UV] -> [Split] -> (V通道)->[One Minus]->[Power(2)] -> [Add] <- [Gradient Noise(Scale:5, 动态UV)] -> [Saturate] -> [Fire Mask A] [Fire Mask A] -> [Add] <- [Gradient Noise(Scale:15, 动态UV)*0.15] //添加细节 -> [Multiply] <- [顶部消散遮罩] //顶部淡化 -> [Saturate] -> [Final Fire Mask] [Final Fire Mask] -> [Sample Gradient] -> (Color)->[Master/Base Color] -> (Alpha)->[Master/Alpha]关键参数调节心得:
- 噪声Scale:第一层主噪声建议3-8,负责大形态;第二层细节噪声建议10-20,负责小跳动。
- 噪声强度:与基础形状相加前,用Multiply节点控制,主噪声强度0.2-0.5,细节噪声强度0.1-0.2。
- 时间系数:给噪声UV的Time乘以不同系数(如1.0和1.3),让多层噪声运动不同步,避免循环感。
- 渐变条(Gradient):这是艺术控制的核心。多观察参考图,反复调试颜色和Alpha控制点的位置。亮色区域窄一点,火焰显得更热、更集中;暗色过渡区长一点,火焰显得更柔和、体积感更强。
4. 材质与渲染设置实战要点
Shader写好了,但要让它在场景里正确显示,还需要配置材质和考虑渲染环境。
4.1 材质球关键参数配置
将ShaderGraph保存并生成材质球后,有几个参数需要关注:
- 渲染队列(Render Queue):对于透明物体,正确的渲染顺序至关重要。火焰通常应该在不透明物体之后、在其他透明物体(如UI)之前渲染。你可以在材质的Inspector面板中,手动将渲染队列设置为
Transparent(值为3000)。如果你的火焰需要出现在某些透明物体之后,可能需要进一步调整。 - 双面渲染(Double Sided):通常需要勾选。因为火焰是体积效果,从背面看也应该可见。
- 深度写入(ZWrite):对于标准的Alpha混合透明材质,通常关闭深度写入(ZWrite Off)。这是因为透明物体需要与后面的物体进行混合,如果写了深度,可能会错误地遮挡后面本应可见的透明片段。但在某些复杂的透明物体交错情况下,可能会产生排序问题,需要根据实际情况调整。
- 深度测试(ZTest):通常保持为
LEqual(小于等于深度则渲染),确保火焰不会被它后面的物体遮挡。
4.2 应用于模型与粒子系统
这个火焰Shader可以应用于多种载体:
- 面片(Quad):最常用的方式。创建一个简单的Quad,将材质赋给它。调整Quad的缩放(Scale),可以控制火焰的大小。让Quad始终面向摄像机(Billboarding),火焰看起来就是立体的。这可以通过一个简单的脚本实现,或者在Shader中使用
Transform节点将顶点位置转换到摄像机空间来实现广告牌效果(这属于进阶技巧)。 - 自定义Mesh:你可以建模一个粗略的火焰形状的Mesh(如多个面片组成的锥形簇),应用此材质,效果会更立体。
- 粒子系统(Particle System):这是产生大量动态火焰(如篝火、火把群)的最佳方式。在粒子系统的
Renderer模块中,将材质指定为我们创建的火焰材质。然后通过粒子的Size over Lifetime、Color over Lifetime等模块,可以控制单个火焰粒子的生命周期变化,与Shader内部的动态结合,能产生极其丰富的结果。
实操心得:在粒子系统中使用此Shader时,建议将Shader中基于世界空间或模型UV的噪声,改为基于粒子UV或自定义顶点流。否则所有粒子会显示完全相同的噪声图案,看起来不自然。更高级的做法是,将
Time的偏移量与粒子的Particle ID或Random Seed关联,让每个粒子的火焰动态相位不同。
4.3 场景光照与后期处理适配
火焰是自发光体。我们的Shader目前只是模拟了颜色,并没有真正发光。
- 发光模拟:在URP管线下,可以将颜色输出也连接到
Emission端口,并调整Emission强度。这样在场景中,火焰就能作为光源影响周围物体(需启用后处理中的Bloom效果才能看到光晕)。 - 不受光照影响:火焰通常不需要场景实时光照的漫反射、高光计算。确保在ShaderGraph的
Master Node设置中,将Material下的Surface Options中的Receive Shadows关闭,或者直接使用Unlit类型的Master Node(如果不需要任何光照交互)。 - 与后处理协作:Bloom(泛光)是增强火焰视觉效果的神器。确保你的URP或HDRP渲染管线配置中启用了Bloom后处理效果。调整Bloom的阈值(Threshold)和强度(Intensity),让火焰的亮部能产生漂亮的辉光溢出效果,大大增强真实感。
5. 性能优化与常见问题排查
一个效果再好,如果性能开销大或者问题频出,也无法在项目中使用。以下是针对这个火焰Shader的优化和排错指南。
5.1 性能优化要点
ShaderGraph节点虽方便,但不当使用也会带来性能负担。
- 噪声节点是性能热点:
Gradient Noise比Simple Noise计算量稍大,但效果更好。在移动平台或需要大量实例化的场合,可以考虑:- 减少噪声层数。优先保证一层主噪声的动态,细节层可以视情况舍去。
- 使用
Simple Noise代替Gradient Noise。 - 考虑使用一张预先计算好的噪声纹理(
Texture 2D Asset节点采样)来代替程序化噪声。虽然会牺牲一些动态变化的自由度,但采样纹理的性能开销通常是稳定且较低的。你可以准备一套多帧噪声图序列,通过时间进行采样动画。
- 控制精度:在ShaderGraph的
Graph Precision设置中,对于移动端,可以尝试使用Half精度代替Float精度,能在一些GPU上提升性能,但需注意可能带来的精度损失和视觉瑕疵。 - 减少透明叠加:Alpha混合透明渲染对带宽和Overdraw非常敏感。尽量避免让大量火焰面片在屏幕上深度重叠。在粒子系统中,合理控制粒子的最大数量、大小和发射率。
- 利用GPU Instancing:如果场景中有大量相同的静态火焰(如大量相同的火把),确保在材质球上启用了
Enable GPU Instancing。这可以极大降低Draw Call。
5.2 常见问题与解决方案速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 火焰全黑或不显示 | 1. 主节点Surface Type未设置为Transparent。2. 渐变条(Gradient)最左侧颜色Alpha为0,但Mask值始终大于0,导致全透明。 3. 模型法线方向错误(背面剔除)。 | 1. 检查并设置Graph的Surface Type为Transparent。 2. 检查 Final Fire Mask的输出值范围,确保它能采样到渐变条Alpha>0的区域。可以临时将颜色输出改为Mask值查看。3. 在材质上勾选 Double Sided。 |
| 火焰边缘有锯齿(Aliasing) | 透明边缘的Alpha变化过于尖锐。 | 1. 调整渐变条上Alpha从1到0的过渡区间,拉长过渡范围。 2. 对最终的Alpha值进行一次平滑处理,例如使用 Smoothstep节点。 |
| 火焰动态看起来很假、有重复感 | 1. 只使用了一层噪声,且时间变化单调。 2. 所有火焰实例使用相同的噪声参数。 | 1. 添加第二层不同Scale和速度的噪声进行叠加。 2. 对于粒子系统,尝试将粒子的 Random Seed或自定义的Particle Age作为噪声UV的偏移量输入。 |
| 火焰在场景中排序错乱 | 透明物体渲染顺序问题。 | 1. 确保火焰材质渲染队列(Render Queue)高于不透明物体(默认3000)。 2. 对于复杂的相互重叠的透明物体,可能需要手动调整它们的渲染顺序,或考虑使用更复杂的混合模式。 |
| 移动设备上帧率下降明显 | Shader计算过于复杂或透明Overdraw严重。 | 1. 按照5.1节进行优化,减少噪声层数、改用低精度。 2. 减少同屏火焰面片数量,降低粒子发射率。 3. 使用LOD(Level of Detail),远离摄像机时使用简化版的Shader或更低分辨率的纹理。 |
5.3 效果调试技巧
当效果不如预期时,不要盲目调整所有参数。学会使用“分治法”调试:
- 隔离检查Mask:创建一个临时的分支,将
Final Fire Mask直接连接到Base Color(通过Float to Vector转换),你会看到一个黑白的火焰形状图。这能最直观地检查你的形状、动态和消散效果是否如你所愿。 - 检查颜色映射:暂时固定Mask的输入,例如用一个
Float节点输入0.5,看Sample Gradient输出的颜色是否正确。再分别测试0.1和0.9,检查渐变条的两端。 - 检查时间影响:将
Time节点输出暂时连接到一个颜色上,观察它是否在持续、平滑地变化。也可以暂停时间,逐帧检查静态下的噪声形态。
火焰效果的创作是一个艺术与技术结合的过程。本教程提供的节点图是一个强大的起点和框架,但最迷人的火焰往往来自于你基于这个框架的个性化调整。多尝试不同的噪声组合、渐变色彩和形状参数,观察现实中的火焰视频或图片,将你的观察转化为节点图中的数值。当你能够随心所欲地控制火焰的“性格”——是狂暴的烈焰还是温顺的烛火——时,你就真正掌握了用ShaderGraph创造视觉魔术的钥匙。