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를 사용하세요. Compatibility 렌더러 지원이 필요하거나, 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)
  • explosiveness0.0 = 연속 스트림, 1.0 = 모든 파티클을 한 번에
  • randomness방출 타이밍에 대한 랜덤 오프셋 (0.0-1.0)
  • speed_scale시뮬레이션 속도 배수
  • emitting시스템이 현재 방출 중인지 여부

4. ParticleProcessMaterial 심층 분석

ParticleProcessMaterial은 개별 파티클이 수명 동안 어떻게 동작하는지 제어합니다. 모든 방향 관련 프로퍼티는 2D 파티클에서도 Vector3를 사용합니다(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. 색상 그라디언트

ParticleProcessMaterialcolor_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
팁: 그라디언트는 항상 알파 = 0으로 끝내서 파티클이 갑자기 사라지지 않고 부드럽게 페이드아웃되도록 하세요.

단색 파티클의 경우 그라디언트 대신 플랫한 color 프로퍼티를 설정할 수도 있습니다:

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

6. 방출 형상

방출 형상은 파티클이 어디서 생성되는지 결정합니다. ParticleProcessMaterialemission_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

연기

회색 그라디언트, 느린 상승 이동, 큰 퍼짐, 옅은 느낌을 위한 낮은 알파.

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 노드입니다. 다음과 같은 경우에 이상적입니다:

  • 불 파티클에서 튀어나오는 불꽃
  • 불꽃놀이 폭발 뒤의 연기 자국
  • 충돌 시 튀는 물방울
  • 2차 폭발

서브 이미터를 설정하려면:

# 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. 퍼포먼스 팁

  • 수량 vs 수명의 트레이드오프: 화면에 보이는 전체 파티클 수 = amount. lifetime을 늘리면 파티클이 더 오래 누적됩니다. 이를 보정하려면 amount를 줄이거나, 더 조밀한 이펙트를 위해 늘리세요.
  • Visibility Range 활용 (3D): GPUParticles3D에 visibility_range_end를 설정하면 멀리 있는 파티클 시스템이 렌더링을 멈춥니다. 오픈 월드에서 큰 이득이 됩니다.
  • 고정 FPS: 파티클 노드에 fixed_fps(예: 30)를 설정하면 시뮬레이션 업데이트가 제한됩니다. 파티클은 여전히 부드럽게 보간되지만 GPU 시간을 덜 사용합니다.
  • 버스트용 One-shot: 폭발과 같은 이펙트에는 one_shot = trueexplosiveness = 1.0을 사용하세요. 수명이 끝난 뒤 노드를 해제할 수 있습니다.
  • 3D에서 LOD 사용: visibility_range_begin/end를 서로 다른 디테일 수준의 여러 파티클 노드와 조합해 LOD(Level of Detail) 파티클 이펙트를 구현하세요.
  • 오버드로 피하기: 반투명 파티클이 많이 겹치면 = 비싼 필레이트. amount를 줄이고, 대신 알파가 낮은 더 큰 파티클을 사용하세요.

11. Godot 3에서 4로 마이그레이션

프로젝트를 Godot 3에서 Godot 4로 마이그레이션하는 경우, 파티클과 관련된 주요 변경 사항은 다음과 같습니다:

Godot 3 Godot 4 비고
Particles2D GPUParticles2D 이름 변경
Particles (3D) GPUParticles3D 이름 변경
ParticlesMaterial ParticleProcessMaterial 새 기능과 함께 이름 변경
CPUParticles2D CPUParticles2D 변경 없음
CPUParticles CPUParticles3D 명확성을 위해 이름 변경
No sub-emitters sub_emitter_mode Godot 4에서 신규
No trails 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