데이터 인터페이스의 기본 개념
데이터 인터페이스가 메시·스켈레탈 메시·스플라인·그리드 정보를 나이아가라 실행 단계에 공급하는 구조를 이해합니다.
지금까지 만든 효과는 모두 나이아가라 안에서 정한 값만으로 움직였습니다. 그런데 실제 게임의 효과는 바깥의 정보를 알아야 할 때가 많습니다. 캐릭터의 손에서 불꽃이 피어나려면 손의 위치를 알아야 하고, 빗방울이 바닥에 닿아 튀려면 바닥이 어디 있는지 알아야 합니다. 나이아가라가 이런 바깥 정보를 읽어 쓸 수 있게 해 주는 것이 데이터 인터페이스(Data Interface)입니다. 이 글에서는 데이터 인터페이스가 무엇이고 어떤 종류가 있는지 살펴본 뒤, 모듈에서 데이터 인터페이스를 쓰는 흐름과 연결이 잘못되었을 때 확인할 곳을 정리합니다.
바깥 데이터를 읽는 통로
데이터 인터페이스(DI)는 나이아가라와 게임 월드, 블루프린트, C++ 코드, 다른 이미터 사이에서 데이터를 주고받는 통로입니다. 정확히는 모듈이 부를 수 있는 함수와 데이터에 접근하는 방법을 제공하는 특수한 데이터 타입이며, 나이아가라의 노드 그래프와 바깥의 데이터 원본 사이에서 번역기 역할을 한다고 생각하면 이해하기 쉽습니다. 데이터 인터페이스 자체와, 그것을 입력으로 받아 쓰는 모듈은 서로 다른 것입니다.
예를 들어 빗방울이 바닥에 닿을 때 물웅덩이 효과를 만들고 싶다면, 나이아가라가 바닥과 부딪혔다는 정보를 읽을 수 있어야 합니다. 이때 Collision 모듈은 CPU에서는 레이 추적으로, GPU에서는 장면 깊이나 거리장(distance field) 같은 지원되는 방법으로 표면을 검사합니다. 직접 스크립트를 만든다면 Collision Query 같은 데이터 인터페이스가 어떤 함수를 제공하는지 확인합니다. CPU에서 쓰는 질의를 GPU에서도 모두 같은 방식으로 부를 수 있는 것은 아닙니다.
데이터 인터페이스로 할 수 있는 일은 크게 네 가지입니다. 첫째, 지형의 높이나 표면의 재질, 주변 물체의 위치 같은 월드의 정보를 파티클에 적용할 수 있습니다. 둘째, 게임 로직에서 파티클 시스템의 값을 플레이 중에 바꾸거나, 반대로 파티클의 데이터를 게임 로직으로 내보낼 수 있습니다. 셋째, Particle Attribute Reader처럼 다른 이미터의 입자 데이터를 읽어 한 이미터의 입자가 다른 이미터에 영향을 주게 할 수 있습니다. 넷째, 미리 정의된 복잡한 함수나 패턴을 파티클에 적용할 수 있습니다.
대표적인 데이터 인터페이스
나이아가라는 여러 종류의 데이터 인터페이스를 제공하며, 각각 특정한 종류의 데이터를 다루는 데 맞춰져 있습니다. 대표적인 몇 가지를 살펴보겠습니다.
Static Mesh
Static Mesh 데이터 인터페이스(UNiagaraDataInterfaceStaticMesh)는 스태틱 메시 에셋의 데이터를 나이아가라에서 쓸 수 있게 합니다. 메시의 표면에 파티클을 만들고 그 자리의 정점(vertex) 색이나 UV를 읽거나, 정점의 위치나 노멀(normal)로 입자의 초기 위치와 방향을 정할 수 있습니다. 텍스처의 색이 필요하다면 읽어 온 UV로 텍스처를 따로 샘플링합니다. 메시의 크기나 바운딩 박스를 참고해 효과가 퍼지는 범위를 조절하는 데도 씁니다.
Skeletal Mesh
Skeletal Mesh 데이터 인터페이스는 캐릭터 같은 스켈레탈 메시의 데이터를 쓸 수 있게 합니다. 캐릭터의 특정 본(bone)이나 소켓에 파티클을 붙여 무기에서 연기가 뿜어 나오게 하거나, 애니메이션에 따라 파티클을 만들고 제어해 캐릭터가 달릴 때만 발밑에 먼지가 일게 할 수 있습니다.
Spline
Spline 데이터 인터페이스는 언리얼 엔진의 스플라인 컴포넌트(Spline Component) 데이터를 쓸 수 있게 합니다. 정해진 궤적을 따라 움직이는 마법 주문처럼 스플라인 경로를 따라 파티클을 움직이거나, 스플라인의 길이나 곡률에 따라 파티클의 속성을 바꿀 수 있습니다.
Grid 2D와 Grid 3D
Grid 2D와 Grid 3D 데이터 인터페이스는 2차원이나 3차원 격자 모양의 데이터를 다룹니다. 주로 유체 시뮬레이션이나 볼륨 효과처럼 계산이 많은 효과에 씁니다. 연기나 불, 액체 시뮬레이션의 흐름을 파티클에 적용하거나, 어떤 영역의 밀도나 온도에 따라 파티클의 움직임을 바꿀 수 있습니다.
모듈에서 데이터 인터페이스 쓰기
데이터 인터페이스를 쓰는 방법은 두 가지입니다. 이미 그 데이터 인터페이스를 쓰도록 만들어진 모듈을 고르거나, 직접 만드는 모듈 그래프에 데이터 인터페이스 타입의 입력을 만들어 함수를 부르는 것입니다. 어느 쪽이든 실행 그룹은 목적에 맞게 고릅니다. 초기값을 한 번 정하는 샘플링이라면 Particle Spawn에, 계속 따라가야 하는 샘플링이라면 Particle Update에 둡니다.
기존 모듈로 시작한다면, 먼저 원하는 데이터를 쓰는 모듈을 찾습니다. 메시의 위치를 읽고 싶다면 Static Mesh Location처럼 그 데이터 인터페이스를 쓰는 모듈부터 찾습니다. 데이터 인터페이스의 클래스 이름이 곧 스택에 추가할 모듈의 이름은 아니라는 점에 주의합니다. 다음으로 그 모듈의 데이터 인터페이스 입력에서 기본 메시나 데이터를 읽을 컴포넌트를 지정하거나, 연결할 사용자 데이터 인터페이스 파라미터를 고릅니다. 이때 미리보기와 게임에서 같은 원본을 쓰는지도 확인합니다.
직접 만드는 모듈에서는 Map Get으로 데이터 인터페이스 입력을 읽은 뒤, 그 핀에서 끌어 지원되는 함수를 고릅니다. 함수로 표본 위치나 노멀 같은 값을 계산하고, Map Set으로 Particles.Position처럼 원하는 입자 속성에 씁니다. 이때 위치와 방향의 값이 어느 좌표 공간의 값인지 구분해야 합니다.
더 깊이: 데이터 인터페이스를 어디에 둘지에 따라 달라지는 비용
Scalability and Best Practices 문서는 데이터 인터페이스를 두는 위치도 비용에 영향을 준다고 설명합니다. 이미터와 파티클 스크립트에 같은 데이터 인터페이스를 겹쳐 두면 필요 없는 메모리를 쓰게 되므로, 데이터 인터페이스를 스택의 더 위쪽으로 옮길 수 있다면 그쪽이 성능에 낫습니다. 또 입자마다, 이미터마다 같은 결과가 나오는 무거운 작업은 대개 스택의 위로 옮길 수 있으며, 데이터 인터페이스와 나이아가라 파라미터 컬렉션(Niagara Parameter Collection)을 읽는 일도 Emitter와 System 스크립트에서 하는 편이 더 빠를 수 있다고 문서는 적고 있습니다.
데이터 인터페이스를 사용자 파라미터로 노출할 때도 메모리 비용이 생깁니다. 나이아가라 컴포넌트마다 새 객체가 만들어지고, Set Asset을 부를 때마다 또 객체가 만들어지기 때문입니다. 그래서 같은 메시를 쓰는 효과가 레벨에 많다면, 원본을 컴포넌트마다 바깥에서 바꿔 넣어야 하는 경우인지 먼저 따져 보고, 바꿀 필요가 없다면 모듈 입력에 기본 원본을 지정하는 방식을 검토합니다.
어떤 입력이 필요할까
모든 바깥 입력에 데이터 인터페이스가 필요한 것은 아닙니다. 예를 들어 모든 입자의 색을 이번 프레임의 체력 비율 0.3에 맞춰 바꾸고 싶다면, Float 사용자 파라미터 하나와 그 값을 읽는 색 계산으로 충분한지 먼저 확인합니다. 반면 캐릭터의 움직이는 손 위치를 읽어야 한다면, 그 스켈레탈 메시를 원본으로 하는 데이터 인터페이스와 지원되는 함수를 써야 합니다.
이 둘을 구분하려면 데이터를 제공하는 데이터 인터페이스와, 함수를 불러 값을 읽는 모듈, 그리고 그 결과를 쓰는 입자 속성이 각각 다른 역할이라는 것을 기억합니다. 또 위치를 한 번 읽었다고 해서 그 뒤의 손 움직임까지 저절로 따라가지는 않습니다. 계속 따라가야 한다면 Particle Update에서 다시 읽어 써야 합니다.
연결 결과 점검하기
데이터 인터페이스를 연결했는데 결과가 이상하다면 데이터가 흐르는 단계를 따라 차례로 확인합니다.
| 단계 | 확인할 것 |
|---|---|
| 원본 | 비어 있거나 이미 사라진 컴포넌트를 가리키고 있지 않은가 |
| 함수 | 고른 CPU나 GPU 대상과 실행 그룹에서 지원되는 함수인가 |
| 결과 | 위치와 방향의 좌표 공간과 자료형이 맞는가 |
| 사용 | 결과를 실제 입자 속성에 쓰고 있으며, 아래쪽 모듈이 덮어쓰지 않는가 |
| 비용 | 표본 수와 갱신 주기, 격자 해상도가 필요한 만큼인가 |
단순한 Float이나 Vector, Color 값은 사용자 파라미터로도 넘길 수 있다는 점도 다시 기억해 둡니다. 사용자 파라미터는 다음 글에서 자세히 다룹니다.
정리
- 데이터 인터페이스는 모듈이 바깥 데이터를 읽는 함수와 접근 방법을 제공하는 특수한 데이터 타입이며, 그것을 쓰는 모듈과는 다릅니다.
- Static Mesh, Skeletal Mesh, Spline, Grid 2D와 Grid 3D 같은 데이터 인터페이스가 각각 메시와 캐릭터, 경로, 격자 데이터를 다룹니다.
- 기존 모듈의 데이터 인터페이스 입력에 원본을 지정하거나, 직접 만든 모듈에서
Map Get으로 읽고 함수를 불러Map Set으로 결과를 씁니다. - 처음 한 번 읽는 값은 Particle Spawn에, 계속 따라가야 하는 값은 Particle Update에서 읽으며, 좌표 공간과 CPU, GPU 지원을 확인합니다.
- 단순한 값은 사용자 파라미터로 충분할 수 있으므로, 모든 바깥 입력에 데이터 인터페이스가 필요하다고 가정하지 않습니다.
연습 문제
1. 같은 강도와 움직이는 표면
모든 입자에 같은 Float 강도를 넘기는 효과와, 움직이는 메시의 서로 다른 표면 지점에 입자가 계속 붙어 있어야 하는 효과가 있습니다. 두 효과 모두 새 C++ 데이터 인터페이스가 꼭 필요할까요? 두 번째 효과를 Particle Spawn에서 위치를 한 번 읽는 것만으로 만들 수 있는지도 판단해 보세요.
해설 보기
같은 Float 강도는 사용자 파라미터로 넘기는 방법부터 검토합니다. 메시 표면에 붙는 효과는 기존 메시 데이터 인터페이스가 제공하는 원본과 함수, 표본을 구별하는 정보를 먼저 검토합니다. 이 두 조건만으로 새 C++ 데이터 인터페이스를 만들기로 결정할 필요는 없습니다.
두 번째 효과는 Particle Spawn에서 한 번 읽는 것만으로는 만들 수 없습니다. 한 번 읽은 위치는 메시가 움직여도 따라가지 않으므로, Particle Update에서도 같은 표면 지점의 위치를 다시 읽어 써야 합니다. 이때 원본 컴포넌트의 수명과 좌표 공간, CPU와 GPU 지원, 그 뒤에서 Position을 다시 쓰는 모듈도 확인합니다.
2. 미리보기에서만 보이는 메시 표면 효과
Static Mesh Location으로 메시 표면에 입자를 만드는 효과가 에디터 미리보기에서는 잘 보이는데, 레벨에 배치한 뒤에는 원점 근처에만 입자가 생깁니다. 무엇을 확인해야 할까요?
해설 보기
먼저 데이터 인터페이스의 원본이 무엇인지 확인합니다. 미리보기는 데이터 인터페이스 입력에 지정한 기본 메시를 쓰지만, 게임에서는 원본으로 삼을 컴포넌트를 찾지 못하거나 다른 원본을 가리킬 수 있습니다. 레벨의 액터에서 원본 컴포넌트가 제대로 지정되었는지, 사용자 데이터 인터페이스 파라미터로 넘긴다면 그 값이 실제로 설정되었는지 봅니다.
원본이 맞다면 표본 위치가 메시의 로컬 좌표인지 월드 좌표인지와, 이미터의 Local Space 설정이 맞는지 확인합니다.
3. 손을 따라가지 않는 불꽃
Particle Spawn에서 Skeletal Mesh 데이터 인터페이스로 손 소켓의 위치를 읽어 입자를 만들었는데, 캐릭터가 손을 움직여도 이미 생긴 입자들은 손을 따라가지 않습니다. 이것은 오류일까요?
해설 보기
오류가 아니라 설계대로 동작한 것입니다. Particle Spawn은 입자가 생길 때 한 번 실행되므로, 그때 읽은 손의 위치가 입자의 초기 위치로만 쓰이고 그 뒤의 움직임은 반영되지 않습니다.
불꽃이 손을 따라가야 한다면 Particle Update에서 손의 위치를 다시 읽어 입자 위치에 반영하거나, 효과 전체를 손 소켓에 붙이고 Local Space를 쓰는 방법을 검토합니다. 반대로 손이 지나간 자리에 남는 효과라면 지금처럼 생성할 때 한 번 읽는 방식이 맞습니다.
데이터 인터페이스 입력에서 함수를 불러 쓰는 그래프의 예는 Epic의 스크래치 패드 모듈 문서에서 확인할 수 있습니다.
다음 글 동적 매개변수 시스템의 기초에서는 블루프린트에서 효과로 값을 넘기는 사용자 파라미터를 만들고 모듈의 입력에 연결해 봅니다.