Godot 2D 精灵着色器实战:Sprite Shaders 演示项目逐效果解析
Godot 2D 精灵着色器实战:Sprite Shaders 演示项目逐效果解析
本文基于仓库 2d/sprite_shaders 目录下的官方演示工程撰写。该项目将多种着色器应用到同一张精灵图上,集中展示了 Godot 的 2D 精灵特效能力,涵盖描边(Outline)、发光(Glow)、模糊(Blur)、阴影(Drop/Offset Shadow)、扭曲变形(Fatty)、剪影(Silhouette)、溶解消散(Disintegrate)等典型效果。读完本文,你将掌握这些效果各自对应的
.gdshader源码实现、关键uniform参数的调参含义,以及如何在 Godot 4.x(Compatibility 渲染器)中搭建一个类似的"同图多效果对照"演示场景。
项目概览:同图多效果的着色器陈列馆
仓库根目录 README.md 对项目的定位非常简明:"a sample consisting of different shaders applied to some sprites",即一个由多种着色器作用于精灵图(Sprite)组成的示例。它验证了 Godot 官方在 2D 渲染领域的两个关键技术选型:
- 语言:全部使用 Godot Shader Language 编写(
shader_type canvas_item),面向 2D 精灵的 CanvasItem 着色器; - 渲染器:Compatibility(GL Compatibility,即 OpenGL 兼容管线),对应 project.godot 中的配置:
这意味着这些着色器全部运行在兼容性渲染管线之上,不依赖 Vulkan 独有特性,可平滑迁移到移动端或老旧显卡环境。[rendering] renderer/rendering_method="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility"
工程内共包含 10 个精灵着色器文件,全部位于 shaders/ 目录:
| 着色器文件 | 效果 | 核心 uniform 参数 |
|---|---|---|
| outline.gdshader | 描边 | outline_width、outline_color |
| aura.gdshader | 光环/外发光轮廓 | aura_width、aura_color |
| blur.gdshader | 模糊 | radius |
| fatty.gdshader | 膨胀扭曲 | fattyness |
| dropshadow.gdshader | 柔边投影 | radius、modulate |
| offsetshadow.gdshader | 偏移硬阴影 | offset、modulate |
| silouette.gdshader | 剪影/单色填充 | modulate |
| glow.gdshader | 自发光光晕 | radius、amount |
| dissintegrate.gdshader | 噪点溶解 | amount |
说明:
dissintegrate.gdshader的拼写是项目原始命名(对应英文 "disintegrate" 的变体写法),文中引用时保持仓库原样。
官方截图如下,画面以同一张角色精灵图为底,按两行排列了 10 种不同效果的实时渲染结果,从左到右依次为原始图、描边、光环、模糊、扭曲、投影、偏移阴影、剪影、发光、溶解:
场景工程拆解:10 个 Sprite2D 共用一张贴图
演示主场景 sprite_shaders.tscn 的结构非常清晰,可以直接看出该工程的组织方式:同一张贴图 godotea.png,通过 10 个 Sprite2D 节点各挂一个 ShaderMaterial 实现效果对照。
从场景源码可以看到关键细节(sprite_shaders.tscn):
- 根节点
SpriteShaders(Node2D)整体缩放scale = Vector2(0.3, 0.3),并挂载了第一个outline材质作为基底; - 10 个子节点均为
Sprite2D,texture统一指向ExtResource("2")即 godotea.png; - 每个节点通过
position在二维网格中错位排列(第一行 5 个、第二行 5 个),texture_filter = 4表示使用最近邻/像素风过滤模式,保证模糊、描边等采样效果在低分辨率下依然可辨识; - 场景末尾带一个
Camera2D(offset = Vector2(248, 120)),用于将 10 个精灵全部纳入视野。
材质参数在场景内以 shader_parameter/... 形式预设,例如 Outline 节点使用了黑色描边 Color(0, 0, 0, 1)、宽度 2.0(sprite_shaders.tscn),Aura 节点使用了青色发光 Color(0, 1, 0.486275, 1)(sprite_shaders.tscn)。这意味着你不必打开编辑器,也能从场景文件直接反推出每个效果的默认参数组合。
工程窗口配置同样值得注意(project.godot):
[display]
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
canvas_items 拉伸模式让 2D 内容随窗口尺寸等比缩放,配合 expand 宽高比策略,无论窗口如何变化,演示网格都能完整显示。
描边与光环:邻域 Alpha 采样法
outline.gdshader(描边)
outline.gdshader 是典型的"边缘检测型"描边实现,核心思路是在当前像素上下左右四个方向偏移采样 Alpha 值,再比较极差:
shader_type canvas_item;
uniform float outline_width = 2.0;
uniform vec4 outline_color : source_color;
void fragment() {
vec4 col = texture(TEXTURE, UV);
vec2 ps = TEXTURE_PIXEL_SIZE;
float a;
float maxa = col.a;
float mina = col.a;
a = texture(TEXTURE, UV + vec2(0.0, -outline_width) * ps).a;
maxa = max(a, maxa);
mina = min(a, mina);
// ……其余三个方向同理……
COLOR = mix(col, outline_color, maxa - mina);
}
TEXTURE_PIXEL_SIZE是 Godot CanvasItem 着色器内置变量,表示单个纹素(texel)在 UV 空间中的尺寸,用它乘以偏移量即可把"像素数"转换为 UV 偏移,这正是outline_width = 2.0表示"向外偏移 2 个像素"的原因;- 当采样点位于精灵边缘时,四邻域 Alpha 的
maxa - mina差值会明显变大,mix就把颜色从原图渐变到outline_color;在精灵内部则差值为 0,保持原色; - 效果是沿形状轮廓生成一圈统一颜色的描边,适合角色选中态、卡片边框等 UI/游戏内反馈。
aura.gdshader(光环)
aura.gdshader 与描边同构,但有两个关键差异:
- 使用
render_mode blend_premul_alpha(预乘 Alpha 混合),源码注释也明确指出"This shader only works properly with premultiplied alpha blend mode"; - 在输出时先做预乘
col.rgb *= col.a,再把光环颜色叠加在外部轮廓上(aura.gdshader):
col.rgb *= col.a;
COLOR = col;
COLOR.rgb += aura_color.rgb * (maxa - mina);
效果是精灵外部出现一圈柔和的单色光晕轮廓,常用于技能范围提示、选中高亮等场景。
模糊与投影:多方向平均采样
blur.gdshader(模糊)
blur.gdshader 是最简单的盒式模糊(Box Blur):将当前像素与上下左右四个偏移采样求和后除以 5(即取 5 点平均):
uniform float radius = 4.0;
void fragment() {
vec4 col = texture(TEXTURE, UV);
vec2 ps = TEXTURE_PIXEL_SIZE;
col += texture(TEXTURE, UV + vec2(0.0, -radius) * ps);
col += texture(TEXTURE, UV + vec2(0.0, radius) * ps);
col += texture(TEXTURE, UV + vec2(-radius, 0.0) * ps);
col += texture(TEXTURE, UV + vec2(radius, 0.0) * ps);
col /= 5.0;
COLOR = col;
}
radius控制采样距离,值越大模糊范围越宽;由于只有十字方向 4 个采样点,模糊呈菱形扩散,演示中radius = 4.0已能产生清晰的柔化观感;- 若需要更强效果,可将采样扩展为 3×3 邻域并配合多次迭代。
dropshadow.gdshader 与 offsetshadow.gdshader(两类阴影)
两个阴影着色器代表了两种常见思路:
dropshadow(柔边投影)在 dropshadow.gdshader 中,先对偏移邻域的 8 个方向取平均得到模糊后的影子,再乘上 modulate 颜色,最后按原图 Alpha 与精灵混合:
uniform float radius = 5.0;
uniform vec4 modulate : source_color;
vec4 shadow = texture(TEXTURE, UV + vec2(-radius, -radius) * ps);
// ……共 8 个方向……
shadow /= 8.0;
shadow *= modulate;
vec4 col = texture(TEXTURE, UV);
COLOR = mix(shadow, col, col.a);
**offsetshadow(偏移硬阴影)**在 offsetshadow.gdshader 中则不做模糊,直接把阴影在 offset 方向平移固定像素距离,颜色取 modulate 的 RGB、透明度取自采样处 Alpha:
uniform vec2 offset = vec2(8.0, 8.0);
vec4 shadow = vec4(modulate.rgb, texture(TEXTURE, UV - offset * ps).a * modulate.a);
vec4 col = texture(TEXTURE, UV);
COLOR = mix(shadow, col, col.a);
从场景配置看,两者默认都使用纯黑阴影(modulate = Color(0, 0, 0, 1)),区别在于 Dropshadow 的 radius = 5.0 产生柔和扩散的投影,OffsetShadow 的 offset = (8, 8) 产生边缘清晰的错位硬阴影——前者适合"悬浮感"、后者适合"纸片感"。
扭曲、剪影与发光:单次采样的巧思
fatty.gdshader(膨胀扭曲)
fatty.gdshader 通过径向重映射 UV 实现"胖化"效果:以贴图中心为原点,把每个像素按距离中心的长短做非线性拉伸:
uniform float fattyness = 2.0;
void fragment() {
vec2 ruv = UV - vec2(0.5, 0.5);
vec2 dir = normalize(ruv);
float len = length(ruv);
len = pow(len * 2.0, fattyness) * 0.5;
ruv = len * dir;
vec4 col = texture(TEXTURE, ruv + vec2(0.5, 0.5));
COLOR = col;
}
pow(len * 2.0, fattyness) * 0.5对半径做幂运算,fattyness > 1时边缘像素向中心收缩、中部像素被放大,产生"鼓胀"变形;- 场景中该效果使用了
fattyness = 3.0(sprite_shaders.tscn),变形更夸张。这类 UV 重映射思路还可扩展到波浪、放大镜、液体晃动等效果。
silouette.gdshader(剪影)
silouette.gdshader 是全部着色器中最短的一个,只用一行输出就完成"单色剪影":
uniform vec4 modulate : source_color;
void fragment() {
COLOR = vec4(modulate.rgb, texture(TEXTURE, UV).a * modulate.a);
}
它丢弃原图全部颜色信息,仅保留 Alpha 通道,填充为 modulate 指定的纯色。场景中使用了洋红色 Color(0.858824, 0.317647, 0.701961, 1),适用于敌人被击中闪白/闪红、禁用态灰化等反馈。
glow.gdshader(自发光光晕)
glow.gdshader 采用双半径邻域采样 + 预乘 Alpha 组合:先在 radius 距离上采样 8 个方向、再在 radius * 2.0 距离上采样 8 个方向,共 17 个采样点平均后乘以 amount 作为光晕,最后与原图预乘颜色叠加:
uniform float radius = 5.0;
uniform float amount = 0.25;
glow /= 17.0;
glow *= amount;
col.rgb *= col.a;
COLOR = glow + col;
amount控制光晕强度(演示默认 0.25),radius控制光晕扩散范围;双半径采样让光晕在近处更密、远处更稀,观感比单半径更柔和;- 与 aura(轮廓型光环)不同,glow 是"整体向外晕开"的弥散光效,适合魔法武器、能量核心等自发光物体。
dissintegrate.gdshader(噪点溶解)
dissintegrate.gdshader 使用一个经典的伪随机哈希函数生成逐像素噪声,再按噪声阈值裁切 Alpha,实现"溶解消散":
uniform float amount = 20.0;
void fragment() {
vec2 uv = UV * 0.05;
float a = fract(sin(dot(UV, vec2(12.9898, 78.233))) * 438.5453);
vec4 col = texture(TEXTURE, UV);
col.a *= pow(a, amount);
COLOR = col;
}
fract(sin(dot(UV, vec2(12.9898, 78.233))) * 438.5453)是广为流传的 GLSL 伪随机写法,对每个 UV 位置生成[0,1)的确定性噪声;pow(a, amount)对噪声做幂运算:amount越大,低噪声区域占比越高、整体越"碎";场景默认amount = 4.0(sprite_shaders.tscn),同时该节点的region_rect = Rect2(141, 115, 39, 6)裁切了一小片区域,用于演示溶解动画的局部特写。若配合时间变量TIME动态调小amount,即可得到从 1 到 0 的完整消散动画。
通用机制小结:TEXTURE_PIXEL_SIZE 与混合模式
纵观全部 9 个着色器,可以发现三个贯穿始终的通用机制:
- 邻域采样是特效基础:描边/光环依赖四邻域 Alpha 极差,模糊/阴影/发光依赖多方向平均采样,全部通过
TEXTURE + UV偏移 × TEXTURE_PIXEL_SIZE完成像素级采样; render_mode决定混合语义:工程中出现了blend_mix(常规混合)与blend_premul_alpha(预乘混合,aura 与 glow 使用),预乘模式能避免半透明边缘出现黑边/光晕伪影,是发光类效果的首选;source_color注解:uniform vec4 xxx : source_color表示该颜色在编辑器中按 sRGB 颜色空间处理,保证调色所见即所得。
运行与扩展建议
运行方式很简单:使用 Godot 4.7 或更高版本(工程 project.godot 声明的 config/features 为 "4.7")导入 2d/sprite_shaders 目录,主场景自动指向 sprite_shaders.tscn,直接按 F5 运行即可看到 10 种效果的同屏对照。若要在自己的项目中复用:
- 将 shaders/ 下某个
.gdshader文件拖入你的 Sprite2D 的 Material 槽位(或创建 ShaderMaterial 引用); - 参照 sprite_shaders.tscn 中的
shader_parameter/...写法在编辑器中实时调参; - 注意:aura、glow 等预乘 Alpha 着色器必须搭配
blend_premul_alpha才能获得正确效果;所有效果均基于 Compatibility 渲染器验证,若切换到 Forward+ 或移动端管线,建议先跑一遍确认采样行为一致。