파티클 시스템의 최적화 기법
파티클 수·콜리전 방식·물리 계산·오버드로·LOD를 조정하고 Niagara 프로파일러로 병목을 측정해 비용을 줄입니다.
7장에서 더한 충돌과 물리, 유체 흉내는 효과를 실감 나게 만들지만 그만큼 비용도 늘립니다. 그런데 충돌의 비용과 물리 계산의 비용, 렌더링의 비용은 서로 다른 원인에서 생기므로, 아무 곳이나 줄여서는 효과만 나빠지고 프레임은 그대로일 수 있습니다. 그래서 먼저 같은 카메라와 품질, 하드웨어, 동시 시스템 수로 기준 장면을 정한 뒤, 가장 큰 비용부터 줄입니다. 이 글에서는 입자 수와 충돌, 스크립트, 렌더링의 비용을 각각 줄이는 방법을 정리하고, CPU 조회의 예산을 숫자로 잡아 본 뒤 측정하고 다시 비교하는 순서를 살펴봅니다.
동시에 살아 있는 입자와 시스템의 수
입자 수는 Spawn Rate와 버스트의 개수, Lifetime을 함께 조절해 정합니다. 계속 만드는 효과라면 정상 상태에서 살아 있는 입자 수를 대략 생성률 × 평균 수명으로 어림할 수 있습니다. 입자가 늘어날 때 비용이 꼭 기하급수적으로 늘어나는 것은 아니며, 입자 하나에 드는 계산과 화면에서의 겹침, 시스템을 관리하는 비용에 따라 달라집니다.
이미터의 메모리 할당에 관한 수치를 살아 있는 입자 수의 강제 상한으로 오해하지 않습니다. 모든 버전의 스케일러빌리티에 Max Particles라는 공통 항목이 있는 것도 아닙니다. 상한은 실제 생성량과 수명, 동시에 재생할 시스템의 예산으로 설계하고, 그 결과를 나이아가라 이펙트 타입(Effect Type)과 시스템, 이미터의 스케일러빌리티 설정에 반영합니다.
충돌과 물리 계산 줄이기
충돌과 물리 쪽에서 줄일 수 있는 것과, 줄이면서도 지켜야 할 결과를 정리하면 다음과 같습니다.
| 줄일 대상 | 조정 방법 | 지켜야 할 결과 |
|---|---|---|
| CPU 트레이스의 수 | 핵심 입자만 충돌시키고 수명과 조회 범위를 제한 | 화면에서 필요한 접촉 반응 |
| GPU 충돌 | Scene Depth와 Distance Fields의 지원과 오차를 비교 | 카메라 밖이나 얇은 표면에서 허용할 범위 |
| 충돌 뒤의 계산 | 첫 충돌 뒤에 없애거나, 실제로 지원되는 정지나 횟수 정책을 씀 | 필요한 반발과 잔상 |
| 힘과 노이즈 | 겹치는 모듈을 지우고, 필요 없는 품질 단계에서 뺌 | 전체 궤적과 실루엣 |
| 무작위 입력 | 바뀌지 않는 성질은 Spawn에서 한 번 기록 | 입자마다의 차이 |
Distance Fields가 언제나 가장 정확하고 가장 싼 것은 아닙니다. CPU 트레이스와 GPU의 표면 조회는 서로 다른 데이터를 쓰므로 목표 장면에서 비교합니다. Restitution을 단순한 숫자로 바꾸거나 Mass를 고정한다고 계산이 저절로 크게 줄지도 않습니다. 기본 Drag는 질량과 상관없으므로, 질량을 다르게 준 것을 곧바로 비용의 차이로 해석하지 않습니다.
스크립트와 데이터 줄이기
스크립트에서는 먼저 쓰지 않는 모듈을 지우고, Static Switch 등으로 뺀 코드가 실제로 컴파일에서 빠지는지 확인합니다. 동적인 If나 Select를 넣었다는 이유만으로 고르지 않은 입력의 비싼 계산이 생략된다고 가정하지 않습니다.
거리와 0 이상의 기준값 R을 비교할 때는 LengthSquared와 R²을 함께 쓰면 제곱근을 피할 수 있습니다. 다만 거리 자체나 단위 방향이 필요한 계산까지 같은 식으로 바꾸지는 않습니다.
다음 틱까지 남겨 두는 사용자 정의 속성(Custom Attribute)은 입자마다 저장 공간을 씁니다. 잠깐 쓰는 중간 값과 다음 틱까지 유지할 속성을 구분하고, 실제 컴파일 결과를 확인합니다. 수명에 맞춘 변화에는 NormalizedAge를 커브의 입력으로 쓰면 의도가 분명해지는데, 이 값은 나이를 수명으로 나눈 것이라 수명이 끝난 입자를 없애지 않으면 1을 넘을 수 있습니다.
데이터 인터페이스를 조회할 때는 필요한 본과 표면, 데이터만 읽고 같은 표본을 다시 씁니다. 모든 데이터 인터페이스와 모든 호출의 비용이 같은 것은 아닙니다.
렌더링과 거리별 품질
렌더링에서도 줄일 곳이 여럿입니다. 들어오는 빛이 필요 없다면 Unlit을 검토하되, 조명에 반응해야 하는 먼지나 파편에는 알맞은 Lit 표현을 유지합니다. Additive도 넓은 반투명 면적이 겹치면 오버드로우를 만들고, Masked는 부드러운 반투명을 표현하지 못하므로, 성능만 보고 둘을 서로 바꾸지 않습니다. 알파를 0으로 만드는 것만으로 픽셀의 작업이 사라지지도 않습니다.
Light Renderer는 조명의 수와 반경을 제한하고, 지원되는 그림자 방식의 비용을 따로 봅니다. 모든 버전에 Intensity나 Affect Global Illumination 같은 같은 항목이 있다고 가정하지 않습니다. Mesh Renderer는 메시의 복잡도와 입자 수, 화면에서의 크기, 머티리얼의 비용을 함께 조절합니다.
거리별 품질은 이펙트 타입과 스케일러빌리티에서 거리와 가시성, 품질 단계마다 생성량과 기능을 분명히 정합니다. LOD 거리 하나를 정한다고 알맞은 모듈 조합이 저절로 만들어지지는 않습니다. 텍스처의 해상도와 밉, 압축은 컬러, 마스크, 노멀 같은 용도와 플랫폼에 맞추되, 알파가 필요한 텍스처를 알파가 없는 포맷으로 바꾸지 않습니다.
멀리서 충돌을 끄거나 생성량을 낮추는 정책은 출발점일 뿐입니다. 효과가 게임의 정보를 전달한다면, 멀리서도 필요한 타이밍과 실루엣이 남는지 확인합니다.
CPU 조회의 예산 잡기
예산을 숫자로 잡아 봅니다. 동시에 재생하는 효과가 3개이고, 각 효과가 초당 20개를 만들며 입자의 평균 수명은 4초라고 합시다. 다른 생성이나 제거가 없는 정상 상태라면 살아 있는 입자는 약 3 × 20 × 4 = 240개입니다. 모든 입자가 업데이트마다 CPU 트레이스를 정확히 한 번 하고 업데이트가 초당 60번이라면, 조회는 약 240 × 60 = 14,400회/초입니다.
이 계산은 조건을 정해 놓고 어림한 작업 수이며, 실제 모듈이 조회하는 횟수나 CPU 시간을 잰 값이 아닙니다. 그래도 어디를 줄이면 작업 수가 얼마나 주는지 미리 가늠하는 데 쓸모가 있습니다.
측정하고 다시 비교하기
비용은 도구로 잽니다. 나이아가라 디버거(Niagara Debugger)와 그 안의 FX Outliner에서는 활성 상태의 시스템과 이미터, 입자 수, 그리고 쓰는 버전이 제공하는 성능 정보를 확인합니다. 모든 모듈의 정확한 CPU, GPU 시간이 저절로 보인다고 가정하지 않습니다. stat Niagara는 전체 통계를 보여 주고, Unreal Insights는 필요한 트레이스 채널을 켜서 CPU와 GPU 시간, 메모리 할당을 분석하게 도와줍니다.
최적화는 다음 순서로 진행합니다. 먼저 기준 장면의 시간(ms)을 기록합니다. 다음으로 병목인 한 축을 고르고 설정을 하나만 바꿉니다. 그리고 같은 장면에서 시간과 화면 품질의 손실을 비교합니다. 결과가 예산 안에 들어오더라도, 가까이서 본 장면과 최대 동시 재생, 거리 전환에서 핵심 표현이 유지되는지 확인해야 합니다.
더 깊이: 입자 수 밖에서 생기는 비용
Scalability and Best Practices 문서는 입자 수 말고도 비용이 생기는 곳을 몇 가지 짚습니다. 같은 시스템의 인스턴스가 많을수록 게임 스레드에서 할 일이 늘어나므로, 화면에 보이는 입자가 적어도 동시에 재생하는 시스템이 많으면 비용이 커질 수 있습니다. 또 많은 입자를 한 프레임에 모두 만들면 끊김(hitch)이 생길 수 있어서, 문서는 생성을 여러 프레임에 나눠 작업을 분산하라고 권합니다. 큰 버스트 하나가 특정 프레임만 튀게 만든다면 이 방법을 먼저 검토합니다.
작업이 프레임의 어느 때에 실행되는지를 바꾸는 설정도 있습니다. 게임의 최신 데이터가 필요 없는 효과에서 Require Current Frame Data를 끄면, 나이아가라가 그 틱을 프레임 안에서 가능한 한 일찍 시작해 작업을 더 긴 시간에 걸쳐 나눌 수 있습니다. 반대로 캐릭터의 현재 위치처럼 이번 프레임의 값을 읽어야 하는 효과라면 이 설정을 유지합니다. 정렬도 숨은 비용이 될 수 있는데, 정렬을 켜면 정렬 작업이 기본으로 GPU에서 실행되어 GPU 부담을 더합니다. CPU 이미터라면 Niagara.GPUSorting.CPUToGPUThreshold와 Niagara.GPUCulling.CPUToGPUThreshold 콘솔 변수로 CPU에서 정렬하도록 강제할 수 있습니다.
정리
- 충돌과 물리, 렌더링의 비용은 원인이 다르므로, 같은 조건의 기준 장면을 정하고 가장 큰 비용부터 줄입니다.
- 입자 수는 생성률과 버스트, 수명으로 정하며, 메모리 할당 수치를 살아 있는 입자 수의 강제 상한으로 오해하지 않습니다.
- 충돌은 핵심 입자에만 쓰고 수명과 조회 범위를 제한하며, Distance Fields가 언제나 가장 정확하고 싸다고 가정하지 않습니다.
Static Switch로 뺀 코드가 실제로 빠지는지 확인하고, 거리 비교에는LengthSquared와 R²을 써서 제곱근을 피합니다.- 작업 수의 어림은 계획에 쓰고, 실제 효과는 디버거와
stat Niagara, Unreal Insights로 같은 장면에서 재어 확인합니다.
연습 문제
1. 수명을 반으로 줄이면
본문의 예에서 각 효과의 평균 수명만 2초로 낮추면, 살아 있는 입자 수와 같은 모델의 조회 횟수는 얼마가 될까요? 또 거리 오프셋 (30, 40, 0)cm에 대해 0 이상의 R=50cm로 LengthSquared ≤ R²을 판정해 보세요. R=-50cm를 그대로 제곱해 같은 반경으로 해석해도 될까요?
해설 보기
살아 있는 입자는 약 3 × 20 × 2 = 120개이고, 조회는 약 120 × 60 = 7,200회/초입니다.
거리의 제곱은 30² + 40² = 2500cm²이고 반경의 제곱도 50² = 2500cm²이므로, 같은 값을 포함하는 이 조건에서는 참입니다. 반경이 음수인 것은 잘못된 입력이므로, 제곱하면 값이 같아진다는 이유로 받아들이지 말고 먼저 거부하거나 정해 둔 규칙으로 처리합니다. 또 작업 수가 절반이 되었다고 CPU 시간도 절반이 되었다는 결론은, 같은 장면에서 실제로 잰 뒤에 내립니다.
2. 투명하게 만든 먼 연기
먼 거리의 연기 비용을 줄이려고 알파를 0으로 낮췄지만 GPU 시간이 거의 그대로입니다. 어떤 방법으로 바꾸면 좋을까요?
해설 보기
알파를 0으로 낮춰도 입자는 살아 있고 렌더러가 그리므로, 셰이더의 실행과 오버드로우가 그대로 남습니다.
이펙트 타입과 스케일러빌리티에서 거리에 따라 효과를 컬링하거나 생성량을 줄이는 방법으로 바꿉니다. 멀리서도 연기의 실루엣이 정보로 필요하다면, 입자 수를 줄이는 대신 크기를 키워 겹침을 줄이는 식으로 모양을 유지하는지 확인합니다.
3. 설정 하나만 바꾸는 이유
최적화를 하면서 생성률과 충돌, 머티리얼을 한꺼번에 바꿨더니 프레임 시간이 2ms 줄었습니다. 그런데 어떤 변경이 효과가 있었는지 알 수 없습니다. 어떻게 진행했어야 할까요?
해설 보기
설정을 한 번에 하나씩 바꾸고, 바꿀 때마다 같은 장면에서 시간과 화면 품질의 손실을 비교했어야 합니다. 여러 설정을 함께 바꾸면 어떤 변경이 시간을 줄였고 어떤 변경이 품질만 떨어뜨렸는지 구별할 수 없습니다.
지금이라도 기준 장면으로 되돌려 시간을 기록하고, 병목인 한 축부터 하나씩 다시 적용해 비교합니다. 효과가 없는 변경은 되돌려 화면 품질을 지킵니다.
비용을 줄이는 권장 사항은 Epic의 나이아가라 스케일러빌리티와 권장 사항에서, 디버깅 도구는 나이아가라 디버거 문서에서 확인할 수 있습니다.
이것으로 7장을 마칩니다. 7장에서는 움직이지 않는 표면과의 충돌에서 시작해, 움직이는 액터와 스켈레탈 메시에 입자를 부딪히고 붙이는 방법, 힘과 질량을 구분한 물리 시뮬레이션, 스프라이트와 격자로 만드는 유체 효과, 그리고 이런 효과의 비용을 줄이는 방법까지 살펴봤습니다. 다음 장의 블루프린트 제어부터는 블루프린트에서 나이아가라 시스템을 만들고 켜고 끄며, 게임플레이의 값과 이벤트에 효과를 연결하는 방법을 다룹니다.