强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Godot 3 GDScript 教程 / Godot 3 GDScript 教程(十七):着色器入门(Shader)

着色器入门(Shader)

着色器是运行在 GPU 上的程序,用于控制物体的渲染方式。Godot 3 使用类 GLSL 的着色器语言,可以实现溶解、水面、描边、卡通渲染等高级视觉效果。


Shader 语言概述

着色器类型

类型应用节点用途
canvas_itemNode2D / Control2D 渲染
spatialSpatial(3D 节点)3D 渲染
particlesParticles粒子系统

基本结构

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);
mat44x4 矩阵mat4 transform;
sampler2D纹理采样器uniform sampler2D tex;

💡 提示:Godot 的着色器语言与 GLSL 非常相似。主要区别是 Godot 提供了内置变量如 VERTEXUVCOLOR 等。


顶点着色器 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

特性ShaderMaterialSpatialMaterial
灵活性完全自定义参数化配置
学习曲线高(需写代码)低(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 优化
简化数学运算powsincos 有一定开销
移动端精简着色器限制纹理采样次数,避免复杂效果

⚠️ 注意:在移动端上,复杂的片段着色器会严重影响帧率。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 与着色器通信的桥梁。建议从简单效果开始,逐步叠加复杂度。