GPUParticles2D/3D trong Godot 4 — Hướng dẫn đầy đủ

1. Giới thiệu

Godot 4 sử dụng GPUParticles2DGPUParticles3D làm các node hệ thống particle mặc định. Chúng chạy hoàn toàn trên GPU, do đó nhanh hơn đáng kể so với các phiên bản CPU khi xử lý số lượng particle lớn. ParticlesMaterial cũ từ Godot 3 đã được thay thế bằng ParticleProcessMaterial, cung cấp khả năng kiểm soát hành vi particle nhiều hơn.

CPUParticles2DCPUParticles3D vẫn tồn tại như các node dự phòng cho những thiết bị không hỗ trợ GPU compute, hoặc khi bạn cần truy cập dữ liệu của từng particle riêng lẻ từ GDScript. Trong hầu hết các trường hợp, particle GPU là lựa chọn được khuyến nghị.

2. GPUParticles2D vs. CPUParticles2D

Đặc điểm GPUParticles2D CPUParticles2D
Xử lý GPU (compute shader) CPU (luồng chính)
Số particle tối đa 100.000+ vẫn hiệu quả ~1.000-5.000 là giới hạn thực tế
Sub-emitter Không
Trail Không
Truy cập từng particle Không (chỉ GPU) Có (từ GDScript)
Chế độ tương thích Yêu cầu Vulkan/Metal/D3D12 Hoạt động ở mọi nơi
Loại material ParticleProcessMaterial Thuộc tính tích hợp sẵn (không cần material)
Mẹo: Mặc định hãy dùng GPUParticles2D. Chỉ chuyển sang CPUParticles2D khi bạn cần hỗ trợ trình render Compatibility, cần đọc vị trí particle trong GDScript, hoặc nhắm tới phần cứng rất cũ.

3. Thiết lập cơ bản

Để tạo một hiệu ứng particle cơ bản, hãy thêm một node GPUParticles2D vào scene của bạn và gán cho nó một ParticleProcessMaterial. Dưới đây là thiết lập tối giản bằng 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)

Các thuộc tính quan trọng trên chính node GPUParticles2D:

  • amountSố particle tồn tại đồng thời tại một thời điểm (mặc định: 8)
  • lifetimeThời gian sống của mỗi particle tính bằng giây (mặc định: 1.0)
  • one_shotPhát ra một lần rồi dừng (mặc định: false)
  • explosiveness0.0 = dòng liên tục, 1.0 = tất cả particle cùng một lúc
  • randomnessĐộ lệch ngẫu nhiên cho thời điểm phát (0.0-1.0)
  • speed_scaleHệ số nhân cho tốc độ mô phỏng
  • emittingHệ thống có đang phát particle hay không

4. Tìm hiểu sâu ParticleProcessMaterial

ParticleProcessMaterial điều khiển cách từng particle hành xử trong suốt vòng đời của nó. Tất cả các thuộc tính về hướng đều dùng Vector3, ngay cả với particle 2D (thành phần Z chỉ đơn giản là bị bỏ qua).

Hướng & Vận tốc

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

Vận tốc góc

Làm particle xoay tròn khi di chuyển. Hữu ích cho mảnh vỡ, hoa giấy hoặc lá cây.

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

Thay đổi tỷ lệ theo vòng đời

Sử dụng CurveTexture để thay đổi kích thước particle theo vòng đời của nó:

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

Damping & Attractor

Damping làm particle chậm lại theo thời gian, mô phỏng lực cản không khí. Attractor (các node riêng biệt) kéo particle về phía một điểm.

mat.damping_min = 5.0
mat.damping_max = 10.0

5. Dải màu chuyển sắc

Thuộc tính color_ramp trên ParticleProcessMaterial nhận một GradientTexture1D để thay đổi mượt mà màu sắc particle theo vòng đời. Điều này rất cần thiết cho các hiệu ứng lửa, khói và ma thuật chân thực.

Ví dụ: Dải màu lửa

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
Mẹo: Luôn kết thúc dải màu của bạn với alpha = 0 để particle mờ dần mượt mà thay vì biến mất đột ngột.

Với particle đơn sắc, bạn cũng có thể đặt một thuộc tính color phẳng thay vì dải màu chuyển sắc:

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

6. Hình dạng emission

Hình dạng emission xác định nơi particle được sinh ra. Được thiết lập qua emission_shape trên ParticleProcessMaterial.

Point

Tất cả particle sinh ra tại gốc tọa độ của node. Đây là hình dạng mặc định.

mat.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_POINT

Sphere / Sphere Surface

Particle sinh ra ngẫu nhiên bên trong một hình cầu (hoặc chỉ trên bề mặt của nó).

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

Particle sinh ra ngẫu nhiên bên trong một khối hình hộp chữ nhật.

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

Ring

Particle sinh ra theo hình vòng (donut), được xác định bởi bán kính trong/ngoài và chiều cao.

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
Mẹo: Kết hợp EMISSION_SHAPE_BOX với chiều X rộng và chiều Y hẹp để tạo các hiệu ứng sát mặt đất như vệt bụi hoặc particle bước chân.

7. Công thức thực tế

Lửa

Dải màu cam-đỏ, vận tốc hướng lên, độ tỏa nhẹ, tỷ lệ giảm dần theo vòng đời.

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

Khói

Dải màu xám, chuyển động đi lên chậm, độ tỏa lớn, alpha thấp để tạo vẻ mờ ảo.

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

Mưa

Các vệt xanh-trắng, trọng lực hướng xuống mạnh, độ tỏa hẹp, số lượng particle lớn.

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

Tia lửa

Vàng-cam, bùng phát dữ dội, vận tốc ban đầu cao, vòng đời ngắn.

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

Tuyết

Particle trắng, rơi xuống chậm và nhẹ nhàng, độ tỏa rộng, hơi trôi ngang.

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. Particle 3D (GPUParticles3D)

GPUParticles3D hoạt động giống hệt GPUParticles2D nhưng vận hành trong không gian 3D. Nó vẫn dùng cùng một ParticleProcessMaterial. Những điểm khác biệt chính:

  • Cả 3 trục của Vector3 đều hoạt động (X, Y, Z)
  • Hình dạng emission hoạt động trong 3D đầy đủ (hình cầu là hình cầu thật, hộp là khối thể tích)
  • Bạn có thể dùng draw_pass_1 với một QuadMesh hoặc mesh tùy chỉnh cho particle dạng billboard
  • Particle dạng mesh có thể dùng StandardMaterial3D hoặc ORMMaterial3D
# 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)
Mẹo: Với particle dạng mesh (mảnh vỡ, vỏ đạn, mảnh vụn), hãy gán một mesh .tres tùy chỉnh cho draw_pass_1 và áp dụng một StandardMaterial3D lên nó. Mỗi particle sẽ được render như một bản sao của mesh đó.

9. Sub-emitter

Godot 4 hỗ trợ sub-emitter cho các hiệu ứng particle-sinh-particle. Sub-emitter là một node GPUParticles2D/GPUParticles3D khác, phát ra particle tại vị trí của mỗi particle cha. Điều này lý tưởng cho:

  • Tia lửa bắn ra từ particle lửa
  • Vệt khói phía sau vụ nổ pháo hoa
  • Giọt nước bắn tóe khi va chạm
  • Vụ nổ thứ cấp

Cách thiết lập sub-emitter:

# 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)

Các chế độ sub-emitter:

  • SUB_EMITTER_CONSTANTPhát liên tục từ mỗi particle cha
  • SUB_EMITTER_AT_ENDPhát khi particle cha kết thúc vòng đời
  • SUB_EMITTER_AT_COLLISIONPhát khi particle cha va chạm
Cảnh báo: Sub-emitter chỉ hoạt động với GPUParticles, không hoạt động với CPUParticles. Mỗi sub-emitter làm tăng tải GPU, vì vậy hãy giữ số lượng particle của sub-emitter ở mức thấp (4-8 mỗi particle cha).

10. Mẹo tối ưu hiệu năng

  • Cân bằng giữa Số lượng và Vòng đời: Tổng số particle hiển thị = amount. Nếu bạn tăng lifetime, particle sẽ tích tụ lâu hơn. Giảm amount để bù lại, hoặc tăng nó để có hiệu ứng dày đặc hơn.
  • Dùng Visibility Range (3D): Đặt visibility_range_end trên GPUParticles3D để các hệ thống particle ở xa ngừng được render. Đây là lợi ích lớn cho thế giới mở.
  • FPS cố định: Đặt fixed_fps trên node particle (ví dụ 30) để giới hạn số lần cập nhật mô phỏng. Particle vẫn được nội suy mượt mà nhưng dùng ít thời gian GPU hơn.
  • One-shot cho các cú bùng phát: Với các hiệu ứng kiểu vụ nổ, hãy dùng one_shot = true cùng explosiveness = 1.0. Node sau đó có thể được giải phóng khi hết vòng đời.
  • Dùng LOD cho 3D: Kết hợp visibility_range_begin/end với nhiều node particle ở các mức chi tiết khác nhau để tạo hiệu ứng particle theo mức độ chi tiết (LOD).
  • Tránh overdraw: Nhiều particle bán trong suốt chồng lên nhau = fill rate tốn kém. Hãy giảm amount và thay vào đó dùng particle lớn hơn với alpha thấp hơn.

11. Chuyển đổi từ Godot 3 sang 4

Nếu bạn đang chuyển một dự án từ Godot 3 sang Godot 4, dưới đây là những thay đổi quan trọng nhất về particle:

Godot 3 Godot 4 Ghi chú
Particles2D GPUParticles2D Đổi tên
Particles (3D) GPUParticles3D Đổi tên
ParticlesMaterial ParticleProcessMaterial Đổi tên kèm tính năng mới
CPUParticles2D CPUParticles2D Không đổi
CPUParticles CPUParticles3D Đổi tên cho rõ ràng
No sub-emitters sub_emitter_mode Mới trong Godot 4
No trails trail_enabled Mới trong Godot 4
flag_align_y particle_flag_align_y Đổi tên thuộc tính
Mẹo: Trình chuyển đổi dự án của Godot xử lý hầu hết các thay đổi tên một cách tự động. Tuy nhiên, mọi tham chiếu ParticlesMaterial trong GDScript cần được cập nhật thủ công thành ParticleProcessMaterial.

Tự động hóa hiệu ứng particle với MCP Pro

Bạn muốn AI tạo hiệu ứng particle giúp bạn? Godot MCP Pro bao gồm các công cụ particle chuyên dụng với preset cho lửa, khói, mưa, tuyết, tia lửa và nhiều hơn nữa. Toàn quyền kiểm soát material, dải màu chuyển sắc và hình dạng emission chỉ trong một cuộc trò chuyện.

  • create_particles
  • set_particle_material
  • set_particle_color_gradient
  • apply_particle_preset
  • get_particle_info
Nhận MCP Pro — $15