2. 高级着色器技术
2026/4/14大约 5 分钟
高级着色器技术
着色器(Shader)是运行在 GPU 上的小程序,用来决定每个像素最终显示什么颜色。掌握着色器技术,可以实现任何你能想象的视觉风格。本章介绍 2.5D 游戏中最实用的几种着色器。
2.1 Godot Shader 基础回顾
Godot 使用自己的着色器语言(GLSL 的变体),分为三种类型:
| 类型 | 用途 | 节点 |
|---|---|---|
spatial | 3D 物体表面 | MeshInstance3D |
canvas_item | 2D 精灵/UI | Sprite2D, Control |
particles | 粒子行为 | GPUParticles3D |
2.5D 游戏中,Sprite3D 使用 spatial 着色器,而 UI 元素使用 canvas_item 着色器。
2.2 像素化着色器
像素化效果(Pixelation)是复古风格游戏的标志性视觉效果,通过降低渲染分辨率来模拟像素艺术风格。
// pixel_art.gdshader - 用于 Sprite3D 的像素化着色器
shader_type spatial;
render_mode unshaded, cull_disabled, depth_draw_opaque;
uniform sampler2D texture_albedo : source_color, filter_nearest;
uniform float pixel_size : hint_range(1.0, 32.0, 1.0) = 4.0;
void fragment() {
// 将 UV 坐标量化到像素网格
vec2 uv = UV;
vec2 texture_size = vec2(textureSize(texture_albedo, 0));
vec2 pixel_uv = floor(uv * texture_size / pixel_size) * pixel_size / texture_size;
vec4 color = texture(texture_albedo, pixel_uv);
// 丢弃透明像素(保持精灵边缘干净)
if (color.a < 0.1) discard;
ALBEDO = color.rgb;
ALPHA = color.a;
}在代码中动态控制像素化程度:
C#
using Godot;
public partial class PixelArtSprite : Sprite3D
{
private ShaderMaterial _shaderMat;
public override void _Ready()
{
// 加载像素化着色器
var shader = GD.Load<Shader>("res://shaders/pixel_art.gdshader");
_shaderMat = new ShaderMaterial();
_shaderMat.Shader = shader;
MaterialOverride = _shaderMat;
}
// 设置像素块大小(越大越像素化)
public void SetPixelSize(float size)
{
_shaderMat.SetShaderParameter("pixel_size", size);
}
// 动画:从清晰渐变到像素化(传送效果)
public async void AnimatePixelate(float targetSize, float duration)
{
float startSize = _shaderMat.GetShaderParameter("pixel_size").AsSingle();
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += (float)GetProcessDeltaTime();
float t = elapsed / duration;
float current = Mathf.Lerp(startSize, targetSize, t);
_shaderMat.SetShaderParameter("pixel_size", current);
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
}
_shaderMat.SetShaderParameter("pixel_size", targetSize);
}
}GDScript
extends Sprite3D
var _shader_mat: ShaderMaterial
func _ready() -> void:
var shader := load("res://shaders/pixel_art.gdshader") as Shader
_shader_mat = ShaderMaterial.new()
_shader_mat.shader = shader
material_override = _shader_mat
func set_pixel_size(size: float) -> void:
_shader_mat.set_shader_parameter("pixel_size", size)
func animate_pixelate(target_size: float, duration: float) -> void:
var start_size := _shader_mat.get_shader_parameter("pixel_size") as float
var elapsed := 0.0
while elapsed < duration:
elapsed += get_process_delta_time()
var t := elapsed / duration
var current := lerpf(start_size, target_size, t)
_shader_mat.set_shader_parameter("pixel_size", current)
await get_tree().process_frame
_shader_mat.set_shader_parameter("pixel_size", target_size)2.3 轮廓线着色器
轮廓线(Outline)效果让角色和物体更突出,是 2.5D 游戏中非常常见的视觉风格。
// outline.gdshader - 轮廓线着色器(canvas_item 版本,用于 Sprite2D)
shader_type canvas_item;
uniform vec4 outline_color : source_color = vec4(0.0, 0.0, 0.0, 1.0);
uniform float outline_width : hint_range(0.0, 10.0) = 2.0;
void fragment() {
vec2 size = outline_width / vec2(textureSize(TEXTURE, 0));
// 采样周围8个方向
float alpha = 0.0;
alpha = max(alpha, texture(TEXTURE, UV + vec2(-size.x, 0)).a);
alpha = max(alpha, texture(TEXTURE, UV + vec2(size.x, 0)).a);
alpha = max(alpha, texture(TEXTURE, UV + vec2(0, -size.y)).a);
alpha = max(alpha, texture(TEXTURE, UV + vec2(0, size.y)).a);
alpha = max(alpha, texture(TEXTURE, UV + vec2(-size.x, -size.y)).a);
alpha = max(alpha, texture(TEXTURE, UV + vec2(size.x, -size.y)).a);
alpha = max(alpha, texture(TEXTURE, UV + vec2(-size.x, size.y)).a);
alpha = max(alpha, texture(TEXTURE, UV + vec2(size.x, size.y)).a);
vec4 original = texture(TEXTURE, UV);
// 如果原始像素透明但周围有不透明像素,则显示轮廓色
if (original.a < 0.1 && alpha > 0.1) {
COLOR = outline_color;
} else {
COLOR = original;
}
}2.4 卡通渲染(Cel Shading)
卡通渲染通过将光照分成几个离散的色阶,模拟手绘动画的风格。
// cel_shading.gdshader - 卡通渲染着色器
shader_type spatial;
render_mode cull_disabled;
uniform sampler2D texture_albedo : source_color;
uniform int shade_levels : hint_range(2, 8) = 3;
uniform float specular_size : hint_range(0.0, 1.0) = 0.1;
uniform vec4 specular_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
void fragment() {
vec4 albedo = texture(texture_albedo, UV);
ALBEDO = albedo.rgb;
ALPHA = albedo.a;
if (ALPHA < 0.1) discard;
}
void light() {
// 计算漫反射强度
float NdotL = dot(NORMAL, LIGHT);
// 将连续光照量化为离散色阶(卡通效果的核心)
float levels = float(shade_levels);
float shaded = floor(NdotL * levels) / levels;
shaded = clamp(shaded, 0.0, 1.0);
// 高光(镜面反射)
vec3 half_vec = normalize(LIGHT + VIEW);
float NdotH = dot(NORMAL, half_vec);
float specular = step(1.0 - specular_size, NdotH);
// 合并漫反射和高光
DIFFUSE_LIGHT += ALBEDO * LIGHT_COLOR * shaded * ATTENUATION;
SPECULAR_LIGHT += specular_color.rgb * specular * ATTENUATION;
}2.5 水面着色器
2.5D 游戏中的水面效果,通过扭曲折射和波纹动画来实现。
// water_25d.gdshader - 2.5D 水面着色器
shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_disabled, unshaded;
uniform sampler2D noise_texture : filter_linear_mipmap;
uniform vec4 water_color : source_color = vec4(0.1, 0.4, 0.8, 0.8);
uniform vec4 foam_color : source_color = vec4(0.9, 0.95, 1.0, 1.0);
uniform float wave_speed = 1.0;
uniform float wave_strength : hint_range(0.0, 0.1) = 0.02;
uniform float foam_threshold : hint_range(0.0, 1.0) = 0.7;
void fragment() {
float time = TIME * wave_speed;
// 两层噪声叠加,产生自然波纹
vec2 uv1 = UV + vec2(time * 0.1, time * 0.05);
vec2 uv2 = UV + vec2(-time * 0.07, time * 0.12);
float noise1 = texture(noise_texture, uv1).r;
float noise2 = texture(noise_texture, uv2).r;
float combined_noise = (noise1 + noise2) * 0.5;
// 泡沫效果(噪声值高的区域显示泡沫)
float foam = step(foam_threshold, combined_noise);
// 混合水色和泡沫色
vec4 final_color = mix(water_color, foam_color, foam);
// 轻微扭曲 UV(折射效果)
vec2 distortion = vec2(noise1 - 0.5, noise2 - 0.5) * wave_strength;
ALBEDO = final_color.rgb;
ALPHA = final_color.a;
}2.6 溶解/消失着色器
溶解效果常用于角色死亡、传送、技能释放等场景。
// dissolve.gdshader - 溶解消失着色器
shader_type spatial;
render_mode unshaded, cull_disabled;
uniform sampler2D texture_albedo : source_color;
uniform sampler2D noise_texture : filter_linear_mipmap;
uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
uniform vec4 edge_color : source_color = vec4(1.0, 0.5, 0.0, 1.0);
uniform float edge_width : hint_range(0.0, 0.2) = 0.05;
void fragment() {
vec4 albedo = texture(texture_albedo, UV);
float noise = texture(noise_texture, UV).r;
// 根据噪声值和溶解进度决定是否显示
if (noise < dissolve_amount) discard;
// 在溶解边缘显示发光颜色
float edge = step(dissolve_amount, noise) - step(dissolve_amount + edge_width, noise);
vec3 final_color = mix(albedo.rgb, edge_color.rgb, edge);
ALBEDO = final_color;
ALPHA = albedo.a;
}在代码中控制溶解动画:
C#
using Godot;
public partial class DissolveEffect : MeshInstance3D
{
private ShaderMaterial _mat;
public override void _Ready()
{
_mat = MaterialOverride as ShaderMaterial;
}
// 播放溶解消失动画
public async void PlayDissolve(float duration = 1.0f)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += (float)GetProcessDeltaTime();
float amount = elapsed / duration;
_mat.SetShaderParameter("dissolve_amount", amount);
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
}
QueueFree();
}
// 播放溶解出现动画(反向)
public async void PlayAppear(float duration = 1.0f)
{
float elapsed = 0f;
_mat.SetShaderParameter("dissolve_amount", 1.0f);
while (elapsed < duration)
{
elapsed += (float)GetProcessDeltaTime();
float amount = 1.0f - (elapsed / duration);
_mat.SetShaderParameter("dissolve_amount", amount);
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
}
_mat.SetShaderParameter("dissolve_amount", 0.0f);
}
}GDScript
extends MeshInstance3D
var _mat: ShaderMaterial
func _ready() -> void:
_mat = material_override as ShaderMaterial
func play_dissolve(duration: float = 1.0) -> void:
var elapsed := 0.0
while elapsed < duration:
elapsed += get_process_delta_time()
_mat.set_shader_parameter("dissolve_amount", elapsed / duration)
await get_tree().process_frame
queue_free()
func play_appear(duration: float = 1.0) -> void:
var elapsed := 0.0
_mat.set_shader_parameter("dissolve_amount", 1.0)
while elapsed < duration:
elapsed += get_process_delta_time()
_mat.set_shader_parameter("dissolve_amount", 1.0 - elapsed / duration)
await get_tree().process_frame
_mat.set_shader_parameter("dissolve_amount", 0.0)2.7 着色器性能优化建议
| 优化点 | 说明 |
|---|---|
减少 texture() 调用 | 每次纹理采样都有开销,合并多个纹理 |
避免 if 分支 | GPU 不擅长条件分支,用 step()/mix() 替代 |
| 使用低精度 | lowp 和 mediump 在移动端更快 |
| 预计算常量 | 把不变的计算移到 CPU 端 |
合理使用 discard | discard 会阻止深度写入,影响性能 |
着色器调试
在 Godot 编辑器中,可以实时修改着色器代码并立即看到效果。利用 ALBEDO = vec3(value) 将中间值可视化,是调试着色器的常用技巧。
