9. 高级着色器技术
高级着色器技术
着色器(Shader)是告诉 GPU "每个像素应该画什么颜色、每个顶点应该放在哪"的程序。基础篇里你学了简单的着色器概念,现在我们来深入挖掘——水面波纹、全息投影、卡通描边、溶解消失……这些炫酷的视觉效果都是着色器做到的。
本章你将学到
- Shader 语言深入:vertex、fragment、light 三大函数
- 常用 Shader 效果:水面、溶解、全息投影、卡通描边
- 后处理 Shader(全屏特效)
- Compute Shader 计算着色器
- Shader 参数(Uniform)与动态控制
- VisualShader 高级用法
Shader 三大函数
Godot 的着色器语言基于 GLSL,但做了一些简化。每个着色器文件中可以定义三个核心函数:
| 函数 | 作用 | 执行频率 |
|---|---|---|
vertex() | 移动顶点位置、传递数据 | 每个顶点一次 |
fragment() | 计算每个像素的颜色 | 每个像素一次 |
light() | 自定义光照计算 | 每个像素 × 每个光源 |
着色器中的坐标系
在 Godot Shader 中,VERTEX 是模型空间坐标(相对于模型原点),POSITION 是裁剪空间坐标(最终屏幕位置)。如果你需要在世界空间中计算,可以用 MODEL_MATRIX * vec4(VERTEX, 1.0) 转换。
常用 Shader 效果
水面反射/折射
水面是游戏中最常见的着色器效果之一。核心思路是:用法线贴图模拟水波纹的凹凸感,加上反射和折射让水面看起来真实。
// water.gdshader - 水面着色器
shader_type spatial;
// Uniform 参数:可以在编辑器或代码中调整
uniform vec4 water_color : source_color = vec4(0.1, 0.3, 0.5, 0.8);
uniform float wave_speed : hint_range(0.0, 5.0) = 1.0;
uniform float wave_scale : hint_range(0.0, 2.0) = 0.5;
uniform sampler2D normal_map : hint_normal;
varying vec2 uv_offset;
void vertex() {
// 让水面网格有起伏动画
float wave = sin(TIME * wave_speed + VERTEX.x * 3.0) * wave_scale;
wave += cos(TIME * wave_speed * 0.8 + VERTEX.z * 2.5) * wave_scale * 0.5;
VERTEX.y += wave;
uv_offset = UV + TIME * vec2(0.05, 0.03);
}
void fragment() {
// 采样法线贴图模拟波纹
vec3 normal = texture(normal_map, uv_offset).rgb;
normal = normalize(normal * 2.0 - 1.0);
// 水面基础颜色 + 深度影响
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
depth = pow(depth, 2.0); // 越深越暗
ALBEDO = water_color.rgb * (1.0 - depth * 0.5);
NORMAL = normal;
ROUGHNESS = 0.1; // 水面很光滑
METALLIC = 0.3;
ALPHA = water_color.a;
}溶解效果
角色死亡时的"灰飞烟灭"效果、物品消失动画,都可以用溶解着色器实现。原理很简单:用一张噪声贴图,把低于阈值的像素丢弃(discard)。
// dissolve.gdshader - 溶解着色器
shader_type spatial;
uniform vec4 base_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
uniform float edge_width : hint_range(0.0, 0.1) = 0.03;
uniform vec4 edge_color : source_color = vec4(1.0, 0.3, 0.0, 1.0);
uniform sampler2D noise_tex;
void fragment() {
float noise = texture(noise_tex, UV).r;
// 溶解阈值:低于此值的像素被丢弃
if (noise < dissolve_amount) {
discard;
}
// 边缘发光效果
float edge = smoothstep(dissolve_amount, dissolve_amount + edge_width, noise);
float edge_glow = 1.0 - edge;
ALBEDO = mix(edge_color.rgb, base_color.rgb, edge);
EMISSION = edge_color.rgb * edge_glow * 3.0;
}全息投影效果
科幻游戏中常见的半透明、带扫描线、闪烁的全息投影。
// hologram.gdshader - 全息投影着色器
shader_type spatial;
uniform vec4 hologram_color : source_color = vec4(0.0, 0.8, 1.0, 1.0);
uniform float scanline_speed : hint_range(0.0, 10.0) = 2.0;
uniform float flicker_strength : hint_range(0.0, 1.0) = 0.1;
void fragment() {
// 扫描线效果
float scanline = sin(FRAGCOORD.y * 1.5 + TIME * scanline_speed) * 0.5 + 0.5;
scanline = pow(scanline, 4.0);
// 闪烁效果
float flicker = 1.0 - flicker_strength * sin(TIME * 30.0);
ALBEDO = hologram_color.rgb * scanline * flicker;
ALPHA = 0.6 * flicker;
EMISSION = hologram_color.rgb * 0.5 * scanline;
ROUGHNESS = 1.0;
METALLIC = 0.0;
}卡通描边效果
卡通渲染(Cel Shading)风格的描边。核心思路是:把模型沿法线方向放大一圈,渲染成纯黑色,形成"描边"。
// outline.gdshader - 卡通描边着色器
shader_type spatial;
render_mode cull_front; // 反转面剔除,只渲染背面
uniform float outline_width : hint_range(0.0, 0.1) = 0.03;
uniform vec4 outline_color : source_color = vec4(0.0, 0.0, 0.0, 1.0);
void vertex() {
// 沿法线方向膨胀
VERTEX += NORMAL * outline_width;
}
void fragment() {
ALBEDO = outline_color.rgb;
}描边效果的使用方法
卡通描边需要两遍渲染:第一遍正常渲染模型(卡通着色),第二遍用描边着色器渲染放大的背面。在 Godot 中,你可以给同一个 MeshInstance3D 添加两个材质,第二个材质的 next_pass 属性设为描边材质。
后处理 Shader
后处理着色器对整个画面进行特效处理,比如模糊、色彩调整、边缘发光等。Godot 4.x 中可以通过 CompositorEffect 或 CanvasItem 的 backbuffer 实现。
简单的屏幕特效示例:晕影(Vignette)
// vignette.gdshader - 晕影后处理
shader_type canvas_item;
uniform float vignette_strength : hint_range(0.0, 2.0) = 0.8;
uniform float vignette_radius : hint_range(0.0, 1.5) = 0.8;
void fragment() {
vec4 color = texture(TEXTURE, UV);
// 计算到屏幕中心的距离
vec2 uv = UV - vec2(0.5);
float dist = length(uv);
// 晕影效果:越靠近边缘越暗
float vignette = smoothstep(vignette_radius, vignette_radius - 0.3, dist);
color.rgb *= mix(1.0, vignette, vignette_strength);
COLOR = color;
}Compute Shader 计算着色器
Compute Shader 不绘制像素,而是利用 GPU 的并行计算能力来处理大量数据。典型场景:粒子物理计算、流体模拟、大规模数据处理。
using Godot;
using System;
public partial class ComputeShaderExample : Node
{
private RdUniform _uniform;
public override void _Ready()
{
var rd = RenderingServer.CreateLocalRenderingDevice();
// 加载 Compute Shader
var shaderFile = GD.Load<RDShaderFile>("res://compute_example.glsl");
var spirv = shaderFile.GetSpirV();
var shader = rd.ShaderCreateFromSpirV(spirv);
// 准备输入数据
float[] inputData = new float[1024];
for (int i = 0; i < inputData.Length; i++)
inputData[i] = i;
byte[] bytes = new byte[inputData.Length * 4];
Buffer.BlockCopy(inputData, 0, bytes, 0, bytes.Length);
// 创建存储缓冲区
var buffer = rd.StorageBufferCreate((uint)bytes.Length, bytes);
_uniform = new RdUniform();
_uniform.UniformType = RenderingDevice.UniformType.StorageBuffer;
_uniform.Binding = 0;
_uniform.AddId(buffer);
// 创建 uniform 集合并绑定
var uniformSet = rd.UniformSetCreate(
new Godot.Collections.Array<RdUniform> { _uniform }, shader, 0
);
// 创建管线并执行
var pipeline = rd.ComputePipelineCreate(shader);
long computeList = rd.ComputeListBegin();
rd.ComputeListBindComputePipeline(computeList, pipeline);
rd.ComputeListBindUniformSet(computeList, uniformSet, 0);
rd.ComputeListDispatch(computeList, 1024 / 64, 1, 1);
rd.ComputeListEnd();
rd.Submit();
rd.Sync();
// 读取结果
var outputBytes = rd.BufferGetData(buffer);
float[] output = new float[1024];
Buffer.BlockCopy(outputBytes, 0, output, 0, outputBytes.Length);
GD.Print($"Compute Shader 输出[0]: {output[0]}");
}
}extends Node
func _ready():
var rd = RenderingServer.create_local_rendering_device()
# 加载 Compute Shader
var shader_file = load("res://compute_example.glsl")
var spirv = shader_file.get_spir_v()
var shader = rd.shader_create_from_spirv(spirv)
# 准备输入数据
var input_data = PackedFloat32Array()
for i in range(1024):
input_data.append(float(i))
var bytes = input_data.to_byte_array()
# 创建存储缓冲区
var buffer = rd.storage_buffer_create(bytes.size(), bytes)
var uniform = RDUniform.new()
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
uniform.binding = 0
uniform.add_id(buffer)
# 创建 uniform 集合并绑定
var uniform_set = rd.uniform_set_create([uniform], shader, 0)
# 创建管线并执行
var pipeline = rd.compute_pipeline_create(shader)
var compute_list = rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
rd.compute_list_dispatch(compute_list, 1024 / 64, 1, 1)
rd.compute_list_end()
rd.submit()
rd.sync()
# 读取结果
var output_bytes = rd.buffer_get_data(buffer)
var output = output_bytes.decode_float(0)
print("Compute Shader 输出[0]: ", output)Shader 参数与 Uniform 控制
在代码中动态修改 Shader 参数是最常见的操作——比如角色受伤时让模型变红、下雨时让地面变湿。
using Godot;
public partial class ShaderController : MeshInstance3D
{
public void SetDissolveAmount(float amount)
{
// 设置 shader uniform 参数
var mat = GetSurfaceOverrideMaterial(0) as ShaderMaterial;
mat?.SetShaderParameter("dissolve_amount", amount);
}
public void SetOutlineWidth(float width)
{
var mat = GetSurfaceOverrideMaterial(0) as ShaderMaterial;
mat?.SetShaderParameter("outline_width", width);
}
public void TriggerDamageFlash()
{
// 受伤闪红
var mat = GetSurfaceOverrideMaterial(0) as ShaderMaterial;
mat?.SetShaderParameter("flash_color", new Color(1, 0, 0, 0.5f));
mat?.SetShaderParameter("flash_intensity", 0.5f);
// 用 Tween 渐变消失
var tween = CreateTween();
tween.TweenMethod(Callable.From<float>(v =>
mat?.SetShaderParameter("flash_intensity", v)),
0.5f, 0.0f, 0.3f);
}
}extends MeshInstance3D
func set_dissolve_amount(amount: float):
# 设置 shader uniform 参数
var mat = get_surface_override_material(0) as ShaderMaterial
if mat:
mat.set_shader_parameter("dissolve_amount", amount)
func set_outline_width(width: float):
var mat = get_surface_override_material(0) as ShaderMaterial
if mat:
mat.set_shader_parameter("outline_width", width)
func trigger_damage_flash():
# 受伤闪红
var mat = get_surface_override_material(0) as ShaderMaterial
if mat:
mat.set_shader_parameter("flash_color", Color(1, 0, 0, 0.5))
mat.set_shader_parameter("flash_intensity", 0.5)
# 用 Tween 渐变消失
var tween = create_tween()
tween.tween_method(func(v): mat.set_shader_parameter("flash_intensity", v),
0.5, 0.0, 0.3)VisualShader 高级用法
VisualShader 是 Godot 的可视化着色器编辑器——不用写代码,拖拽节点就能创建着色器。适合不熟悉 GLSL 的开发者快速原型设计。
VisualShader 的优势
- 所见即所得:节点之间的连线直接对应数据流,一目了然
- 内置节点丰富:数学运算、纹理采样、时间、法线等上百个节点
- 可转代码:设计好后可以参考生成的 GLSL 代码来学习
高级技巧
- 自定义表达式节点:在 VisualShader 中插入
Expression节点,可以写一小段 GLSL 代码 - Port 复用:一个节点的输出可以连到多个输入,避免重复计算
- 分组管理:用
Comment节点给节点分组,保持整洁
何时用 VisualShader vs 手写 Shader
- 简单效果、快速原型:用 VisualShader
- 复杂逻辑(循环、分支多)、需要精确控制:手写 Shader
- 学习 Shader 编程:先用 VisualShader 搭建,然后查看生成的代码来理解原理
本章小结
| 技术 | 用途 | 难度 |
|---|---|---|
| vertex() 函数 | 水面起伏、草地摆动、描边膨胀 | 中 |
| fragment() 函数 | 溶解、全息投影、卡通着色 | 中 |
| light() 函数 | 自定义光照模型(如卡通光照) | 高 |
| 后处理 Shader | 晕影、模糊、色彩调整、Bloom | 中 |
| Compute Shader | 粒子物理、流体模拟、并行计算 | 高 |
| Uniform 参数 | 代码动态控制着色器效果 | 低 |
| VisualShader | 无代码方式创建着色器 | 低 |
着色器是游戏视觉效果的"灵魂"。建议从简单的溶解效果开始练习,逐步挑战水面、后处理等复杂效果。
相关章节
- 基础篇 - 材质与 Shader:着色器基础概念
- 性能优化:着色器的性能影响
- 粒子与视觉特效:粒子与着色器的配合
