in 포스트

지오메트리를 스마트하게 관리하는 렌더링 기술들이 최적화에 도움이 되는가에 대한 생각

최근 언리얼과 유니티에서 렌더링 기술 관련하여 적극적으로 마케팅되는 공통된 방향이 있다.

“지오메트리를 더 많이 다루면서도 성능을 유지하기”

대표적으로 다음 기술들이 있다.

  • 언리얼 엔진의 Nanite
  • 유니티의 GPU Occlusion Culling / GPU Driven Rendering

이 기술들의 기본 방향은 대체로 다음과 같다.

지오메트리를 더 스마트하게 관리하면 성능을 개선할 수 있다.

하지만 커뮤니티 반응을 보면
“효과가 없다”, “마케팅 기술이다” 같은 의견도 꽤 자주 보인다.

개인적으로도 이 기술들을 테스트해보았을 때 딱 잘라 말하기 힘든 결과가 나왔던 경험이 있다.

그래서

지오메트리를 공격적으로 관리하는 것이 실제 성능을 얼마나 개선하는가?

에 대해 몇 가지 생각을 정리해봤다.

Meshlet, Unreal Nanite, Unity GPU Driven Rendering

우선 본문에 들어가기 전에 이 기술들이 어떤 계열의 기술인지 간단히 정리해보자.

최근 GPU 렌더링에서 중요한 개념 중 하나가 Meshlet이다. Meshlet은 메시를 작은 단위로 분해한 geometry 묶음이다.

보통 하나의 meshlet은
• 약 64 ~ 128 vertices
• 약 100개 정도의 triangle

정도로 구성된다.

메시렛 예제
출처 : https://metalbyexample.com/mesh-shaders/

이 정도 크기는 GPU의 warp / wavefront SIMD 구조에 맞춰
병렬 처리 효율을 높이기 위해 선택된 값이다.

이 구조의 장점은 다음과 같다.

  • GPU 병렬 처리 효율 증가
  • 가시성 판단 단위 세분화
  • GPU driven rendering에 적합

최근 등장한 기술들은 대부분 이 방향과 연결된다.

  • 메시렛 : GPU 친화적인 geometry 단위
  • 유니티 GPU Driven Rendering : culling을 GPU에서 처리, GPU 상태 변화 최소, CPU와 GPU 모두 데이터 기반 접근법으로 최적화
  • 언리얼 Nanite : meshlet + LOD + streaming + visibility 시스템

즉 최근 렌더링 기술의 흐름은 크게 보면 "geometry를 작은 단위로 나누고 GPU가 직접 가시성을 관리하는 방향"이라고 볼 수 있다.

Meshlet 아이디어 자체는 오래전부터 실무에서 사용되어 왔다. 대표적으로 Ubisoft에서는 초창기 메시를 작은 클러스터 단위로 분해하는 geometry pipeline을 어쌔신크리드 유니티 시절 쯤에 소개했었다.

이 경우 메시를 다음과 같은 방식으로 처리한다.

  • 메시 → 클러스터로 미리 쪼개기 → 런타임 렌더링

초기에는 이러한 클러스터 분해를 엔진 외부 툴을 쓰거나 빌드에서 별도의 단계를 추가하여 시행했다. 그리고 경우에 따라 아티스트가 메시 구조를 고려하여 애셋을 제작해야 하는 경우도 있었다.

즉 GPU에 최적화된 지오메트리 구조를 사람이 어느 정도 관리해야 했던 것이다.

Nanite의 차이

나나이트도 메시를 클러스터 단위로 분해한다. 하지만 이 클러스터 구조를 아티스트가 직접 관리할 필요가 없다.

Nanite에서는 하이폴리 메시를 import하면 엔진이 내부적으로 다음 구조를 생성한다.

High poly mesh
→ cluster partition
→ cluster hierarchy
→ compressed Nanite data

그리고 런타임에서 카메라에 보이는 클러스터들을 선택하고 레스터화한다.

GPU Driven Rendering

Meshlet 기반 렌더링에서 또 하나 중요한 접근은 GPU에서 가시성 판단을 수행하는 것이다.

전통적인 렌더링에서는 보통 다음 작업이 CPU에서 이루어진다.

  • draw call 정렬
  • 가시성 판단
  • LOD 선택

하지만 씬이 복잡해질수록 이러한 작업은 CPU 부담이 커진다.
그래서 최근 엔진들은 다음 작업들을 GPU로 옮기고 있다.

  • visibility
  • culling
  • sorting

유니티의 경우 이미 DOTS / ECS 렌더링, Batch Renderer Group을 이미 적용하여 CPU 측 렌더링 부하를 줄였둔 상태였는데, Unity 6에서는 여기에 GPU Occlusion Culling을 추가하여 컬링 부하를 GPU로 옮겼다.

나나이트의 경우, 클러스터에서 계산된 depth buffer와 HZB 기반의 occlusion 시스템과 결합되어 GPU측에서 가시성 판단이 이루어진다.

지오메트리가 정말 병목인가?

이제 이 기능들이 최적화에 정말 도움이 되는지 판단해보자. 여기서 주된 질문은 결국 이거다.

정말 지오메트리가 주요 병목인가?

하이엔드 그래픽으로 갈수록 셰이딩 비용이 크게 증가한다. 예를 들어 다음과 같은 요소들이다.

  • 복잡한 라이팅
  • PBR 머티리얼
  • 포스트 프로세싱
  • 오버드로우
  • 스크린 공간 이펙트

그래서 개인적인 체감으로는 High Fidelity를 노릴수록 버텍스 비용보다 픽셀 비용의 증가폭이 훨씬 크다고 생각한다.

즉 실제 프로젝트에서 병목이 되는 경우는 종종 다음 쪽이다.

  • 셰이더 연산, 렌더 상태 스왑
  • 메모리 대역폭
  • 오버드로우

지오메트리 비용이 문제가 아니라는 것은 아니지만 지오메트리를 더 많이 컬링한다고 해서 항상 큰 성능 개선이 나오지는 않는 경우도 많다.

GPU 자체의 컬링

또 하나 중요한 점은 GPU 자체에도 이미 깊이 기반 컬링 메커니즘이 존재한다는 것이다.

  • Early-Z
  • Hierarchical Z
  • 타일 기반 렌더링

예를 들어 Early-Z의 경우 픽셀 셰이딩 전에 depth test를 수행하여
이미 가려진 프래그먼트에 대해 픽셀 셰이더가 실행되지 않도록 한다.

즉 다음과 같은 비용이 줄어든다.

  • 픽셀 셰이딩
  • 라이팅 연산
  • 텍스처 샘플링

모바일 GPU의 경우 Tile-based rendering을 사용하여
타일 내부에서 depth를 기반으로 프래그먼트를 상대적으로 더 공격적으로 제거한다.

결론적으로 GPU 자체가 이미 숨겨진 지오메트리에 대한 픽셀 셰이딩 비용을 상당 부분 줄이고 있다. 물론 이것은 픽셀 셰이딩을 줄이는 것이지 버텍스 셰이더나 레스터라이저 단계가 사라지는 것은 아니다.

하지만 일반적으로 버텍스 변환 비용은 픽셀 셰이딩 비용보다 훨씬 작다.

따라서 소프트웨어적으로 남은 버텍스 변환 비용 조차 완전히 제거한다는 방식은 체감상 큰 효과가 없을 수 있다.

오쿨루전 자체의 비용

또 하나의 문제는 오쿨루전 계산 자체의 비용이다.

하이엔드 기기로 갈수록 셰이딩 비용은 올라가고, 지오메트리 처리 비용이 차지하는 비율은 상대적으로 줄어드는 경향이 있다.

그런데 동시에 강력한 GPU일수록 지오메트리 연산 자체가 빠르다. 따라서 오쿨루전 컬링으로 얻는 이득이 생각보다 작을 수 있다.

반대로 저사양 모바일 기기에서는 상황이 조금 역설적이다. 저사양 기기에서는 지오메트리 비용이 상당히 크다

하지만 동시에

  • 복잡한 동적 컬링
  • 클러스터 기반 관리
  • GPU 기반 오쿨루전

같은 알고리즘을 빠르게 실행하기 어렵다.

즉, 지오메트리 최적화의 영향을 가장 크게 받는 저사양 기기들이 오히려 지오메트리를 “스마트하게 관리하는 시스템”을 돌리기 어려운 모순이 있어, 나나이트나 GPU 오쿨루전을 활용하기 어렵다

그렇다면 이런 기술은 왜 쓰는가?

그럼에도 불구하고 이러한 기술들은 여전히 의미가 있다. 특히 다음과 같은 경우에는 매우 유용하다.

  • 극단적으로 복잡한 지오메트리 환경
  • 시네마틱 수준의 장면
  • 게임 플레이와 컷신이 자연스럽게 이어지는 구조

나는 개인적으로 언리얼의 나나이트와 유니티의 GPU 오클루전이 셰이딩 성능 측면으로 과장되어 마케팅 된 경향이 있다고 생각한다. 사실, 이 기술들의 가장 큰 장점은 성능보다 워크플로우 개선이다.

아티스트 워크플로우 개선

예를 들어 언리얼의 Nanite는 수백만 폴리곤 규모의 에셋을 LOD 제작 없이 직접 사용할 수 있게 한다.

기존 AAA 아트 파이프라인은 보통 다음과 같다.

하이폴리 제작
→ 리토폴로지
→ 노말맵 베이크
→ LOD 제작
→ 엔진 임포트

나나이트를 사용하면 하이폴리 모델을 엔진에 바로 사용할수 있게된다.

물론 내부적으로는 메시가 클러스터 구조로 변환되지만
아티스트 입장에서는 LOD 제작을 직접 관리할 필요가 없다(혹시 잘못 알고 있는 사람들을 위해 노트를 남기자면, 나나이트도 에디터 내에서 메시를 베이크하는 방식이다).

유니티 6부터 새로 추가된 Mesh LOD도 동일한 장점을 제공한다. 엔진 에디터 내에서 LOD를 베이크하므로 외부에서 LOD를 베이크해서 다시 임포트하는 작업 코스트가 해소된다.

가시성 관리 자동화

위 내용을 다시 정리하면 나나이트를 사용하는 경우 하이폴리 메시를 별도의 리토폴로지나 LOD 제작 없이 엔진에 바로 사용할 수 있다.

다만 원본 메시가 그대로 렌더링되는 것은 아니고, 임포트 과정에서 메시가 클러스터 기반 계층 구조로 변환된다.

그리고 런타임에서는 이 클러스터 단위의 데이터가 선택적으로 된다.

여기서 언리얼 엔진은 컬링에 사용하는 깊이 버퍼를 클러스터에서 생성하는데, 섬세한 지형 데이터에서 내놓은 깊이 버퍼이기 때문에 얇은 벽 같은 단위의 정밀한 오쿨루전이 가능하다.

기존 엔진에서는 하이 폴리 메시에 로우 폴리 프록시 오쿨루더(예를 들어 건물 전체에 단순한 박스 오쿨루더 씌우기)를 씌우거나, 정밀함을 위해서 오쿨루더를 베이크를 다 해두어야 했는데 이 부분이 생략되는것이다.

유니티의 GPU Occlusion Culling 또한 유사한 장점을 제공한다. 동적 오쿨루더를 지원하므로, 오쿨루더를 구워둘 필요가 없다.

이런 부분들이 레벨 디자인 측면에서 꽤 유용한 기능이다.

콘텐츠 스케일 확장

마지막으로 당연한 장점을 짚고 넘어가면, 이 기술들의 또 다른 중요한 장점은 씬의 지오메트리 스케일을 크게 확장할 수 있다는 것이다.

포토 리얼리스틱, 스캔 기반 애셋, 대규모 환경 아트 등, 현실의 대규모 지형 데이터를 그대로 옮긴 경우에는 사람이 처리하기 보다 에디터 내부에서 이를 자동화하는게 맞는 방향이라 본다.

정리하면, 이러한 기술들은 “지오메트리를 줄이기 위한 기술”이라기보다는 “지오메트리를 사람이 관리하지 않아도 되게 만드는 기술”에 가깝다고 생각한다.

참고 : 나나이트가 해결하려는 문제

나나이트가 흔히 폴리곤 수를 줄이는 기술로 설명되지만, 나나이트나 메시렛이 해결하려는 문제는 Tiny Triangle Problem에 더 가깝다.

GPU는 삼각형을 병렬로 처리하는 구조인데, 삼각형이 지나치게 작아지면 다음과 같은 문제가 발생한다.

  • 픽셀 하나보다 작은 삼각형이 매우 많아짐
  • GPU SIMD 활용률 저하
  • 레스터라이저 단계의 비효율 증가

예를 들어 하나의 픽셀보다 작은 삼각형이 매우 많은 경우, GPU는 삼각형을 처리하기 위해 여러 연산을 수행하지만 실제로 화면에 기여하는 픽셀은 거의 없을 수 있다.

이러한 문제는 실제 지형을 스캔한 하이폴리 지형들 사이에 엄청난 량의 프롭들이 나뒹구는 씬에서는 심화될것이므로 클러스트 단위로 관리하는게 도움이 되는 것이다.

참고 2 : 왜 유니티는 나나이트 없음?

가끔 어그로성 발언으로 “유니티에는 Nanite가 없다”는 식의 이야기를 하는 경우가 있다. 또는 “Unity China 엔진에는 Nanite가 있는데 글로벌 버전에는 없다” 같은 식으로 기술력 비교로 이어지는 경우도 종종 보인다.

이 부분은 몇 가지 맥락을 정리해볼 필요가 있다.

우선 앞서 언급했듯이 Nanite에 사용된 기술적 아이디어 자체는 완전히 새로운 것은 아니다.
메시를 작은 단위로 분해하고, GPU에서 가시성 판단을 수행하는 접근은 이미 오래전부터 연구되었고 실제 엔진에서도 부분적으로 사용되어 왔다.

차이는 그 기술들을 어떤 형태로 엔진에 통합하느냐에 가깝다.

엔진 아키텍처 차이

유니티와 언리얼은 애셋 파이프라인 구조 자체가 상당히 다르다.

유니티의 경우

  • 프로젝트 내 Assets 폴더에는 원본 애셋이 유지
  • 엔진에 맞게 변환된 데이터는 Library 폴더에 임포트 데이터로 저장

되는 구조다.

즉 유니티는 원본 애셋을 유지하고 필요할 때 변환된 데이터를 생성하는 방식이다.

반면 언리얼은

  • 애셋을 임포트할 때 엔진 내부 포맷으로 완전히 변환된 에셋을 생성
    하는 구조에 가깝다.

이 차이 때문에 나나이트 처럼 메시 구조 자체를 엔진 내부 포맷으로 크게 변형하는 접근이 언리얼에서는 자연스럽게 들어갈 수 있었다.

반면 유니티는 기본적으로 원본 애셋을 유지하는 파이프라인이기 때문에
같은 방식으로 엔진 내부 메시 구조를 크게 바꾸는 접근은 엔진의 전반적인 디자인에 어울리지 않는다.

이미 일부 기능 존재

또 하나 중요한 점은 나나이트의 구성 요소 일부는 이미 유니티에도 존재한다는 것이다.

예를 들어 Nanite의 핵심 기능을 나누어 보면 다음과 같다.

  • GPU 기반 가시성 판단
  • 자동 LOD
  • geometry 클러스터링
  • GPU driven rendering

이 중 일부는 이미 유니티에서도 구현되어 있다.

예를 들어

  • GPU Occlusion Culling
  • Mesh LOD
  • Batch Renderer Group
  • DOTS / ECS 기반 렌더링

같은 기능들을 조합하면 유사한 방향의 기술 스택을 구성할 수 있다.

여기서, 유니티에서 구현안된 부분이자 나나이트의 가장 특징적인 부분은 지오메트리를 클러스터 단위로 묶어 관리하는 것이다. 하지만 이 부분이 항상 모든 플랫폼에서 큰 이득을 주는 것은 아니다.

특히 모바일 환경에서는 앞서 설명했듯이 클러스터 기반 렌더링의 관리 비용, 그리고 GPU 드리븐 렌더링 자체의 효율성 저하로 성능 역행이 발생할 수 있다.

그래서 모바일 플랫폼 비중이 큰 엔진에서는 반드시 우선적으로 구현해야 하는 기능으로 볼수 없다.

CPU 오버헤드 문제

이러한 기술들의 또 하나의 목적은 CPU 측 렌더링 오버헤드를 줄이는 것이다.

씬이 복잡해질수록 이 부분이 병목이 되기 때문에 최근 엔진들은 이를 GPU로 옮기려는 방향을 취하고 있다.

유니티의 경우 이 문제를 해결하기 위해 꽤 오래전부터

  • ECS
  • DOTS 기반 렌더링
  • Batch Renderer Group

같은 구조를 준비하여 CPU 오버헤드를 줄여왔다. 즉 나나이트의 지오메트리 구조 변경과는 다른, CPU의 렌더링 명령어 구조화 최적화를 통한 렌더링 최적화를 구현해온 것이다.

하여간 정리하면 유니티에는 나나이트가 없다는 것을 기술력 차이로 이해하는 것은, 나나이트를 잘못 이해한것이고, 엔진 아키텍처와 설계 방향의 차이로 이해하는 것이 이를 제대로 이해한 것이다.

참고 3 : 커뮤니티 논쟁

나나이트가 등장하면서 레딧에 자주보이는 의견 중 이런게 있었다.

  • “나나이트 때문에 메시 최적화를 안 하고 사람들이 그냥 하이폴리를 넣는다.”

이 의견은 좀 과장된 부분이 있다고 본다.

원래 최적화는 지속적인 관리가 필요한 것이다.

나나이트의 큰 장점은 버텍스 예산을 개발자가 직접 관리할 필요를 덜어주었다는 건데, 그것 때문에 역으로 개발자들이 불필요하게 큰 메시랑 과도하게 복잡한 애셋을 남발하고, 게으르게 관리하게 되는 환경을 만든다는 것이다.

근데, 나나이트 여부와 상관없이 원래 최적화 신경안쓰는 친구들은 죽어도 신경안쓴다. 이런건 개인적으로 팀의 개발 문화에 가깝다고 생각하는 편이다.

덧글 쓰기

Comment