작성일 :

물리에서 파티클로

PhysicsOptimization 시리즈에서 물리 서브시스템의 최적화를 다루었습니다. 충돌 검사 비용을 줄이고, 물리 시뮬레이션의 반복 횟수를 제한하고, 레이어 마스크로 불필요한 연산을 제거하는 방법을 확인했습니다. 물리 서브시스템이 게임 로직을 위한 것이라면, 파티클 시스템(Particle System)은 시각 효과를 위한 서브시스템입니다.


연기, 불꽃, 폭발, 먼지, 빗방울, 마법 이펙트 등 게임에서 흔히 보는 시각 효과는 대부분 파티클 시스템으로 구현됩니다. 작은 입자(파티클) 수백 ~ 수천 개를 생성하고, 각각의 위치, 속도, 크기, 색상, 수명을 매 프레임 갱신하며, 화면에 그려내는 구조입니다.

파티클 시스템은 CPU와 GPU 양쪽에 비용이 발생합니다. CPU가 시뮬레이션을, GPU가 렌더링을 담당하는 구조입니다.

모바일에서 이 비용은 데스크톱보다 민감하게 프레임 예산에 영향을 미칩니다. 파티클 하나의 비용은 작지만, 수백 개가 동시에 활성화되면 CPU 시뮬레이션과 GPU 렌더링 양쪽에서 프레임 예산을 압박합니다.


이 글에서는 파티클 시스템의 비용 구조를 CPU 시뮬레이션과 GPU 렌더링으로 나누어 분석하고, 모바일에서 가장 큰 비용인 오버드로우의 원인을 파악합니다. 이어서 파티클 수 예산, Prewarm 스파이크 회피, Culling Mode 설정, GPU Instancing 적용까지 각 비용을 줄이는 방법을 하나씩 다룹니다.


파티클 비용 구조

파티클 시스템의 비용은 두 영역으로 나뉩니다. 하나는 CPU에서 발생하는 시뮬레이션 비용이고, 다른 하나는 GPU에서 발생하는 렌더링 비용입니다.

CPU 비용: 파티클 시뮬레이션

CPU는 매 프레임 활성 상태인 모든 파티클에 대해 다음 연산을 수행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
파티클 시뮬레이션 (CPU, 매 프레임)

활성 파티클 하나에 대한 처리:

  1) 수명 갱신
     현재 나이 += deltaTime
     나이 > 수명이면 파티클 제거

  2) 위치 갱신
     위치 += 속도 * deltaTime
     속도 += 가속도 * deltaTime

  3) 크기 변화
     크기 = 커브에서 현재 나이에 해당하는 값 읽기

  4) 색상/알파 변화
     색상 = 그라디언트에서 현재 나이에 해당하는 값 읽기

  5) 회전
     회전 += 회전 속도 * deltaTime

  6) 추가 모듈 (활성화된 경우)
     Noise, Collision, Force, SubEmitter 등

──────────────────────────────────────────────────────────
파티클 수 × 위 연산 = 프레임당 CPU 비용


파티클 수가 많아지면 이 연산이 선형적으로 증가합니다. 파티클 100개와 파티클 1,000개의 시뮬레이션 비용은 단순히 10배 차이입니다. 여기에 Noise 모듈이나 Collision 모듈처럼 비용이 높은 모듈이 활성화되어 있으면, 파티클당 연산량이 더 커집니다.


예를 들어 Collision 모듈은 파티클마다 물리 월드에 대한 충돌 검사를 수행합니다. 파티클 500개가 Collision 모듈을 사용하면, 매 프레임 500번의 레이캐스트(또는 유사한 검사)가 추가됩니다. 레이캐스트는 물리 엔진의 Broadphase와 Narrowphase를 거치는 연산이므로, 파티클 시뮬레이션 비용 위에 물리 비용이 중첩됩니다.

GPU 비용: 파티클 렌더링

GPU는 각 파티클을 화면에 그립니다. Unity의 파티클 시스템은 기본적으로 각 파티클을 빌보드(Billboard)(항상 카메라를 향하도록 회전하는 사각형 메쉬)로 렌더링합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
파티클 렌더링 (GPU)

파티클 하나의 렌더링 과정:

  1) 빌보드 메쉬 생성
     카메라를 향하는 사각형 (정점 4개, 삼각형 2개)

  2) 버텍스 셰이더
     사각형의 정점을 화면 좌표로 변환

  3) 래스터화
     사각형이 덮는 픽셀 영역 결정

  4) 프래그먼트 셰이더
     텍스처 샘플링, 색상 계산, 알파 블렌딩


파티클 하나의 메쉬는 정점 4개짜리 사각형이므로, 버텍스 처리 비용은 미미합니다. GPU 비용의 대부분은 프래그먼트 셰이더에서 발생합니다. 파티클이 화면에서 차지하는 면적이 클수록, 그리고 파티클끼리 겹칠수록 프래그먼트 셰이더 실행 횟수가 증가합니다.


파티클 오버드로우: 모바일 최대 비용

파티클 시스템에서 GPU 비용의 핵심은 오버드로우(Overdraw)입니다. 오버드로우는 화면의 같은 픽셀을 여러 오브젝트가 순서대로 덮어 그리는 현상입니다.

픽셀이 덮어 그려질 때마다 프래그먼트 셰이더가 한 번씩 실행되므로, 오버드로우 수치가 클수록 프래그먼트 셰이더 실행 횟수도 그만큼 늘어납니다. GPU 아키텍처 (2) - 모바일 GPU와 TBDR에서 다룬 것처럼, 모바일 GPU에서 오버드로우는 제한된 프래그먼트 셰이더 예산을 직접적으로 소모합니다.


파티클 이펙트에서 오버드로우가 심한 이유는 두 가지입니다.

반투명 렌더링

파티클은 대부분 반투명(Alpha Blending)으로 렌더링됩니다. 연기가 서서히 사라지고, 불꽃이 빛을 내고, 먼지가 배경을 비쳐 보이려면 알파 값이 필요하기 때문입니다.


불투명 오브젝트는 깊이 버퍼에 “이 픽셀에 가장 가까운 물체의 거리”를 기록합니다. 뒤에 있는 물체가 같은 픽셀을 그리려 하면 깊이 비교에서 걸러지므로 프래그먼트 셰이더가 실행되지 않고, 모바일 GPU의 오버드로우 대응 기술(Mali의 FPK, Adreno의 LRZ, Apple의 HSR)도 이 깊이 정보를 이용하여 가려진 프래그먼트를 미리 건너뜁니다.

반투명 오브젝트는 뒤가 비쳐 보여야 하므로 깊이 값을 기록하지 않습니다. 깊이 값을 기록하면 뒤에 있는 배경이나 다른 파티클이 깊이 테스트에서 폐기되어 보이지 않게 되기 때문입니다. 깊이 값이 없으므로 FPK, LRZ, HSR 모두 동작하지 않고, 파티클이 겹치면 겹친 수만큼 프래그먼트 셰이더가 반드시 실행됩니다.

공간적 밀집

이펙트는 좁은 공간에 많은 파티클이 집중되는 특성이 있습니다. 폭발 이펙트는 원점 근처에서 수십 ~ 수백 개의 파티클이 겹치고, 연기 기둥은 좁은 영역에서 파티클이 수직으로 쌓입니다. 화면의 한 영역에 파티클이 밀집하면, 해당 영역의 오버드로우가 급증합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
파티클 오버드로우 예시

불꽃 이펙트 (화면 중앙에 50개 파티클이 겹침)

  화면 영역:
  ┌───────────────────────────────────────┐
  │                                       │
  │           ┌─────────────┐             │
  │           │ 파티클 1     │             │
  │           │ ┌─────────┐ │             │
  │           │ │파티클 2  │ │             │
  │           │ │┌───────┐│ │             │
  │           │ ││파티클 3││ │             │
  │           │ ││  ...  ││ │             │
  │           │ │└───────┘│ │             │
  │           │ └─────────┘ │             │
  │           └─────────────┘             │
  │                                       │
  └───────────────────────────────────────┘

  겹치는 영역의 한 픽셀:
  ─────────────────────────────────────────
  파티클 1 프래그먼트 셰이더 실행  (블렌딩)
  파티클 2 프래그먼트 셰이더 실행  (블렌딩)
  파티클 3 프래그먼트 셰이더 실행  (블렌딩)
  ...
  파티클 50 프래그먼트 셰이더 실행 (블렌딩)
  ─────────────────────────────────────────
  → 이 픽셀은 50번 셰이딩됨
  → 오버드로우 = 50×


모바일 GPU의 필레이트(Fill Rate) — 초당 처리 가능한 프래그먼트 수 — 는 제한적입니다. 파티클 이펙트 하나가 화면 중앙의 200×200 픽셀 영역에 오버드로우 30×를 발생시킨다면, 해당 영역에서만 200 × 200 × 30 = 1,200,000번의 프래그먼트 셰이딩이 추가됩니다. 이런 이펙트가 동시에 여러 개 활성화되면 프레임 예산이 초과됩니다.

오버드로우 줄이기

파티클 오버드로우는 파티클 수, 파티클 크기, 셰이더 복잡도 세 측면에서 줄일 수 있습니다.

파티클 수 — 겹치는 파티클 수가 줄어들면 오버드로우가 직접적으로 감소합니다. 파티클 100개로 표현하던 연기를 30개로 줄이면, 오버드로우도 대략 1/3로 줄어듭니다. 파티클 하나에 더 풍부한 텍스처를 사용하면, 적은 수의 파티클로도 시각적 밀도가 유지됩니다.

파티클 크기 — 파티클 수가 같더라도, 각 파티클이 화면에서 차지하는 면적이 작을수록 겹치는 영역이 줄어듭니다. 같은 이펙트를 커다란 빌보드 10개로 표현할 때보다 작은 빌보드 10개로 표현할 때 겹침 면적이 적고, 오버드로우도 낮아집니다.

셰이더 복잡도 — 파티클 셰이더가 복잡할수록 한 번의 프래그먼트 셰이딩 비용이 커지고, 오버드로우의 영향이 배가됩니다. Unlit(조명 없음) 셰이더를 사용하면 조명 계산과 노멀 맵 연산이 빠지므로 프래그먼트당 비용이 줄어들고, 텍스처 샘플링 횟수가 적을수록 차이가 커집니다.


파티클 수 예산

오버드로우를 줄이는 가장 직접적인 방법은 파티클 수 자체를 제한하는 것입니다. 게임 전체에서 동시에 활성화할 수 있는 파티클의 총 수를 미리 정해두고, 이 상한을 넘지 않도록 관리하는 방식을 파티클 수 예산(Particle Budget)이라고 합니다.


모바일 권장 예산

모바일 환경에서의 파티클 수 예산은 기기 성능과 해상도에 따라 달라지지만, 대략적인 기준은 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
모바일 파티클 수 예산 (권장)

화면 전체 동시 활성 파티클 수: 200 ~ 500개

기기 등급별 참고:

  저사양 (2~3년 전 중급 기기)
  ──────────────────────────────────────
  동시 파티클: ~200개
  이펙트당 파티클: 10~30개
  동시 활성 이펙트: 5~10개

  중사양 (현세대 중급 기기)
  ──────────────────────────────────────
  동시 파티클: ~400개
  이펙트당 파티클: 20~50개
  동시 활성 이펙트: 8~15개

  고사양 (현세대 플래그십)
  ──────────────────────────────────────
  동시 파티클: ~500개
  이펙트당 파티클: 30~80개
  동시 활성 이펙트: 10~20개


위 수치는 불투명 파티클 기준이며, 반투명 파티클이 많을수록 오버드로우로 인해 예산이 줄어듭니다. 파티클 셰이더의 복잡도, 파티클 크기, 해상도, 다른 렌더링 부하의 비중에 따라서도 달라지므로, 프로파일링으로 실제 비용을 검증해야 합니다.

Max Particles 설정

Unity의 파티클 시스템에는 Max Particles 설정이 있습니다. 이 값은 해당 파티클 시스템이 동시에 존재할 수 있는 파티클의 최대 수를 제한하며, 기본값은 1000입니다. Max Particles에 도달하면 새 파티클은 생성되지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Max Particles 동작

Max Particles = 50으로 설정된 연기 이펙트:

시간 →
  t=0   파티클 생성 시작
  t=1   활성 파티클: 20개 (계속 생성 중)
  t=2   활성 파티클: 40개 (계속 생성 중)
  t=3   활성 파티클: 50개 (상한 도달)
  t=4   활성 파티클: 50개 (새 파티클 생성 차단)
        수명이 다한 파티클이 소멸하면 그 자리에 새 파티클 생성

──────────────────────────────────────────────────────────
Max Particles가 없으면:
  Emission Rate가 높고 수명이 긴 경우, 파티클이 계속 쌓임
  수천 개까지 늘어날 수 있음


모바일 이펙트에서 이 기본값을 그대로 두면, 이펙트 하나에서 파티클이 1000개까지 생성될 수 있어 프레임 예산을 압박합니다. Max Particles를 시각적으로 필요한 최소 수준으로 낮추면, Emission Rate가 높거나 수명이 긴 이펙트에서도 파티클 수가 상한을 넘지 않습니다.

이펙트별 파티클 수 가이드라인

이펙트의 종류에 따라 적정 파티클 수가 크게 달라집니다. 순간적으로 생성되었다가 빠르게 소멸하는 타격 이펙트는 5~15개로도 충분하고, 화면 전체를 덮는 비나 눈은 50~100개가 필요합니다.

비나 눈 같은 환경 이펙트는 파티클 수가 많지만, 개별 파티클이 작고 셰이더가 단순하여 오버드로우 영향이 상대적으로 적습니다. 반면 폭발이나 마법 이펙트는 파티클이 크고 반투명이며 밀집되어 있어, 파티클 수 대비 오버드로우 영향이 큽니다.


Prewarm과 시뮬레이션 스파이크

동시 활성 파티클의 총 수가 예산 안에 있더라도, 특정 한 프레임에 CPU 비용이 몰리면 스파이크가 발생합니다. 파티클이 0개에서 서서히 늘어나면 비용이 프레임에 걸쳐 분산되지만, Prewarm이 켜져 있으면 첫 프레임에 수 초 분량의 시뮬레이션을 한꺼번에 실행하여 수백 개 파티클의 생성과 갱신이 단일 프레임에 집중됩니다.

Prewarm의 동작

Prewarm은 파티클 시스템이 활성화되는 순간, 한 루프 사이클(duration) 분량의 시뮬레이션을 한꺼번에 실행하여 파티클을 배치합니다.

Prewarm 없이 모닥불 이펙트를 켜면, 파티클이 0개에서 시작해 시간이 지나면서 서서히 늘어납니다. duration(예: 5초)이 지나야 생성과 소멸이 균형을 이룬 정상 상태가 됩니다. Prewarm을 켜면 첫 프레임에 5초 분량을 fast-forward — 실제 시간을 기다리지 않고 시뮬레이션만 한꺼번에 진행 — 하므로, 첫 프레임부터 활활 타오르는 정상 상태로 시작합니다.

Prewarm은 Looping(재생이 끝나면 처음부터 반복)이 활성화된 파티클 시스템에서만 동작합니다. Looping이 없는 파티클 시스템은 한 번 재생하고 끝나므로, 한 사이클을 fast-forward하면 파티클이 전부 소멸한 상태가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Prewarm Off vs On

Prewarm Off (기본):
──────────────────────────────────────
  t=0  파티클 시스템 활성화
       ○                              파티클 1개
  t=1  ○ ○ ○                          파티클 3개
  t=2  ○ ○ ○ ○ ○ ○                    파티클 6개
  t=3  ○ ○ ○ ○ ○ ○ ○ ○ ○ ○           파티클 10개 (정상 상태)

  → 정상 상태까지 서서히 도달
  → 자연스럽지만, 켜는 순간 비어 보임


Prewarm On:
──────────────────────────────────────
  t=0  파티클 시스템 활성화
       ○ ○ ○ ○ ○ ○ ○ ○ ○ ○           파티클 10개 (즉시 정상 상태)
  t=1  ○ ○ ○ ○ ○ ○ ○ ○ ○ ○           파티클 10개
  t=2  ○ ○ ○ ○ ○ ○ ○ ○ ○ ○           파티클 10개

  → 활성화 순간부터 완전한 이펙트
  → 첫 프레임에 모든 파티클을 한꺼번에 시뮬레이션


Prewarm의 비용

Prewarm이 문제가 되는 이유는 활성화되는 첫 프레임에 시뮬레이션 비용이 집중되기 때문입니다.


Prewarm이 켜진 파티클 시스템을 활성화하면 Unity는 한 루프 사이클(Duration) 분량의 시뮬레이션을 한 프레임 안에서 일괄 수행합니다. Duration이 5초이고 Emission Rate가 초당 20개라면 첫 프레임에서 최대 100개(5초 × 20개/초)의 파티클을 생성하고, 각 파티클의 위치, 속도, 크기, 색상을 한 사이클 분량만큼 계산해야 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
Prewarm 스파이크 (CPU 부하)

Prewarm Off — 비용이 프레임에 걸쳐 분산:
  t=0  ▎           0.05ms  (파티클 1개)
  t=1  ██           0.15ms  (파티클 3개)
  t=2  ████         0.30ms  (파티클 6개)
  t=3  ██████       0.50ms  (정상 상태)

Prewarm On — 첫 프레임에 스파이크:
  t=0  ████████████████████████  2.0ms  (한 사이클 전체를 시뮬레이션)
  t=1  ██████       0.50ms  (정상 상태)
  t=2  ██████       0.50ms
  t=3  ██████       0.50ms


이 스파이크는 파티클 시스템의 Duration이 길수록, Emission Rate가 높을수록, 활성화된 모듈이 많을수록 커집니다. 모바일에서 30fps(33.3ms)의 프레임 예산 중 2~3ms가 Prewarm 하나에 소비되면, 프레임 예산의 6~9%가 이펙트 초기화 한 건에만 사용됩니다. 여러 이펙트가 동시에 Prewarm으로 활성화되면 프레임 드롭이 발생할 수 있습니다.


Prewarm 대안

Prewarm을 끄면 활성화 순간 파티클이 비어 보이지만, 스파이크 없이도 정상 상태로 시작할 수 있습니다.


이펙트를 미리 활성화해 둡니다. 씬 로드 시점이나 화면 전환 중에 파티클 시스템을 미리 켜놓으면 실제로 플레이어가 해당 이펙트를 보는 시점에는 이미 정상 상태에 도달해 있습니다. 모닥불이나 환경 연기처럼 씬에 상시 존재하는 이펙트에 적합합니다.

ParticleSystem.Simulate() 메서드를 사용합니다. 이 메서드는 지정한 시간만큼의 시뮬레이션을 수행합니다. Prewarm과 동작은 동일하지만 호출 시점을 개발자가 직접 제어합니다. 로딩 화면이나 씬 전환 중처럼 프레임 예산에 여유가 있는 시점에 호출하면 스파이크를 피할 수 있습니다.


파티클 Culling

카메라 밖에 있는 파티클 시스템도 기본적으로 CPU 시뮬레이션을 계속 수행합니다. 화면에 그려지지 않으므로 GPU 비용은 없지만, CPU 비용은 그대로 발생합니다.


Culling 모드

Unity의 파티클 시스템에는 카메라 밖에 있을 때의 동작을 제어하는 Culling Mode 설정이 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
파티클 Culling 모드

  카메라 Frustum
  ┌─────────────────────────────┐
  │                             │
  │    ○ ○ ○  파티클 시스템 A     │    → 시뮬레이션 + 렌더링
  │                             │
  └─────────────────────────────┘
                              ○ ○ ○  파티클 시스템 B
                              → Culling Mode에 따라 결정

──────────────────────────────────────────────────────────

Automatic (기본)
  루핑 시스템에는 Pause, 비루핑(one-shot) 시스템에는
  Always Simulate를 적용
  Unity가 Looping 여부에 따라 자동 결정

Pause And Catch-up
  화면 밖에서는 시뮬레이션 일시정지
  화면에 다시 들어오면 밀린 시간만큼 시뮬레이션을 한꺼번에 수행
  → 화면 복귀 시 스파이크 가능성

Pause
  화면 밖에서는 시뮬레이션 일시정지
  화면에 다시 들어오면 정지했던 상태에서 이어서 시뮬레이션
  → 시간 흐름이 끊기지만 스파이크 없음

Always Simulate
  화면 밖에서도 시뮬레이션 계속 실행
  → CPU 비용이 항상 발생


Always Simulate는 화면 밖에서도 시뮬레이션을 멈추지 않으므로 CPU 비용이 계속 발생합니다. 카메라가 돌아왔을 때 이펙트의 시간 흐름이 정확해야 하는 경우 — 컷씬 연출 등 — 에 사용합니다.

Pause And Catch-up은 화면 밖에서 시뮬레이션을 멈추지만, 카메라가 돌아오면 밀린 시간만큼 한꺼번에 시뮬레이션하므로 Prewarm과 같은 스파이크가 발생합니다.

AutomaticPause는 화면 밖에서 시뮬레이션을 멈추고, 카메라가 돌아오면 멈춘 지점에서 이어서 재생합니다. 이펙트의 시간 흐름이 실제와 어긋나지만, 플레이어는 카메라 밖의 시간 경과를 알 수 없으므로 차이를 인지하지 못합니다.


화면 밖 이펙트 비활성화

Culling Mode는 카메라 Frustum을 기준으로 동작하므로, Frustum 안에 있기만 하면 거리에 관계없이 시뮬레이션이 실행됩니다. 카메라에서 수백 미터 떨어진 이펙트는 화면에서 몇 픽셀에 불과하거나 아예 식별되지 않지만, CPU 시뮬레이션 비용은 가까이 있을 때와 동일합니다. 거리 기반으로 파티클 시스템의 게임 오브젝트를 비활성화하면 시뮬레이션과 렌더링이 모두 제거됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
거리 기반 이펙트 비활성화

             활성 범위
  ┌──────────────────────────────┐
  │                              │
  │    ◉ 카메라                  │
  │                              │
  │          ○ 이펙트 A  ← 활성   │
  │                              │
  └──────────────────────────────┘
               ○ 이펙트 B  ← 비활성

                     ○ 이펙트 C  ← 비활성

  거리 < 활성 범위    → 이펙트 활성화
  거리 > 비활성 범위  → 이펙트 비활성화 (게임 오브젝트 Off)


오픈 월드나 넓은 맵에서 환경 이펙트(모닥불, 증기, 먼지)가 많을 때 비용 절감이 큽니다.

활성 범위와 비활성 범위가 같은 거리이면, 플레이어가 경계 근처에서 앞뒤로 움직일 때 이펙트가 매 프레임 켜졌다 꺼지는 떨림이 발생합니다. 히스테리시스(Hysteresis) — 활성화 거리와 비활성화 거리를 서로 다르게 설정하는 방식 — 로 이를 방지합니다. 30m 이내로 접근하면 켜지고 50m 이상 멀어져야 꺼지도록 설정하면, 20m의 여유 구간이 떨림을 흡수합니다.


GPU Instancing을 이용한 파티클 렌더링

파티클 렌더링에는 오버드로우 외에 드로우 콜(Draw Call) 비용도 있습니다. 드로우 콜 하나하나에 CPU 오버헤드가 따르므로, 드로우 콜 수가 많아지면 CPU 병목이 발생합니다.

기본 파티클 렌더링의 드로우 콜

하나의 파티클 시스템은 기본적으로 하나의 드로우 콜을 발생시킵니다. Unity는 동일한 파티클 시스템에 속한 파티클들을 하나의 메쉬 버퍼에 모아서 한 번에 그립니다(Dynamic Batching). 이 때문에 파티클 시스템 자체의 드로우 콜 수는 대부분의 경우 문제가 되지 않습니다.

파티클의 Render Mode를 빌보드가 아닌 Mesh로 설정하면 드로우 콜이 늘어날 수 있습니다. 메쉬 파티클은 각 파티클이 지정된 3D 메쉬(큐브, 스피어, 커스텀 메쉬 등)로 렌더링되므로 빌보드보다 정점 수가 많고, Dynamic Batching의 정점 수 제한에 걸리기 쉽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
빌보드 vs 메쉬 파티클

빌보드 (기본):
  파티클 1개 = 정점 4개, 삼각형 2개
  파티클 100개 = 정점 400개
  → Dynamic Batching으로 드로우 콜 1회

메쉬 (예: 정점 100개짜리 바위 조각):
  파티클 1개 = 정점 100개, 삼각형 ~60개
  파티클 100개 = 정점 10,000개
  → Dynamic Batching의 vertex attribute 제한(900) 초과 가능
  → 드로우 콜 증가

메쉬 파티클은 빌보드(정점 4개)보다 파티클당 정점 수가 많으므로, 파티클 수가 늘어나면 Dynamic Batching 제한을 넘어 드로우 콜이 늘어납니다.


GPU Instancing 적용

메쉬 파티클에 GPU Instancing을 적용하면, 동일한 메쉬를 여러 번 그릴 때 드로우 콜이 줄어듭니다. GPU Instancing은 하나의 드로우 콜로 동일한 메쉬를 여러 인스턴스(위치, 크기, 색상 등이 다른 복제본)로 렌더링하는 기술입니다.

GPU Instancing이 동작하려면 세 가지 조건이 필요합니다. Render Mode가 Mesh여야 하고, 셰이더가 Instancing을 지원해야 하며, Renderer 모듈의 Enable GPU Instancing이 켜져 있어야 합니다.

URP(Universal Render Pipeline)의 기본 파티클 셰이더(Universal Render Pipeline/Particles/Unlit, Universal Render Pipeline/Particles/Lit)는 Instancing을 기본 지원합니다. 커스텀 셰이더는 #pragma multi_compile_instancing, #pragma instancing_options procedural:vertInstancingSetup 선언과 UnityStandardParticleInstancing.cginc 인클루드가 필요합니다.

GPU Instancing은 빌보드 파티클에는 적용되지 않습니다. 빌보드 파티클은 이미 Dynamic Batching으로 하나의 드로우 콜로 처리되기 때문입니다.


파티클 최적화 체크리스트

모바일 파티클 이펙트에서 비용에 영향을 주는 항목을 정리한 체크리스트입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
파티클 최적화 체크리스트

항목                          비용 영향
──────────────────────────────────────────────────────────
Max Particles                기본값 1000
                             파티클 수 상한 → CPU 시뮬레이션 비용 상한

Emission Rate                초당 생성 수 → 동시 활성 파티클 수에 직결

파티클 크기                   빌보드 면적 → 오버드로우 면적에 비례

셰이더 복잡도                 프래그먼트당 비용
                             오버드로우 시 영향 배가

Prewarm                      첫 프레임에 한 사이클 시뮬레이션 스파이크
                             Simulate()로 호출 시점 제어 가능

Culling Mode                 Always Simulate는 화면 밖에서도 CPU 비용 발생
                             Pause는 화면 밖 CPU 비용 제거

Collision 모듈               파티클당 레이캐스트 추가
                             Quality가 높을수록 정밀도와 비용 증가

Noise 모듈                   Quality에 비례하여 파티클당 연산 증가

Sub Emitters                 부모 파티클 이벤트마다 자식 파티클 생성
                             파티클 수 증폭

GPU Instancing               메쉬 파티클의 드로우 콜 감소
                             빌보드에는 불필요 (Dynamic Batching 사용)

파티클에서 애니메이션으로

파티클 시스템이 시각 효과를 위한 서브시스템이라면 애니메이션은 캐릭터와 오브젝트의 움직임을 위한 서브시스템입니다.

애니메이션도 매 프레임 CPU에서 처리되며 리그 타입(Generic vs Humanoid), 압축 설정, Culling 모드에 따라 비용이 크게 달라집니다. 캐릭터 수가 늘어날수록 애니메이션의 CPU 비용이 프레임 예산에서 차지하는 비중도 커집니다. Part 2에서 이어서 다룹니다.



마무리

  • 파티클 시스템의 비용은 CPU 시뮬레이션(파티클 수 × 모듈 복잡도)과 GPU 렌더링(오버드로우 × 셰이더 복잡도)으로 나뉩니다.
  • 반투명 파티클은 깊이 값을 기록하지 않아 모바일 GPU의 오버드로우 방지 기술이 동작하지 않습니다.
  • Max Particles는 파티클 수의 상한을 제한하며, 모바일 전체 파티클 예산은 200~500개입니다.
  • Prewarm은 첫 프레임에 시뮬레이션 스파이크를 일으킵니다. Simulate() 메서드나 사전 활성화로 대체할 수 있습니다.
  • Culling Mode의 Automatic과 Pause는 화면 밖 시뮬레이션 비용을 제거합니다.
  • 메쉬 파티클에 GPU Instancing을 적용하면 드로우 콜이 줄어듭니다.

파티클 시스템의 비용은 “얼마나 많은 파티클이, 얼마나 넓은 면적을, 얼마나 복잡한 셰이더로 그리는가”로 요약됩니다. 이 글에서 다룬 항목들은 CPU 비용과 GPU 비용 두 축에서 이 세 변수를 줄이는 구조입니다.


Part 2에서는 매 프레임 CPU에서 처리되는 애니메이션 서브시스템을 다룹니다. Animator와 Animation(Legacy)의 차이, 리그 타입(Generic vs Humanoid), 애니메이션 압축, Culling 모드, GPU Skinning을 순서대로 살펴봅니다.



관련 글

시리즈

전체 시리즈

Tags: Unity, 모바일, 이펙트, 최적화, 파티클

Categories: ,