Godot 3 GDScript 教程 / Godot 3 GDScript 教程(十七):着色器入门(Shader)
着色器入门(Shader)
着色器是运行在 GPU 上的程序,用于控制物体的渲染方式。Godot 3 使用类 GLSL 的着色器语言,可以实现溶解、水面、描边、卡通渲染等高级视觉效果。
Shader 语言概述
着色器类型
| 类型 | 应用节点 | 用途 |
|---|---|---|
canvas_item | Node2D / Control | 2D 渲染 |
spatial | Spatial(3D 节点) | 3D 渲染 |
particles | Particles | 粒子系统 |
基本结构
shader_type canvas_item;
// uniform 变量(从 GDScript 传入)
uniform float speed = 1.0;
uniform vec4 color : hint_color = vec4(1.0);
void vertex() {
// 顶点着色器:处理顶点位置
}
void fragment() {
// 片段着色器:处理像素颜色
}
数据类型
| 类型 | 说明 | 示例 |
|---|---|---|
float | 浮点数 | float a = 1.0; |
vec2/3/4 | 向量 | vec2 uv = vec2(0.5, 0.5); |
mat4 | 4x4 矩阵 | mat4 transform; |
sampler2D | 纹理采样器 | uniform sampler2D tex; |
💡 提示:Godot 的着色器语言与 GLSL 非常相似。主要区别是 Godot 提供了内置变量如 VERTEX、UV、COLOR 等。
顶点着色器 vertex
顶点着色器对每个顶点执行,主要用于变换顶点位置。
内置变量
| 变量 | 说明 |
|---|---|
VERTEX | 顶点位置(本地坐标) |
NORMAL | 顶点法线 |
UV | 纹理坐标 |
TIME | 时间(秒) |
波浪动画
shader_type spatial;
uniform float wave_speed = 2.0;
uniform float wave_height = 0.3;
void vertex() {
float wave = sin(VERTEX.x * 2.0 + TIME * wave_speed) * wave_height;
VERTEX.y += wave;
}
呼吸效果
shader_type spatial;
void vertex() {
float breath = sin(TIME * 1.5) * 0.02;
VERTEX += NORMAL * breath;
}
片段着色器 fragment
片段着色器对每个像素执行,控制最终颜色输出。
内置变量
| 变量 | 说明(spatial) |
|---|---|
UV | 纹理坐标 |
ALBEDO | 基础颜色 |
METALLIC | 金属度 |
ROUGHNESS | 粗糙度 |
EMISSION | 自发光颜色 |
ALPHA | 透明度 |
基础材质
shader_type spatial;
uniform vec4 albedo_color : hint_color = vec4(0.8, 0.2, 0.2, 1.0);
uniform float metallic = 0.0;
uniform float roughness = 0.5;
void fragment() {
ALBEDO = albedo_color.rgb;
METALLIC = metallic;
ROUGHNESS = roughness;
}
uniform 变量
uniform 是从 GDScript 传递数据到着色器的桥梁。
// 声明与 hint
uniform float strength : hint_range(0.0, 1.0) = 0.5;
uniform vec4 color : hint_color = vec4(1.0, 0.0, 0.0, 1.0);
uniform sampler2D noise_texture;
# 从 GDScript 设置 uniform
var material = $MeshInstance.material_override as ShaderMaterial
material.set_shader_param("strength", 0.8)
material.set_shader_param("color", Color(1, 0, 0, 1))
material.set_shader_param("noise_texture", preload("res://textures/noise.png"))
ShaderMaterial
# 代码创建 ShaderMaterial
var shader = Shader.new()
shader.code = """
shader_type canvas_item;
uniform vec4 tint : hint_color = vec4(1.0);
uniform float intensity : hint_range(0.0, 1.0) = 0.5;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
COLOR = mix(tex, tint, intensity);
}
"""
var material = ShaderMaterial.new()
material.shader = shader
material.set_shader_param("tint", Color(0, 1, 0))
$Sprite.material = material
ShaderMaterial vs SpatialMaterial
| 特性 | ShaderMaterial | SpatialMaterial |
|---|---|---|
| 灵活性 | 完全自定义 | 参数化配置 |
| 学习曲线 | 高(需写代码) | 低(GUI 调整) |
| 适用场景 | 特效、自定义渲染 | 通用 PBR 材质 |
CanvasItem 着色器
CanvasItem 着色器用于 2D 节点。
2D 灰度效果
shader_type canvas_item;
uniform float grayscale_amount : hint_range(0.0, 1.0) = 1.0;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
float gray = dot(tex.rgb, vec3(0.299, 0.587, 0.114));
vec3 result = mix(tex.rgb, vec3(gray), grayscale_amount);
COLOR = vec4(result, tex.a);
}
2D 描边效果
shader_type canvas_item;
uniform vec4 outline_color : hint_color = vec4(0, 0, 0, 1);
uniform float outline_width : hint_range(0.0, 10.0) = 2.0;
void fragment() {
vec2 pixel_size = TEXTURE_PIXEL_SIZE * outline_width;
float a = texture(TEXTURE, UV).a;
float n = texture(TEXTURE, UV + vec2(0, -pixel_size.y)).a;
float s = texture(TEXTURE, UV + vec2(0, pixel_size.y)).a;
float e = texture(TEXTURE, UV + vec2(pixel_size.x, 0)).a;
float w = texture(TEXTURE, UV + vec2(-pixel_size.x, 0)).a;
float outline = max(max(n, s), max(e, w)) - a;
vec4 tex_color = texture(TEXTURE, UV);
COLOR = mix(tex_color, outline_color, outline);
}
Spatial 着色器
Spatial 着色器用于 3D 物体。
全息效果
shader_type spatial;
uniform vec4 holo_color : hint_color = vec4(0, 0.8, 1, 0.5);
uniform float scan_speed = 3.0;
void fragment() {
float scan_line = sin((UV.y + TIME * scan_speed) * 50.0 * PI) * 0.5 + 0.5;
float fresnel = 1.0 - dot(NORMAL, VIEW);
fresnel = pow(fresnel, 2.0);
ALBEDO = holo_color.rgb;
ALPHA = (scan_line * 0.3 + fresnel * 0.7) * holo_color.a;
EMISSION = holo_color.rgb * (scan_line * 0.5 + fresnel);
}
溶解效果
溶解(Dissolve)是最经典的游戏特效,常用于角色死亡、传送等。
shader_type spatial;
uniform vec4 dissolve_color : hint_color = vec4(1.0, 0.5, 0.0, 1.0);
uniform sampler2D noise_texture;
uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
uniform float edge_width : hint_range(0.0, 0.2) = 0.05;
uniform float edge_emission : hint_range(0.0, 5.0) = 2.0;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
float noise = texture(noise_texture, UV).r;
if (noise < dissolve_amount) {
discard;
}
float edge = smoothstep(dissolve_amount, dissolve_amount + edge_width, noise);
vec3 edge_color = dissolve_color.rgb * edge_emission;
ALBEDO = mix(edge_color, tex.rgb, edge);
EMISSION = edge_color * (1.0 - edge);
}
# GDScript 控制溶解
extends MeshInstance
var dissolve_speed: float = 0.5
var current_dissolve: float = 0.0
func start_dissolve():
current_dissolve = 0.0
func _process(delta):
current_dissolve = min(1.0, current_dissolve + delta * dissolve_speed)
material_override.set_shader_param("dissolve_amount", current_dissolve)
水面效果
shader_type spatial;
uniform vec4 water_color : hint_color = vec4(0.1, 0.3, 0.6, 0.7);
uniform float wave_speed = 1.0;
uniform float wave_strength = 0.1;
uniform sampler2D normal_map_a : hint_normal;
uniform sampler2D normal_map_b : hint_normal;
void vertex() {
float wave1 = sin(VERTEX.x * 2.0 + TIME * wave_speed) * wave_strength;
float wave2 = sin(VERTEX.z * 1.5 + TIME * wave_speed * 0.8) * wave_strength * 0.7;
VERTEX.y += wave1 + wave2;
}
void fragment() {
vec2 uv1 = UV + vec2(TIME * 0.03, TIME * 0.02);
vec2 uv2 = UV + vec2(-TIME * 0.02, TIME * 0.03);
vec3 n1 = texture(normal_map_a, uv1).rgb;
vec3 n2 = texture(normal_map_b, uv2).rgb;
NORMALMAP = normalize(n1 + n2);
float fresnel = pow(1.0 - dot(NORMAL, VIEW), 3.0);
vec3 color = mix(water_color.rgb, water_color.rgb * 0.5, fresnel);
ALBEDO = color;
ALPHA = mix(water_color.a, 1.0, fresnel);
METALLIC = 0.3;
ROUGHNESS = 0.1;
}
性能注意事项
| 优化建议 | 说明 |
|---|---|
| 减少纹理采样 | 纹理采样是最昂贵的操作之一 |
| 避免分支(if/else) | GPU 不擅长分支预测 |
使用 discard 谨慎 | 会打断 Early-Z 优化 |
| 简化数学运算 | pow、sin、cos 有一定开销 |
| 移动端精简着色器 | 限制纹理采样次数,避免复杂效果 |
⚠️ 注意:在移动端上,复杂的片段着色器会严重影响帧率。discard 语句会破坏 GPU 的 Early-Z 优化,尽量用 ALPHA = 0.0 替代。每个额外的纹理采样都会增加带宽压力。
受击闪烁效果
shader_type canvas_item;
uniform float flash_amount : hint_range(0.0, 1.0) = 0.0;
uniform vec4 flash_color : hint_color = vec4(1, 1, 1, 1);
void fragment() {
vec4 tex = texture(TEXTURE, UV);
COLOR = mix(tex, flash_color, flash_amount);
}
# 角色受击时闪烁
func take_damage():
$Sprite.material.set_shader_param("flash_amount", 1.0)
yield(get_tree().create_timer(0.1), "timeout")
$Sprite.material.set_shader_param("flash_amount", 0.0)
扩展阅读
💡 总结:着色器开发的核心是理解 vertex 和 fragment 两个阶段。
uniform变量是 GDScript 与着色器通信的桥梁。建议从简单效果开始,逐步叠加复杂度。