씬 그래프란 무엇인가?

언리얼 엔진 6 로드맵에서 흥미로운 변화 중 하나는 Scene Graph입니다.
에픽은 언리얼 엔진 6에서 Verse 기반의 새로운 게임플레이 프레임워크를 Scene Graph라는 이름으로 소개하고 있습니다.
씬 그래프 자체는 완전히 새로운 개념은 아닙니다. 게임 엔진, 렌더링 엔진, 3D 툴, GUI 시스템 등에서 오래전부터 사용되던 개념입니다.
그럼에도 불구하고 지금 이 개념을 다시 살펴볼 필요가 있습니다.
왜냐하면 씬 그래프는 단순히 오브젝트를 트리 형태로 배치하는 기술이 아니라,
게임 월드 안의 오브젝트 관계, Transform 상속, 업데이트 순서, 계층 구조를 이해하는 기본적인 사고방식이기 때문입니다.
기존 방식부터 생각해보기
게임을 만들 때 보통 여러 개의 오브젝트를 다룹니다.
- 플레이어
- 카메라
- 무기
- 몬스터
- 맵 오브젝트
- 이펙트
가장 단순한 방식은 이 오브젝트들을 하나의 리스트에 넣고 순서대로 업데이트하는 것입니다.
std::vector<GameObject*> objects;
for (GameObject* object : objects)
{
object->Tick();
}
이 방식은 직관적이며 이해하기 쉽습니다. 많은 게임 엔진에서 전통적으로 사용하던 방식입니다.
하지만 오브젝트 사이에 관계가 생기기 시작하면 점점 불편해집니다.
예를 들어 캐릭터가 무기를 들고 있다고 생각해보겠습니다.
캐릭터가 이동하면 무기도 같이 이동해야 합니다.
캐릭터가 회전하면 무기도 같이 회전해야 합니다.
캐릭터가 사라지면 무기도 함께 정리되어야 합니다.
이때 무기와 캐릭터를 완전히 독립된 오브젝트로만 다루면, 프레임마다 무기의 위치를 캐릭터 기준으로 다시 계산해야 합니다.
weapon.position = player.position + weaponOffset;
처음에는 별문제가 없어 보입니다.
하지만 카메라가 플레이어를 따라가고, 무기 아래에 총구 이펙트가 붙고, 캐릭터의 손 본 위치를 기준으로 무기가 움직이고, 그 무기에서 다시 탄환이 생성되기 시작하면 구조가 점점 복잡해집니다.
씬 그래프의 핵심 아이디어
씬 그래프의 핵심은 간단합니다.
오브젝트를 독립된 목록으로만 관리하지 않고, 부모-자식 관계를 가진 계층 구조로 관리하는 것입니다.
예를 들어 아래와 같은 구조를 생각할 수 있습니다.
World
└─ Player
├─ Camera
└─ Weapon
└─ MuzzleEffect
이 구조에서 Player는 Camera와 Weapon의 부모입니다.
Weapon은 MuzzleEffect의 부모입니다.
이제 Player가 이동하면 Camera와 Weapon도 Player를 기준으로 함께 움직일 수 있습니다.
Weapon이 회전하면 MuzzleEffect도 Weapon을 기준으로 함께 회전할 수 있습니다.
이 구조에서 각 오브젝트는 자신의 위치만 가지는 것이 아니라, 부모를 기준으로 한 상대적인 위치를 가질 수 있습니다.
Local Transform과 World Transform
씬 그래프를 이해할 때 가장 중요한 개념은 Local Transform과 World Transform입니다.
Local Transform은 부모를 기준으로 한 위치, 회전, 크기입니다.
World Transform은 최종적으로 월드에서 보이는 위치, 회전, 크기입니다.
예를 들어 Player의 위치가 (10, 0)이고, Weapon의 Local 위치가 (2, 0)이라고 해보겠습니다.
Player World Position = (10, 0) Weapon Local Position = (2, 0) Weapon World Position = (12, 0)
Weapon은 자기 자신만 보면 (2, 0)에 있는 것이 아니라, Player를 기준으로 (2, 0)만큼 떨어져 있는 것입니다.
따라서 월드 기준으로는 (12, 0)에 위치하게 됩니다.
이 구조 덕분에 부모 오브젝트를 움직이는 것만으로 자식 오브젝트의 최종 위치도 자연스럽게 바뀔 수 있습니다.
왜 그래프라고 부를까?
이름에는 그래프라는 단어가 들어가지만, 입문 단계에서는 트리 구조로 이해해도 충분합니다.
하나의 부모 아래에 여러 자식이 있고, 각 자식은 다시 자신의 자식을 가질 수 있습니다.
이런 계층 구조를 통해 게임 월드 안의 오브젝트 관계를 표현합니다.
Root
├─ Node A
│ ├─ Node A-1
│ └─ Node A-2
└─ Node B
└─ Node B-1
중요한 것은 단순히 모양이 트리라는 점이 아닙니다.
씬 그래프는 오브젝트의 소유 관계, Transform 계산 순서, 업데이트 순서, 렌더링 대상 수집 방식과도 연결될 수 있습니다.
콘솔 기반으로 먼저 구현해보자
이번 연재에서는 복잡한 3D 렌더링부터 시작하지 않습니다.
윈도우즈 콘솔 기반의 간단한 예제로 씬 그래프를 직접 구현해볼 예정입니다.
처음에는 화면에 그래픽을 그리지 않고, 노드의 계층 구조를 텍스트로 출력하는 것부터 시작합니다.
World
└─ Player
├─ Camera
└─ Weapon
그 다음에는 각 노드에 위치 값을 추가하고, 부모의 위치가 자식의 위치에 어떻게 반영되는지 확인합니다.
Player Local : (10, 0) Weapon Local : (2, 0) Weapon World : (12, 0)
이 과정을 통해 씬 그래프가 단순한 이론이 아니라, 실제 게임 오브젝트 구조를 만드는 기본 설계 방식이라는 것을 확인할 수 있습니다.
이번 연재에서 만들 구조
이번 시리즈에서는 아래와 같은 순서로 콘솔 기반 씬 그래프를 만들어볼 예정입니다.
- SceneNode 클래스 만들기
- 부모-자식 관계 연결하기
- 계층 구조 출력하기
- Local Position과 World Position 계산하기
- Tick 순서 확인하기
- 간단한 컴포넌트 구조와 연결하기
최종적으로는 아래와 같은 구조를 코드로 표현할 수 있게 만드는 것이 목표입니다.
SceneNode* world = new SceneNode("World");
SceneNode* player = new SceneNode("Player");
SceneNode* camera = new SceneNode("Camera");
SceneNode* weapon = new SceneNode("Weapon");
world->AddChild(player);
player->AddChild(camera);
player->AddChild(weapon);
world->PrintTree();
이 코드는 아직 간단해 보이지만, 여기서부터 게임 엔진의 오브젝트 계층 구조, Transform 상속, 씬 관리 구조로 확장할 수 있습니다.
마무리
씬 그래프는 오래된 개념입니다.
하지만 오래된 개념이라는 말은 낡았다는 뜻이 아닙니다.
오히려 수많은 엔진과 툴에서 검증된 기본 구조에 가깝습니다.
언리얼 엔진 6에서 Scene Graph가 중요한 키워드로 다시 등장했다는 것은,
앞으로 게임 오브젝트를 바라보는 방식이
기존 Actor 중심 구조에서 더 명시적인 계층 구조와 데이터 흐름 중심으로 확장될 가능성이 있다는 의미로도 볼 수 있습니다.
다음 글에서는 가장 작은 단위인 SceneNode 클래스를 만들고, 부모-자식 관계를 코드로 직접 구현해보겠습니다.