Godot 4 中的 GPUParticles2D/3D — 完整指南

1. 简介

Godot 4 使用 GPUParticles2DGPUParticles3D 作为默认的粒子系统节点。它们完全在 GPU 上运行,因此在处理大量粒子时比 CPU 版本快得多。Godot 3 中旧的 ParticlesMaterial 已被 ParticleProcessMaterial 取代,后者对粒子行为提供了更多控制。

CPUParticles2DCPUParticles3D 仍作为后备节点存在,用于缺乏 GPU 计算支持的设备,或当你需要从 GDScript 访问单个粒子数据时。对于大多数用例,GPU 粒子是推荐选择。

2. GPUParticles2D vs CPUParticles2D

特性 GPUParticles2D CPUParticles2D
处理方式 GPU(计算着色器) CPU(主线程)
最大粒子数 可高效处理 100,000+ 实际上限约 1,000-5,000
子发射器 支持 不支持
拖尾 支持 不支持
单个粒子访问 不可(仅 GPU) 可(从 GDScript)
兼容模式 需要 Vulkan/Metal/D3D12 随处可用
材质类型 ParticleProcessMaterial 内置属性(无需材质)
提示: 默认使用 GPUParticles2D。仅当你需要兼容渲染器支持、需要在 GDScript 中读取粒子位置,或面向非常老旧的硬件时,才切换到 CPUParticles2D

3. 基础设置

要创建一个基础的粒子效果,请在场景中添加一个 GPUParticles2D 节点,并为它分配一个 ParticleProcessMaterial。以下是 GDScript 中的最小配置:

# Create a GPUParticles2D node via code
var particles = GPUParticles2D.new()
particles.amount = 50
particles.lifetime = 2.0
particles.explosiveness = 0.0  # 0 = continuous, 1 = all at once
particles.randomness = 0.5

# Create and assign the material
var mat = ParticleProcessMaterial.new()
mat.direction = Vector3(0, -1, 0)  # Note: uses Vector3 even in 2D
mat.initial_velocity_min = 50.0
mat.initial_velocity_max = 100.0
mat.gravity = Vector3(0, 98, 0)

particles.process_material = mat
add_child(particles)

GPUParticles2D 节点本身的主要属性:

  • amount — 同一时刻存活的粒子数量(默认:8)
  • lifetime — 每个粒子的存活时长(秒)(默认:1.0)
  • one_shot — 发射一次后停止(默认:false)
  • explosiveness — 0.0 = 连续发射,1.0 = 所有粒子一次性发射
  • randomness — 发射时机的随机偏移(0.0-1.0)
  • speed_scale — 模拟速度的倍数
  • emitting — 系统当前是否正在发射

4. ParticleProcessMaterial 深入解析

ParticleProcessMaterial 控制单个粒子在其生命周期中的行为方式。所有方向属性都使用 Vector3,即使对于 2D 粒子也是如此(Z 分量会被直接忽略)。

方向与速度

var mat = ParticleProcessMaterial.new()

# Direction: normalized vector for initial particle heading
mat.direction = Vector3(0, -1, 0)  # Upward in 2D (Y is flipped)

# Spread: cone angle in degrees (0 = straight line, 180 = hemisphere)
mat.spread = 45.0

# Initial velocity: particles spawn with speed in this range
mat.initial_velocity_min = 100.0
mat.initial_velocity_max = 200.0

# Gravity: constant acceleration applied every frame
mat.gravity = Vector3(0, 98, 0)  # Pulls downward in 2D

角速度

让粒子在移动时旋转。适用于碎片、彩纸屑或落叶。

mat.angular_velocity_min = -180.0  # degrees per second
mat.angular_velocity_max = 180.0

生命周期内的缩放

使用 CurveTexture 让粒子尺寸在生命周期内变化:

var curve = Curve.new()
curve.add_point(Vector2(0.0, 1.0))  # Full size at birth
curve.add_point(Vector2(0.5, 1.2))  # Slightly larger at midlife
curve.add_point(Vector2(1.0, 0.0))  # Shrink to nothing at death

var curve_tex = CurveTexture.new()
curve_tex.curve = curve
mat.scale_curve = curve_tex

阻尼与吸引器

阻尼会随时间减慢粒子的速度,模拟空气阻力。吸引器(独立节点)会把粒子拉向某一点。

mat.damping_min = 5.0
mat.damping_max = 10.0

5. 颜色渐变

ParticleProcessMaterial 上的 color_ramp 属性接受一个 GradientTexture1D,用于在粒子生命周期内平滑地改变其颜色。这对于逼真的火焰、烟雾和魔法效果至关重要。

火焰渐变示例

var gradient = Gradient.new()
gradient.colors = PackedColorArray([
    Color(1.0, 1.0, 1.0, 1.0),    # White (hot core)
    Color(1.0, 1.0, 0.2, 1.0),    # Yellow
    Color(1.0, 0.5, 0.0, 1.0),    # Orange
    Color(0.8, 0.1, 0.0, 0.8),    # Red
    Color(0.2, 0.0, 0.0, 0.0),    # Dark red, transparent (fade out)
])
gradient.offsets = PackedFloat32Array([0.0, 0.15, 0.4, 0.7, 1.0])

var grad_tex = GradientTexture1D.new()
grad_tex.gradient = gradient
mat.color_ramp = grad_tex
提示: 始终让渐变以 alpha = 0 结束,这样粒子会平滑淡出,而不是突然消失。

你也可以设置一个扁平的 color 属性来代替渐变,用于生成颜色统一的粒子:

mat.color = Color(0.2, 0.6, 1.0, 0.8)  # Semi-transparent blue

6. 发射形状

发射形状决定了粒子在何处生成。通过 ParticleProcessMaterial 上的 emission_shape 设置。

Point

所有粒子从节点的原点生成。这是默认形状。

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINT

Sphere / Sphere Surface

粒子在球体内(或仅在其表面上)随机生成。

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
mat.emission_sphere_radius = 50.0

# Surface only:
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE_SURFACE
mat.emission_sphere_radius = 50.0

Box

粒子在一个矩形体积内随机生成。

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
mat.emission_box_extents = Vector3(100, 10, 0)  # Wide and thin

Ring

粒子在环形(甜甜圈)形状上生成,由内径/外径和高度定义。

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_RING
mat.emission_ring_radius = 80.0       # Outer radius
mat.emission_ring_inner_radius = 60.0 # Inner radius (hole)
mat.emission_ring_height = 0.0        # Flat ring
mat.emission_ring_axis = Vector3(0, 0, 1)  # Ring normal
提示: EMISSION_SHAPE_BOX 与较宽的 X 范围和较窄的 Y 结合,可以制作地面高度的效果,如尘土拖尾或脚步粒子。

7. 实用配方

火焰

橙红色渐变、向上的速度、轻微的扩散,尺寸在生命周期内递减。

func create_fire() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 40
    p.lifetime = 0.8
    p.randomness = 0.3

    var mat = ParticleProcessMaterial.new()
    mat.direction = Vector3(0, -1, 0)
    mat.spread = 15.0
    mat.initial_velocity_min = 40.0
    mat.initial_velocity_max = 80.0
    mat.gravity = Vector3(0, -20, 0)  # Slight upward pull
    mat.damping_min = 5.0
    mat.damping_max = 10.0

    # Color ramp: white -> yellow -> orange -> red -> transparent
    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(1, 1, 1, 1), Color(1, 1, 0.2, 1),
        Color(1, 0.4, 0, 0.9), Color(0.6, 0.1, 0, 0.4),
        Color(0.2, 0, 0, 0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.1, 0.35, 0.7, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    # Scale: shrink over time
    var curve = Curve.new()
    curve.add_point(Vector2(0, 0.8))
    curve.add_point(Vector2(0.3, 1.0))
    curve.add_point(Vector2(1, 0.0))
    var curve_tex = CurveTexture.new()
    curve_tex.curve = curve
    mat.scale_curve = curve_tex

    p.process_material = mat
    return p

烟雾

灰色渐变、缓慢上升、大范围扩散、低 alpha 以呈现缥缈的外观。

func create_smoke() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 25
    p.lifetime = 3.0
    p.randomness = 0.5

    var mat = ParticleProcessMaterial.new()
    mat.direction = Vector3(0, -1, 0)
    mat.spread = 35.0
    mat.initial_velocity_min = 10.0
    mat.initial_velocity_max = 30.0
    mat.gravity = Vector3(0, -5, 0)
    mat.damping_min = 2.0
    mat.damping_max = 5.0
    mat.angular_velocity_min = -30.0
    mat.angular_velocity_max = 30.0

    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(0.6, 0.6, 0.6, 0.0), Color(0.5, 0.5, 0.5, 0.3),
        Color(0.4, 0.4, 0.4, 0.2), Color(0.3, 0.3, 0.3, 0.0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.15, 0.6, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    # Scale: grow over time (smoke expands)
    var curve = Curve.new()
    curve.add_point(Vector2(0, 0.3))
    curve.add_point(Vector2(0.5, 1.0))
    curve.add_point(Vector2(1, 1.5))
    var curve_tex = CurveTexture.new()
    curve_tex.curve = curve
    mat.scale_curve = curve_tex

    p.process_material = mat
    return p

蓝白色条纹、强烈的向下重力、狭窄的扩散、大量粒子。

func create_rain() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 200
    p.lifetime = 1.0

    # Use a box emission to cover the screen width
    var mat = ParticleProcessMaterial.new()
    mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
    mat.emission_box_extents = Vector3(600, 0, 0)
    mat.direction = Vector3(0.05, 1, 0)  # Slightly angled
    mat.spread = 3.0
    mat.initial_velocity_min = 400.0
    mat.initial_velocity_max = 500.0
    mat.gravity = Vector3(0, 200, 0)

    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(0.7, 0.8, 1.0, 0.6), Color(0.7, 0.85, 1.0, 0.4),
        Color(0.7, 0.85, 1.0, 0.0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.8, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    # Stretch particles to look like streaks
    mat.scale_min = 0.5
    mat.scale_max = 0.5

    p.process_material = mat
    return p

火花

黄橙色、爆发式喷发、高初速度、短生命周期。

func create_sparks() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 30
    p.lifetime = 0.5
    p.one_shot = true
    p.explosiveness = 1.0  # All at once

    var mat = ParticleProcessMaterial.new()
    mat.direction = Vector3(0, -1, 0)
    mat.spread = 180.0  # Full sphere
    mat.initial_velocity_min = 150.0
    mat.initial_velocity_max = 300.0
    mat.gravity = Vector3(0, 200, 0)  # Fall quickly
    mat.damping_min = 10.0
    mat.damping_max = 20.0

    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(1, 1, 0.8, 1), Color(1, 0.7, 0.1, 1),
        Color(1, 0.3, 0, 0.5), Color(0.5, 0.1, 0, 0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.2, 0.6, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    # Scale: start small, stay small
    mat.scale_min = 0.3
    mat.scale_max = 0.6

    p.process_material = mat
    return p

白色粒子、缓慢温和的下落、大范围扩散、轻微的水平飘移。

func create_snow() -> GPUParticles2D:
    var p = GPUParticles2D.new()
    p.amount = 100
    p.lifetime = 5.0
    p.randomness = 0.8

    var mat = ParticleProcessMaterial.new()
    mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
    mat.emission_box_extents = Vector3(500, 0, 0)
    mat.direction = Vector3(0.1, 1, 0)
    mat.spread = 10.0
    mat.initial_velocity_min = 20.0
    mat.initial_velocity_max = 40.0
    mat.gravity = Vector3(0, 10, 0)
    mat.damping_min = 1.0
    mat.damping_max = 3.0
    mat.angular_velocity_min = -45.0
    mat.angular_velocity_max = 45.0

    var grad = Gradient.new()
    grad.colors = PackedColorArray([
        Color(1, 1, 1, 0), Color(1, 1, 1, 0.8),
        Color(1, 1, 1, 0.7), Color(1, 1, 1, 0)
    ])
    grad.offsets = PackedFloat32Array([0.0, 0.05, 0.8, 1.0])
    var grad_tex = GradientTexture1D.new()
    grad_tex.gradient = grad
    mat.color_ramp = grad_tex

    mat.scale_min = 0.3
    mat.scale_max = 0.8

    p.process_material = mat
    return p

8. 3D 粒子(GPUParticles3D)

GPUParticles3D 的工作方式与 GPUParticles2D 完全相同,但在 3D 空间中运行。使用的是同一个 ParticleProcessMaterial。主要区别:

  • Vector3 的全部 3 个轴都生效(X、Y、Z)
  • 发射形状在完整的 3D 中工作(球体是真正的球体,盒体是体积)
  • 你可以将 draw_pass_1QuadMesh 或自定义网格搭配,用于公告板粒子
  • 网格粒子可以使用 StandardMaterial3DORMMaterial3D
# 3D fire torch example
var particles_3d = GPUParticles3D.new()
particles_3d.amount = 60
particles_3d.lifetime = 1.0

var mat = ParticleProcessMaterial.new()
mat.direction = Vector3(0, 1, 0)  # Upward in 3D
mat.spread = 20.0
mat.initial_velocity_min = 1.0
mat.initial_velocity_max = 2.0
mat.gravity = Vector3(0, -0.5, 0)  # Slight counter-gravity

# Emission from a small sphere
mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
mat.emission_sphere_radius = 0.2

particles_3d.process_material = mat

# Use a QuadMesh as the particle shape
var quad = QuadMesh.new()
quad.size = Vector2(0.3, 0.3)
particles_3d.draw_pass_1 = quad

# Billboard mode: particles always face the camera
mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED

add_child(particles_3d)
提示: 对于网格粒子(碎片、弹壳、块体),将自定义的 .tres 网格分配给 draw_pass_1 并为其应用 StandardMaterial3D。每个粒子都会渲染为该网格的副本。

9. 子发射器

Godot 4 支持用于粒子生成粒子效果的子发射器。子发射器是另一个 GPUParticles2D/GPUParticles3D 节点,它会在每个父粒子的位置发射粒子。这非常适合:

  • 从火焰粒子中飞散的火花
  • 烟花爆炸后留下的烟雾轨迹
  • 碰撞时的飞溅水滴
  • 二次爆炸

设置子发射器:

# 1. Create the sub-emitter node (must be a sibling or child)
var sub_sparks = GPUParticles2D.new()
sub_sparks.amount = 4
sub_sparks.lifetime = 0.3
sub_sparks.explosiveness = 1.0
sub_sparks.emitting = false  # Controlled by parent
var sub_mat = ParticleProcessMaterial.new()
sub_mat.direction = Vector3(0, -1, 0)
sub_mat.spread = 180.0
sub_mat.initial_velocity_min = 50.0
sub_mat.initial_velocity_max = 100.0
sub_sparks.process_material = sub_mat
add_child(sub_sparks)

# 2. Reference it from the parent material
var parent_mat: ParticleProcessMaterial = fire_particles.process_material
parent_mat.sub_emitter_mode = ParticleProcessMaterial.SUB_EMITTER_AT_END
# Also: SUB_EMITTER_AT_COLLISION, SUB_EMITTER_CONSTANT

# 3. Set the sub_emitter property on the parent node
fire_particles.sub_emitter = fire_particles.get_path_to(sub_sparks)

子发射器模式:

  • SUB_EMITTER_CONSTANT — 从每个父粒子持续发射
  • SUB_EMITTER_AT_END — 在父粒子消亡时发射
  • SUB_EMITTER_AT_COLLISION — 在父粒子碰撞时发射
警告: 子发射器仅适用于 GPUParticles,不适用于 CPUParticles。每个子发射器都会增加 GPU 开销,因此请把子发射器的粒子数保持在较低水平(每个父粒子 4-8 个)。

10. 性能技巧

  • 数量与生命周期的权衡: 可见粒子总数 = amount。如果你增加 lifetime,粒子会存留更久。为此可减少 amount 来平衡,或增加它以获得更密集的效果。
  • 使用可见范围(3D): 在 GPUParticles3D 上设置 visibility_range_end,让远处的粒子系统停止渲染。这对开放世界是一大收益。
  • 固定 FPS: 在粒子节点上设置 fixed_fps(例如 30)以限制模拟更新次数。粒子仍会平滑插值,但占用更少的 GPU 时间。
  • 用 One-shot 处理爆发: 对于类似爆炸的效果,使用 one_shot = true 搭配 explosiveness = 1.0。生命周期结束后即可释放该节点。
  • 在 3D 中使用 LOD: visibility_range_begin/end 与多个不同细节级别的粒子节点结合,实现 LOD(细节层次)粒子效果。
  • 避免过度绘制: 大量半透明粒子相互重叠 = 昂贵的填充率。请减少 amount,改用更大且 alpha 更低的粒子。

11. Godot 3 到 4 的迁移

如果你正在把项目从 Godot 3 迁移到 Godot 4,以下是粒子方面的主要变化:

Godot 3 Godot 4 备注
Particles2D GPUParticles2D 已重命名
Particles (3D) GPUParticles3D 已重命名
ParticlesMaterial ParticleProcessMaterial 重命名并加入新功能
CPUParticles2D CPUParticles2D 未更改
CPUParticles CPUParticles3D 为清晰起见而重命名
无子发射器 sub_emitter_mode Godot 4 新增
无拖尾 trail_enabled Godot 4 新增
flag_align_y particle_flag_align_y 属性已重命名
提示: Godot 的项目转换器会自动处理大多数重命名。不过,GDScript 中任何 ParticlesMaterial 的引用都需要手动更新为 ParticleProcessMaterial

使用 MCP Pro 自动化粒子效果

想让 AI 为你创建粒子效果吗?Godot MCP Pro 包含专门的粒子工具,内置火焰、烟雾、雨、雪、火花等预设。只需一次对话即可完全掌控材质、渐变和发射形状。

  • create_particles
  • set_particle_material
  • set_particle_color_gradient
  • apply_particle_preset
  • get_particle_info
获取 MCP Pro — $15