Godot 4의 GPUParticles2D/3D — 완벽 가이드
1. 소개
Godot 4는 기본 파티클 시스템 노드로 GPUParticles2D와 GPUParticles3D를 사용합니다. 이들은 전적으로 GPU에서 실행되므로, 대량의 파티클을 다룰 때 CPU 기반 노드보다 훨씬 빠릅니다. Godot 3의 옛 ParticlesMaterial은 파티클 동작을 더 세밀하게 제어할 수 있는 ParticleProcessMaterial로 대체되었습니다.
CPUParticles2D와 CPUParticles3D는 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)explosiveness— 0.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. 색상 그라디언트
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
단색 파티클의 경우 그라디언트 대신 플랫한 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
연기
회색 그라디언트, 느린 상승 이동, 큰 퍼짐, 옅은 느낌을 위한 낮은 알파.
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_1에QuadMesh나 커스텀 메시를 사용할 수 있음 - 메시 파티클은
StandardMaterial3D나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)
.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 = true와explosiveness = 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 |
프로퍼티 이름 변경 |
ParticlesMaterial 참조는 ParticleProcessMaterial로 수동 업데이트해야 합니다.
MCP Pro로 파티클 이펙트 자동화하기
AI가 파티클 이펙트를 대신 만들어 주길 원하시나요? Godot MCP Pro에는 불, 연기, 비, 눈, 불꽃 등을 위한 프리셋이 포함된 전용 파티클 도구가 있습니다. 머티리얼, 그라디언트, 방출 형상을 단 한 번의 대화로 완전히 제어하세요.
- create_particles
- set_particle_material
- set_particle_color_gradient
- apply_particle_preset
- get_particle_info