Unity ShaderGraph实战:程序化生成动态星空传送门效果
1. 项目概述与核心思路
最近在做一个科幻题材的独立游戏项目,需要一个能瞬间将玩家带入异次元空间的视觉锚点——一个动态的星空传送门。这个效果不能是简单的贴图旋转,它需要有深邃的星空背景、流动的星云、闪烁的星辰,以及一个能量涌动的“门框”边缘。用传统的代码编写Shader固然强大,但调试过程堪称噩梦。这次我决定全程使用Unity的ShaderGraph,从零开始构建这个效果,目标是让美术同学甚至是对Shader编程有畏惧感的开发者也能看懂、能复现,并且能灵活调整出属于自己的风格。
这个传送门效果的核心,在于对多层、多频率噪声的巧妙运用。我们不会去手绘每一颗星星,而是通过程序化生成的方式,用数学创造出整个宇宙。整个过程就像在指挥一场视觉的交响乐:低频的Worley噪声塑造出星云的基础形态和色彩,高频的Simple噪声或Gradient Noise则用来生成细碎的星点,最后通过一些简单的数学运算(如取绝对值、取小数部分)来创造流光和扭曲效果。ShaderGraph的可视化节点让我们能直观地看到每一步操作对最终结果的影响,这是代码Shader难以比拟的优势。
最终效果将包含几个关键视觉层:1)一个深邃的、有色彩变化的星空背景;2)在背景上缓慢旋转和流动的星云状图案;3)随机分布且亮度、大小各异的闪烁星点;4)一个环绕门洞的、带有能量波纹和辉光边缘的边框。所有这些效果都将在一个Shader中通过叠加和混合完成。
2. 核心节点解析与视觉原理
在动手连接节点之前,我们需要先吃透几个ShaderGraph中的核心“乐器”,理解它们各自能发出什么样的“声音”。
2.1 噪声节点:宇宙的画笔
噪声是创造自然随机效果的基础。ShaderGraph提供了多种噪声,我们需要根据效果选择。
- Simple Noise:这是最基础的噪声,生成的是类似电视雪花屏的黑白噪点。它的特点是高频、细节丰富,但形态比较“硬”。在星空效果中,它非常适合用来生成那些细密、锐利的小星点。你可以把它想象成一把撒向画布的盐粒。
- Gradient Noise (Perlin Noise):这是最著名的程序化噪声,生成的是连续、平滑的云雾状图案。它的过渡非常自然,是创造星云、气体云团效果的绝对主力。通过调整其频率(Scale),你可以得到从宏观星云到微观纹理的各种形态。
- Voronoi (Worley Noise):这个噪声会生成类似细胞或鹅卵石状的图案,其特征是明显的“晶胞”结构和晶胞间的边界。在星空Shader中,它有两大妙用:第一,低频率的Voronoi可以模拟出大块星云团之间模糊的边界;第二,取其“Cells”输出通道,可以得到一个个离散的点,这些点经过处理就能变成明亮的恒星或星团核心,这比用Simple Noise生成的点更有“聚集感”。
注意:不同版本的ShaderGraph或渲染管线中,噪声节点的名称和可用性可能略有不同。例如,在URP中你可能直接找到“Voronoi”节点,而在旧版或自定义中,它可能叫“Worley Noise”。原理相通,不必纠结于名称。
2.2 数学节点:效果的魔术师
有了噪声原料,我们需要用数学节点进行“烹饪”。
- Fraction(取小数部分):这是创造流光效果的关键。当你对一个随时间线性增大的值取小数部分时,你会得到一个在0到1之间循环往复的锯齿波。将这个锯齿波作为UV的偏移量,就能让纹理产生平滑的、循环流动的动画。我们的传送门边缘能量流,核心就靠它。
- Absolute(绝对值):绝对值节点能把负值翻转为正值。一个经典技巧是:
abs(frac(x) - 0.5)。frac(x)生成0-1的锯齿波,减去0.5后变成-0.5到0.5,取绝对值后,就变成了一个在0到0.5之间来回震荡的三角波。这个三角波是创造脉冲、呼吸灯效果的理想信号源,可以用来驱动星星的闪烁或边缘辉光的强弱变化。 - Power(幂)和 Multiply(乘法):这两个节点常用于控制对比度和亮度。对噪声纹理进行一次
Power运算,可以显著提高其对比度——让亮部更亮,暗部更暗,星云的结构会更突出。Multiply则常用于混合多个效果层时,控制其显示强度(即Alpha混合)。
2.3 UV操作与时间:让宇宙动起来
静态的星空是壁画,动态的星空才有灵魂。
- Tiling And Offset:这是调整UV的瑞士军刀。通过调整Tiling(平铺),你可以缩放噪声纹理,放大看细节(高频)或缩小看全貌(低频)。Offset则用于平移。这里有一个重要技巧:为了创造有层次感的星空,我们至少需要两套UV。一套用于低频、大尺度的星云背景(Tiling值较小,如0.5),另一套用于高频、小尺度的星星细节(Tiling值较大,如5.0)。让这两套UV以不同的速度和方向进行偏移,就能轻松营造出视差般的景深感。
- Time 节点:ShaderGraph中的
Time节点输出的是自游戏开始以来的秒数。我们很少直接使用它,而是会通过一个Multiply节点乘以一个系数(比如0.1),得到一个缓慢变化的“自定义时间”,再用它去驱动UV的Offset。这样,星云的流动、星星的闪烁周期都在可控范围内。
3. 分步构建星空传送门Shader
接下来,我们进入ShaderGraph工作区,开始实际的节点连接。我建议你一边看一边打开Unity跟着做,理解会更深刻。
3.1 构建基础星空背景
首先,创建一个Unlit Shader Graph,因为我们的传送门是自发光效果,不需要复杂的光照计算。
创建星云基底:
- 添加一个
Gradient Noise节点。将其Scale设置为一个较小的值,例如0.5。这会生成一片平滑、低频的云状图案。 - 添加一个
Color节点,选择一种深蓝色(如#0A1E3C)。再添加一个Color节点,选择一种星云常见的紫红色(如#6B2C91)。 - 添加一个
Lerp(线性插值)节点。将深蓝色连接到A,紫红色连接到B,将Gradient Noise的输出连接到T。这样,噪声的灰度值(0到1)就决定了每个像素是显示深蓝还是紫红,从而形成了有颜色变化的星云基底。将Lerp的输出连接到主节点的Base Color。
- 添加一个
添加星点细节:
- 添加一个
Simple Noise节点。将其Scale设置为一个较大的值,例如8.0。 - 添加一个
Step节点。Step节点像一个阈值过滤器:当输入值大于阈值Edge时,输出1(白色);否则输出0(黑色)。将Simple Noise连接到In,调整Edge值(例如0.95)。你会发现画布上出现了很多稀疏的白色小点——这就是我们的星星雏形。 - 但是,这些星星太“齐整”了。我们需要让它们有亮有暗。在
Simple Noise和Step之间,插入一个Power节点。设置Power为3或4。这会极大地提高噪声的对比度,原本接近阈值的灰色区域会变暗,只有最亮的峰值才能通过Step阈值,从而产生了亮度随机分布的星点。 - 现在,将
Step输出的白色星点,通过一个Add节点,叠加到之前的星云基底颜色上。注意控制叠加的强度,可以用一个Multiply节点将星点输出乘以一个0.5左右的系数再相加,避免星星过曝。
- 添加一个
至此,一个静态的、有层次的星空背景就完成了。你可以通过调整两个噪声的Scale和Step的Edge值,来控制星云的规模和星星的密度。
3.2 实现动态效果与深度感
让星空“活”起来,并营造出深邃感。
让星云流动:
- 创建两个
Time节点,分别乘以不同的系数,比如0.05和0.03,得到慢速和快速两个时间信号。 - 为星云使用的
Gradient Noise节点创建UV输入。添加一个Tiling And Offset节点,将UV接入,将其Offset的X或Y分量与慢速时间信号相连。这样,星云就会以一种宏伟、缓慢的速度漂移。 - 为星星使用的
Simple Noise节点也创建UV输入。使用另一个Tiling And Offset节点,将其Offset与快速时间信号相连,但方向可以与星云相反(例如星云向右,星星向左)。这样,星星和星云就有了相对运动,瞬间产生了深度感和动态感。
- 创建两个
添加闪烁的星星:
- 静态的星星像假的一样。我们让一部分星星闪烁。再创建一个
Simple Noise节点,Scale设得更大(如15.0),用于生成高频的亮度变化信号。 - 添加一个
Sine(正弦)节点,将其输入与一个更慢的时间(Time * 0.5)相乘。正弦波会输出在-1到1之间平滑振荡的值。 - 将正弦波的输出通过
Remap节点,从(-1, 1)映射到(0.5, 1.5)。这个振荡的数值作为系数,去Multiply(乘)之前星星的亮度。结果就是,所有星星的亮度都会同步地、平滑地脉动。 - 如果你想要更随机、更自然的闪烁,可以用那个高频的
Simple Noise去扰动Sine波的频率或振幅,或者用Step阈值筛选出少数星星,只让它们进行强烈的闪烁。
- 静态的星星像假的一样。我们让一部分星星闪烁。再创建一个
3.3 塑造传送门形状与能量边缘
星空有了,现在要把它“装”进传送门的圆形框里,并做出发光的边缘。
创建圆形遮罩:
- 添加一个
Ellipse节点。这本质上是一个生成圆形渐变遮罩的节点。中心最亮(1),边缘最暗(0)。 - 我们需要一个硬边的圆洞。添加一个
Smoothstep节点。Smoothstep比Step更高级,它会在两个阈值之间产生平滑的过渡。将Ellipse的输出连接到In,设置Edge1为0.4,Edge2为0.5。你会得到一个边缘非常平滑的白色圆形,外部是黑色。这个白色圆形就是我们的“门洞”区域。 - 将之前制作好的、动态的星空颜色,与这个圆形遮罩通过
Multiply节点相乘。这样,星空就只显示在圆形区域内了。将相乘的结果输出到主节点的Base Color。
- 添加一个
制作能量边缘:
- 边缘不能只是一个硬边,要有光晕和流动感。我们利用圆形遮罩的梯度信息。
Ellipse节点输出的是一个从中心到边缘的渐变,边缘处的值变化最剧烈。 - 添加一个
DDX和DDY节点(屏幕空间导数)。将Ellipse的输出同时输入两者。然后通过Add节点将它们相加,再通过Absolute取绝对值。这个操作能高亮出图像中数值剧烈变化(即边缘)的区域。 - 但这还不够动态。添加一个
Twirl(旋转)节点。将UV输入Twirl,其Center设置在画布中心(0.5, 0.5),Strength与一个缓慢变化的时间信号相连。然后将Twirl后的UV输入给一个Gradient Noise(Scale设大,如10.0)。 - 将上一步得到的边缘检测信号,与这个动态的
Gradient Noise通过Multiply相乘。这样,我们就得到了一条沿着门洞边缘、不断扭曲变化的噪声带。 - 最后,将这条噪声带通过
Add节点,叠加到之前的星空颜色上。为了让它发光,我们需要将它同时输出到主节点的Emission(自发光)通道,并且强度可以给高一些。你还可以用另一个Color节点(如亮蓝色#00FFFF)通过Multiply来为边缘着色。
- 边缘不能只是一个硬边,要有光晕和流动感。我们利用圆形遮罩的梯度信息。
边缘流光效果:
- 想要边缘有跑马灯一样的流光效果,需要用到
Fraction节点。 - 创建一个基于角度的UV。使用
Arctangent2节点(ATan2),输入位置坐标(可以用Position节点的XY分量减去中心点坐标),得到每个像素相对于中心的角度值(-π到π)。 - 将这个角度值归一化到0-1之间,然后加上
Time * 0.2,再输入Fraction节点。你会得到一个沿着圆周方向不断扫过的锯齿波。 - 用一个
Smoothstep节点从这个锯齿波中提取出一条窄带(如Edge1=0.45, Edge2=0.55),这条窄带就是流光。 - 将这条流光窄带与之前的能量边缘噪声带通过
Multiply或Add混合,再叠加到最终颜色和自发光上。一个生动的、循环流动的能量边缘就完成了。
- 想要边缘有跑马灯一样的流光效果,需要用到
4. 材质调节与场景应用技巧
Shader写好了,但要让它在游戏场景中真正“发光”,还需要在材质和场景布置上下点功夫。
4.1 材质参数化与实时调节
一个好的效果Shader必须是高度可调的。我们应该把关键参数暴露给材质面板。
- 星空颜色与密度:将星云
Lerp节点的两个Color输入,替换为Color类型的材质属性(Property)。同样,将星星的Step阈值、噪声的Scale也暴露为Vector1属性。这样,美术同学可以在Inspector面板上轻松切换星空的色调(从幽蓝的虫洞到炽热的恒星门),调整星星的稀疏稠密。 - 动态速度:将驱动UV偏移的
Time系数暴露为属性。你可以分别暴露“星云流速”、“星点流速”和“边缘扭曲速度”,方便微调出最舒服的动态平衡。 - 边缘效果强度:将边缘辉光的颜色、自发光强度、流光速度等参数全部属性化。在测试时,我习惯为所有重要的中间值(如噪声采样结果、遮罩结果)都创建临时的
Branch节点输出到Preview,方便逐级调试,确定效果后再关闭。
4.2 场景集成与性能考量
在场景中使用这个传送门材质时,有几个实践心得:
- 使用Quad面片:最简单的方式是创建一个Quad(四边形面片),将材质赋给它。调整面片缩放即可改变传送门大小。确保面片的法线方向朝向玩家摄像机。
- 添加后期辉光:仅靠Shader自身的
Emission,发光效果在有些光照环境下可能不够“耀目”。务必在URP或HDRP的Volume中启用Bloom(泛光)后处理效果。调整Bloom的阈值和强度,让传送门的能量边缘产生真实的、溢出的光晕,视觉冲击力会提升一个档次。 - 性能注意:这个Shader使用了多层噪声和多次屏幕空间计算(如
DDX)。虽然对于单个传送门来说开销不大,但如果你的场景需要同时出现几十个,就需要留意。可以考虑的优化点包括:1)将共用的、不随时间变化的噪声采样(如用于星星分布的基础噪声)烘焙到一张小的纹理中;2)在移动平台或低端设备上,适当减少噪声的层数或降低其频率;3)确保传送门面片不要离摄像机过近以至于覆盖整个屏幕,这会导致全屏执行昂贵的片段着色器计算。
5. 常见问题与效果进阶
在实际制作和复现过程中,你可能会遇到以下问题,这里是我的排查记录和解决方案。
5.1 效果不显示或显示异常
| 问题现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 模型全黑或全白 | Shader未正确编译或主节点输出未连接 | 检查ShaderGraph是否有红色报错节点;确保Base Color或Emission有有效输入;在场景中选中材质,检查其Shader是否为你创建的ShaderGraph。 |
| 星空没有动态效果 | Time节点未连接或系数为0 | 检查所有驱动UV偏移或颜色变化的Multiply节点,确认其第二个输入是Time * Speed,且Speed不为0。可以临时给Speed一个很大的值(如10)来确认动画系统是否工作。 |
| 圆形边缘锯齿严重(像素化) | Ellipse生成的遮罩边缘太硬,分辨率不足 | 使用Smoothstep代替Step来软化边缘。增加Smoothstep两个阈值之间的差值(如从0.1增加到0.3),边缘过渡会更平滑。在高质量模式下,也可以考虑开启抗锯齿。 |
| 能量边缘闪烁或抖动 | DDX/DDY节点在屏幕空间计算不稳定 | 这在摄像机或物体快速移动时可能发生。一个替代方案是:不使用DDX/DDY,而是对Ellipse输出做一个Power运算(如0.1),这样会在边缘自然产生一个渐变的辉光带,虽然精度稍低但更稳定。或者,将边缘计算转移到基于UV的平滑函数上。 |
5.2 效果不够“炫酷”的进阶技巧
如果你觉得基础效果已经满足,可以尝试下面这些进阶玩法,让传送门更具个性:
- 深度扭曲效果:真正的传送门应该扭曲其背后的场景。这需要用到渲染命令。在URP中,你可以创建一个
Render Feature,在渲染完不透明物体后、渲染传送门前,将场景颜色抓取到一张Render Texture中。然后在ShaderGraph中,采样这张纹理,但采样时用传送门中心的噪声去扰动UV坐标(例如,用星云噪声的RG通道乘以一个强度系数,加到原本的UV上),再将扭曲后的场景图与星空混合。这样,透过传送门看到的背景世界就是扭曲变形的,沉浸感爆棚。 - 粒子系统增强:Shader负责本体,粒子系统(VFX Graph)负责细节。在传送门边缘发射一些稀疏的、高亮度的粒子火花,或者从门内偶尔涌出一小股粒子流,能极大地增强能量的迸发感和随机性。让粒子系统的颜色与Shader的边缘光颜色联动,保持视觉统一。
- 交互反馈:将Shader中的某些参数与游戏代码联动。例如,当玩家靠近时,通过脚本增加
Emission强度、加快边缘流动速度或改变星空颜色,可以做出非常生动的交互反馈。
这个从零打造星空传送门的过程,本质上是一次对程序化生成和层叠混合思维的训练。ShaderGraph把复杂的数学可视化,让你能直观地理解每一个噪声、每一个运算对最终像素的贡献。当你掌握了这些基本元件的组合方式后,就不仅能做传送门,任何需要动态、有机、科幻感视觉效果的地方,你都可以尝试用节点去“画”出来。我最享受的时刻,不是效果完成的那一刻,而是在调试过程中,随意拖动一个参数滑块,偶然发现了一种意想不到的、绝美的星云形态的时候。那种可控的随机性,就是程序化艺术最大的魅力所在。
