▲ 에픽게임즈코리아 박창현 엔진 서포트 엔지니어

언리얼서밋 2018 오후 진행된 세션에서는 에픽게임즈코리아의 박창현 엔지니어가 언리얼 엔진 4의 모바일 플랫폼 렌더링이 다른 플랫폼과 어떤 차이가 있는지 전반적으로 이야기 하는 시간을 가졌다. 본 강연에서 그는 모바일 플랫폼에서 지원하는 언리얼 엔진4의 기능을 정리하고, 모바일 씬 렌더링 과정을 단계별로 분석한 뒤 끝으로 개발자를 위한 여러 팁을 공유했다.


전달의 용이성을 위해 강연자의 시점에서 서술하였습니다


■ 모바일 플랫폼의 특징은 무엇인가?


일반적으로 데스크탑과 달리, 모바일 디바이스는 칩 안에 여러가지 기능을 넣어놓은 AP가 두뇌로 사용된다. CPU와 GPU가 하나의 칩에 포함된 형태로 제조사가 다양하고 많다. 국내에서 가장 많이 사용되는 엑시노스 AP는 아드레노와 말리를 GPU로 많이 사용하고 있으며, 아이폰의 경우 6S 이상에서는 파워 VR GPU가, 8 이상에서는 애플 자체 개발 GPU가 사용되고 있다.

현재 모바일 GPU들은 일반적으로 셰이더 코어가 어떤 타입의 셰이더든 처리할 수 있도록 유니파이드 셰이더 아키텍쳐 형태로 변경되고 있다. 과거에는 GPU만 접근 가능한 공간을 따로 지정해둔 반면, 이러한 아키텍쳐에서는 CPU와 GPU의 MMU가 같은 주소 공간을 활용하도록 되어 있다.


다음으로, 모바일 하드웨어는 다음과 같은 특징을 가지고 있다. 먼저, 배터리를 이용해 구동되기 때문에 같은 성능이라도 저전력이 선호되며, 메인 메모리도 DDR이 아닌 LPDDR을 사용한다. 또한, 메모리 대역폭이 클 수록 배터리 소모가 큰 특징을 가지고 있다.

또한, 쿨러가 달려 있는 핸드폰은 없듯, 모바일 하드웨어는 자체적으로 열을 내뿜는 방식으로 쿨링을 시키는 것이 일반적이다. 따라서 발열이 심해지면 하드웨어를 보호하기 위한 조치로 자동으로 성능을 줄이는 스로틀링을 진행하는데, 모바일 환경의 특징으로 알아두어야 한다.


성능 측면에서 모바일 플랫폼은 다른 플랫폼과 비교해 메모리 대역폭이 낮고, 1초에 연산할 수 있는 소수점 연산 수가 낮은 편이다. 이러한 제약들을 극복하기 위해서는 타일 베이스드 GPU와 얼리Z 테스팅(Early Z Testing)이 일반적으로 활용된다.

타일 베이스드 GPU는 한 장면을 그리기 위해 필요한 리소스를 전부 GPU 메모리에 올려놓는 일반적인 방식이 아니라, 모든 렌더링을 타일 단위로 진행하는 것을 의미한다. 일반적인 방식의 경우 셰이더 코어와 GPU 메모리간 대역폭을 계속 사용하게 되는데, 이를 통해 메모리 대역폭을 아껴 배터리 소모를 줄일 수 있다. 단점으로는 타일을 나누고 관리하기 위해서는 추가적인 관리비용이 발생한다.


얼리 Z 테스트는 Depth 테스트라고 불리는 Z 테스트 이전에 진행되는 것으로, 일반적인 Z 테스트는 픽셀셰이딩 연산이 끝난 뒤 렌더링 결과물을 타겟에 블렌딩 할 것인지를 결정하는 과정이다. 이 때 테스트에 실패하면 해당 렌더링 결과물은 드랍이 되는데, 픽셀 셰이딩 연산이 모두 끝난 뒤 드랍되는 것으로 상당히 소모적인 방식이다.

얼리 Z 테스트는 이런 일반적인 방법과 반대로 픽셀 셰이딩 이전에 미리 드랍 여부를 테스트한다. 또한, 제조사 및 아키텍쳐 별로 다른 접근법을 추가해 픽셀셰이딩 과정 중에도 드랍이 판단되면 연산을 멈추는 기능을 지원하고있다.


또한, 모바일 플랫폼의 특징 중 하나로는 '단편화'가 발생한다는 것인데, 이는 같은 제조사의 같은 모델인데도 어떤 기종에서는 실행이 되고, 어떤 기종에서는 안되는 현상을 말한다. GPU 제조사도 많고, 아키텍쳐가 다를 때마다 지원되는 툴이 다르며, 모바일 AP나 제조사에서 배포하는 드라이버가 다를 수도 있어서 이러한 현상이 발생한다.

애플의 Metal api의 경우는 하나의 제조사가 모든 것을 관리하게 때문에 단편화가 거의 발생하지 않는다. 반면 안드로이드는 상당수의 회사가 존재하며 영향이 미칠 수 있는 부분이 많기에 OpenGL ES에서는 glGetString api를 통해 해결하는 편이다. 언리얼 엔진 4에서는 어떤 렌더링 그래픽스 api를 결정하고, 디바이스 별 Feature를 결정하는지 판단하는 것이 가능하며, 추가적으로 ES3.1 버전을 ES2로 폴백시키는 툴을 지원하는 등으로 단편화를 해결할 수 있다.


언리얼 엔진4가 지원하는 그래픽스 API로는 데스크탑에서는 Mac OS를 제외하고 벌칸과 OpenGL을 모두 지원하며, 모바일 플랫폼에서는 안드로이드에서 OpenGL ES와 벌칸, iOS는 Metal을 지원한다. 애플 진영의 벌칸 API에 대해서는 지난 2월 지원을 발표했으며, 현재 아직은 지원이 이뤄지지 않고 있다. 추가적으로 벌칸 API의 경우 아직 실험적으로 지원하고 있으며 많은 안정화과 성능 최적화가 필요한 상태다.

또한, 언리얼 엔진은 플랫폼 및 버전을 떠나 지원되는 기능 단위로 그래픽스 렌더링 API를 구분하고 있다. '피쳐 레벨'로 불리는 것으로 ES2, ES3_1, SM4, SM5 단계로 구분되며, 현재 차세대 API인 Metal과 벌칸이 ES3_1에 포함된 부분에 대해서는 추후 해당 API들이 더욱 성숙되고 안정화가 된다면 피쳐 레벨 구성이 달라질 수도 있다.




■ 모바일에서는 포워드 셰이딩만 지원? UE4 렌더러 살펴보기


언리얼 엔진에서는 여러 피쳐들을 활용해 Scene을 렌더링하게 되는데, 이를 책임지는 것이 렌더러다. 렌더러의 설명에 앞서, 렌더링 파이프라인은 하나의 지오메트리를 구려내는 과정을 의미한다. 처음 드로우콜(Draw Call)을 통해 버텍스 및 재질 정보 등을 입력하면 미리 세팅된 RenderState를 통해 버텍스에서 프리미티브가 되고, 이후 픽셀화가 되어 색이 덧입혀진 뒤 렌더 타겟에 블렌딩하는 과정을 거치게 된다.

이러한 여러 지오메트리를 엔진 레벨에서 한데 묶어 그리는 것을 렌더링 패스라고 하며, 렌더러는 결국 이러한 패스들을 연산을 통해 데이터로 이뤄진 씬을 그려내는 역할을 한다.


렌더러는 각 지오메트리를 그리는 방식에 따라 크게 디퍼드 셰이딩과 포워드 셰이딩으로 구분할 수 있다. 디퍼드 셰이딩은 mesh를 그릴 때 가지고 있는 여러 정보들을 G-buffer에 두고 필요한 정보를 새겨놓고, 이를 기반으로 모든 각각의 라이팅에 대해 mesh가 가진 정보를 활용해 픽셀들의 색을 결정하는 방식이다. 디퍼드 셰이딩은 다이나믹 렌더링에 좋고 surface attribute 변경이 불편하다는 장단점을 가지고 있다.

포워드 셰이딩의 경우는 보다 직관적으로 mesh에 영향을 주는 모든 라이트를 계산, 한 번에 그려내는 것을 의미하는데, Surface Attribute에 유연하고 하드웨어 MSAA를 사용할 수 있다는 장점이 있으나 동적 라이트 추가 시 성능 부하가 크다는 단점을 가지고 있다.

언리얼 엔진 4는 모바일 플랫폼에서는 포워드 셰이딩만 사용이 가능하며, 이 기능은 모바일 씬 렌더러가 담당하고 있다. 모바일에서 디퍼드 셰이딩을 지원하지 않는 이유는 G-buffer 사용시 많은 대역폭을 요구하며, 현 세대의 디바이스에서 게임을 플레이할 수 있는 정도의 성능을 낼 수 없기 때문이다. 대안으로는 iOS 전용으로 Metal 셰이더를 데스크탑 포워드 렌더러 기준으로 컴파일하는 Desktop-Class Forward Renderer가 존재한다.



■ UE4 모바일 씬 렌더러(Mobile Scene Renderer) 분석


언리얼엔진4의 모바일 씬 렌더러의 역할을 중요한 일만 간추리면 ▲InitViews(그려질 지오메트리를 선별), ▲MobileBasePass(불투명한 지오메트리를 그리기), ▲Translucency(반투명한 지오메트리 그리기), ▲PostProcessing(후처리)로 나눌 수 있다.

1) 그려질 지오메트리 선별


불필요한 것들을 렌더링 파이프라인에 그리면 낭비가 되기 때문에, 처음에 최적의 요소만을 찾는 과정이 중요하다. 언리얼 엔진은 '가시성 판단'이라고 하여 보여질 것만 추려내 특정 목록에 저장해 그리는 식으로 진행되는데, 이 때 가시성 판단은 객체 단위로 이뤄지게 된다.

Distance Culling은 해당 객체가 특정 거리에 있으면 그리고, 멀어지면 안 그린다는 지정을 하는 데 사용되며, Frustum Culling은 카메라 시야에 객체가 있는지의 여부를 판단한다. FOV의 각도가 크면 많은 영역을 그리고, 작으면 적은 영역을 그리게 된다. 이 때 카메라 시야 밖에 있는 객체를 날리는 데는 벡터의 바운드가 기준이 되기 때문에, 바운드가 불필요하게 클 경우 그려지는 mesh가 없음에도 그려질 지오메트리로 선별될 수 있기에 주의가 필요하다.

Precomputed Visibility는 더욱 복잡한 차폐처리를 하는 데 용이하다. 씬을 삼차원 그리드로 나누고, 셀마다 그 안에서 어떤 객체들이 보이는지에 대한 정보를 미리 구워놓고 실시간 시점에서 보이는 객체만 그리게끔 판단하는 방식이다. 마지막으로 객체가 실시간으로 다른 오브젝트에 가려졌는지를 결정하는 Occlusion Culling은 4.20 버전부터 지원될 예정이다.


2) 불투명, 반투명한 지오메트리를 그리기

불투명한 지오메트리를 그리는 것은 각 지오메트리에 대해 라이팅과 그리자를 처리하는, 가장 중요하고 복잡한 패스다. 라이팅과 그림자를 처리하는 방법은 크게 동적(Dynamic) 접근과 정적(Static) 접근으로 나뉜다. 반투명 지오메트리를 그리는 과정은 크게 다르지 않으나, 덮어쓰기가 아닌 Alpha Blending을 진행하여 뒤 배경이 비춰보일 수 있도록 한다.


  • 동적 (Dynamic) 접근

    동적인 접근은 일반적으로 라이트가 타입에 따라 다른 모양의 바운드를 가지며, 이 바운드 내에 존재하는 지오메트리는 라이팅을 받는다고 판단한다. 그림자의 경우는 주로 셰도우 맵(Shadow map)을 사용하는데, 이는 그림자를 그리고자 하는 픽셀부터 라이트까지 직선으로 연결할 때 무언가에 가려진다면 어두워진다고 판단하는 과정을 거친다. 라이트에서 씬을 바라보는 깊이 정보가 필요하기도 하다.

    동적 접근의 장점으로는 라이트의 색 추가/제거를 실시간으로 할 수 있다는 것이며, 지오메트리를 그리기 위해 추가적인 준비를 할 필요가 없다는 것이다. 다만, 그림자와 라이트의 추가가 성능에 큰 영향을 미친다는 단점도 가지고 있다.

    모바일을 위한 언리얼 엔진4에서는 동적 접근의 경우 라이트는 디렉셔널 라이트만 지원하는데, 성능을 위해 디폴트값은 꺼져있는 상태다. 또한 포인트, 스포트라이트의 Movable을 지원하지 않으며, 그림자 또한 Cascade shadow map만 지원한다.


  • 정적(Static) 접근

    정적인 접근의 경우 씬에 변화가 없다면 리얼타임에서 라이팅을 조절할 필요가 없기에, 시간을 충분히 들여 라이트와 그림자를 뿌려주기만 하자는 접근이다. 동적인 접근에 비해 적은비용으로 높은 퀄리티를 낼 수 있지만, 추가적인 메모리를 많이 차지하고 라이팅을 빌드시 시간이 많이 든다는 단점이 있다. 또한, 매번 변경이 생길 때마다 라이팅 빌드를 다시 해야 하는 단점도 있다.

    언리얼 엔진4 모바일에서는 모든 타입(디렉셔널, 포인트, 스포트)의 Static mobility 라이트를 지원하고, Stationary Light도 지원하지만 directional만을 지원한다. 그림자의 경우는 구워진 라이트맵이나 셰도우 맵을 사용해야 하며, 스태틱 메시에 대해서는 Distance Field Shadow를 사용할 수 있다.


  • 최고의 방법은 섞어 쓰는 것

    씬을 만들때는 위의 두 가지 방법을 조화롭게 사용하는 것이 중요하다. 정적인 방식은 원거리의 약한 라이팅을 표현할 때 사용해 성능을 아낄 수 있고, 실시간 간접광을 얻어올 수 있는 유일한 방법이기도 하다. 동적인 방식으로는 씬이 플레이어의 행동에 반응하는 것 처럼 만들 수 있으며, 정적인 라이팅/그림자에 동적인 형태를 덧입혀 현실감을 추가할 수도 있다.



    3) 후처리(Post Processing)


    후처리는 렌더링 과정 가장 마지막에 적용되는 비주얼 이펙트로, Scence color/Scene Depth 등 Off-screen 렌더 타겟들의 정보를 사용해 완성한다. 모바일과 다른 플랫폼의 기능과 퀄리티 부분의 차이가 가장 많이 나는 부분이기도 하다.

    후처리는 픽셀셰이더에 바운드된 과정이며, 굉장히 무거운 작업이기 때문에 언리얼 엔진4 모바일 포스트 프로세싱은 퀄리티를 조금 희생하더라도 성능을 우선적으로 확보하도록 구현되어 있다.

    성능적인 문제 때문에 모바일에서는 지원되는 포스트 프로세싱 기능 또한 어느 정도 제약되어 있다. Mobile TonemapperFilm을 켜두었을 떄는 컬러그레이딩과 일부 렌즈 효과, 포스트 프로세스 머테리얼과 같은 기능을 지원하며, 블룸이나 카메라, 노출 과 같은 기능들은 제한적으로 사용할 수 있다. TonemapperFilm이 꺼져있을 때는 컬러그레이딩을 지원하지 않으며, 포스트 프로세스 머테리얼도 지원되지 않는다. 아주 단순한 기능을 가진 모바일 톤맵퍼는 사용이 가능하다.

    ▲ TonemapperFilm을 켜두었을 때와

    ▲ 꺼두었을 때 지원되는 기능의 차이



    ■ 그 외 모바일 렌더링 Tip 모음


    ◎ 모바일 프리뷰 기능 제공
    = 언리얼 엔진4는 모바일 플랫폼 개발을 돕기 위해 '모바일 프리뷰' 기능을 제공하고 있다. 데스크탑에서는 Metal, OpenGl ES 등의 API를 지원하지 않지만, 이 기능을 통해 마치 지원되는 것처럼 모바일 피쳐를 시뮬레이션 하는 것이 가능하다. 다만, 실제 디바이스에서도 동일한 결과를 보장하지는 않으므로 색 등 종합적인 정보는 디바이스에 확인하는 것을 추천한다.

    ◎ 모바일 GPU에서 Read back 연산이 느리다
    = GPU 메모리 위의 Framebuffer에 대한 "Read pixel"이 발생할 경우 타일베이스 GPU에서는 모든 타일에 대한 연산이 완료되어야 읽어올 수 있다. 그렇게 되면 원치 않는 그래픽스 파이프라인의 지연이 일어나 성능이 저하될 수 있다.

    ◎ 드라이버 레벨 최적화
    = Ex. Opaque가 한번도 그려지지 않은 타일에 대해서 깊이 버퍼 초기화가 생략될 수도 있는데, 이렇게 되면 이전 프레임의 정보가 남아있게 되어 현재 프레임에서 깊이 버퍼 정보를 쓰는 연출에서 문제를 일으킬 수도 있다.

    ◎ 성능 쓰로틀링
    = 여기에 대한 명쾌한 답은 없다. 성능 쓰로틀링은 가정하고 작업해 나가는 것이 맞으며, CPU와 GPU가 종종 Idle 상태로 갈 수 있도록 자주 쉬게 해주는 것이 중요하다.


    ◎ 드로우콜(Draw call) 최소화
    = 드로우콜 발생시 어떤 지오메트리를 그리냐와는 무관하게 항상 메모리 할당, 데이터 복사등의 프로세스가 진행된다. 때문에 이러한 과정 자체가 성능 낮은 모바일 플랫폼에서는 힘들므로 되도록 메시가 배칭되도록 콘텐츠를 구성하는 것이 필요하다.

    ◎ RenderState 변경 최소화
    = 그래픽스 파이프라인은 상태 머신/ 상태 변경 하나하나가 오버헤드이기 때문이다. 언리얼 엔진 자체가 이를 고려해서 렌더러가 짜여 있긴 하지만, 패스별 지오메트리 특성별로 그룹화시키기 때문에 렌더러 수정시에 유의해야 한다.


    ◎ 성능을 올리려면 그래픽스 파이프라인이 쉬지 않도록 해야
    = 성능문제가 어디서 발생하는지 알아야 하는 것이 중요하다. Vertex Processing 문제라면 LOD를 적극적으로 활용하며, 원거리에 있어 잘 보이지 않는 세세한 지오메트리는 노멀맵으로 대체하는 방법이 있다. 또한 객체 단위 컬링을 대충 한 상태로 트라이앵클 클리핑에 기대는 것은 그래픽스 파이프라인에 큰 부담을 줄 수 있다.

    Pixel Processing에 문제가 있는 경우에는 Overdraw를 최대한 줄이는 방법이 있다. 하나의 픽셀을 결정할 때는 한 번에 그리는 것이 중요하고, 성능 버짓을 정확히 확인한 상태에서 가용할 수 있는 복잡도의 셰이더를 사용하는 것이 필요하다. 또한 언리얼 엔진은 전체 해상도를 줄여 연산할 픽셀 수를 조절하는 기능도 제공하고 있다.

    ◎ 메모리 대역폭 절약 = 배터리 소모 최소화
    = 모바일에서는 메모리 대역폭을 관리하는 것이 중요하다. 다른 플랫폼 대비 스펙이 부족하고, 주된 병목현상이 일어나는 부분이기도 하며, CPU와 GPU 모두가 같은 대역폭을 사용하다 보니 밸런스를 잡는 것이 필요하다.

    이를 위해서는 압축된 텍스쳐를 잘 활용하고, 불필요하게 높은 BIt Depth의 텍스쳐를 생성하지 않도록 하며, LOD 또한 적극적으로 활용하는 것이 좋다.