원문 : Fill-Rate, Canvases and Input

주의 : 번역이 개판이므로 이상하면 원문을 참조하십시오.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.



Fill-Rate, Canvases and Input


확인 완료한 버전: 5.3 - 난이도: 고급


이 챕터는 유니티 UI 를 구조화하는 것과 관련한 여러 이슈들에 대해 논의합니다.


Remediating Fill-Rate Issues


GPU 프래그먼트 파이프라인 상에서 부하를 제거하기 위해서 취해질 수 있는 두 가지 행동이 있습니다:


    • 프래그먼트 쉐이더의 복잡도를 줄이는 것입니다.
      • 더 많은 세부사항을 원한다면 "UI Shaders and low-spec devices" 섹션을 참고하십시오.
    • 샘플링되어야 하는 픽셀의 개수를 줄이는 것입니다.


UI 쉐이더는 일반적으로 표준화되어 있기 때문에, 가장 일반적인 문제는 단순히 fill-rate 용량을 초과하는 것입니다. 이는 대부분 UI 요소들이 매우 많이 겹쳐 그려지거나 화면의 많은 부분을 차지하는 UI 요소들이 많기 때문에 발생합니다. 둘다 높은 수준의 overdraw 를 유발할 수 있습니다.


fill-rate 에 대한 overutilization 을 줄이고 overdraw 를 줄이려면, 다음과 같은 해결책들을 고려해 보십시오.


Eliminating invisible UI


이 기법은, 플레이어에게 보이지 않는 비활성화된 요소들을 단순화하기 위해, 현존하는 UI 요소들을 최소로 재설계하는 것을 요구합니다. 가장 일반적인 경우는 불투명한 백그라운드를 가진 전체화면 UI 를 여는 것입니다. 이 경우 전체화면 UI 아래에 배치된 모든 UI 요소들은 비활성화될 수 있습니다.


이를 위한 가장 단순한 방법은 루트 게임오브젝트나 안 보이는 UI 요소를 포함하는 게임오브젝트들을 비활성화하는 것입니다. 대안적인 해결책을 원한다면 Disabling Canvas Renderer 섹션을 참고하십시오.


Disabling invisible camera output


불투명한 배경을 가진 전체 화면 UI 가 유니티 UI 에서 열려 있다면, 월드 공간 카메라는 여전히 표준 3D 씬을 UI 뒤에다가 렌더링하고 있을 것입니다. 이 렌더러는 전체화면 유니티 UI 가 전체 3D 씬을 가리고 있다는 사실을 인지하지 못합니다.


그러므로, 만약 완전한 전체화면 UI 가 열렸다면, 차폐된 모든 월드 공간 카메라를 비활성화하는 것은 GPU 부하를 줄여주며, 이는 단순히 불필요한 3D 공간 렌더링을 제거함으로써 수행됩니다.


노트: 만약 Canvas 가 "Screen Space - Overlay" 로 설정되어 있다면, 씬에서 활성하된 카메라의 개수를 무시하고 그려질 것입니다.


Majority-obscured cameras


많은 "전체화면" UI 들은 실제로는 전체 3D 월드를 차폐하지 않습니다. 그러나 월드의 일부분만을 보이게합니다. 이 경우에는, 보이는 월드 부분을 렌더 텍스쳐에 캡쳐하는 것이 더 효율적일 수 있습니다. 만약 월드의 보이는 부분이 렌더 텍스쳐에 "캐싱"되면, 실제 월드 공간 카메라가 비활성화될 수 있으며, 캐싱된 렌더 텍스쳐는 3D 월드에 대한 임포스터 버전을 제공하기 위해서 UI 스크린 뒤에 그려질 수 있습니다.


Composition-based UIs


컴포지션을 통해서 UI 를 생성하는 것은 매우 일반적입니다 - 최종 UI 를 생성하기 위해서 표준 백그라운드와 요소들을 합치고 레이어화하는 것입니다. 이 작업은 상대적으로 단순하고 반복적으로 수행하기 쉽지만, 그것은 유니티 UI 가 transparent 렌더링 큐를 사용하므로 성능에 좋지 않습니다.


백그라운드, 버튼, 그리고 버튼 위의 텍스트로 구성된 단순한 UI 를 생각해 봅시다. 텍스트 글리프( glyph ) 내에 포함된 픽셀의 경우, GPU 는 백그라운드 텍스쳐와 버튼 텍스쳐를 샘플링한 후에 최종적으로 텍스트 아틀라스 텍스쳐를 처리합니다. 결국 세 번의 샘플링이 필요합니다. UI 의 복잡도가 커질수록, 백그라운드 위쪽에 레이어화되어야 하는 요소들이 많아지며, 샘플링 개수는 급격하게 증가할 것입니다.


만약 큰 UI 가 fill-rate 한계( bound )에 도달했다는 것이 밝혀지면, UI 의 장식된( decorative )/불변하는( invariant ) 요소들을 그것의 백그라운드 텍스쳐와 머지하는 특별한 UI 스프라이트를 생성하는 것이 최선입니다. 이는 다른 요소 위에 레이어화되어야 하는 요소의 개수를 줄여주지만, 노가다가 필요하며 프로젝트의 텍스쳐 아틀라스의 크기가 증가합니다.


주어진 UI 를 특별한 UI 스프라이트 위에 생성하는 데 필요한 레이어화된 요소들을 압축하는데 있어서의 원칙은 sub-element 들을 위해서 사용될 수도 있습니다. 제품에 대한 스크롤링 팬을 가진 스토어 UI 를 생각해 봅시다. 각 제품의 UI 요소는 경계선, 배경, 가격을 표시하는 아이콘, 이름, 그리고 다른 정보들을 포함합니다.


스토어 UI 는 백그라운드를 필요로 하지만, 제품은 백그라운드 위에서 스크롤링되기 때문에 제품 요소는 스토어 UI 의 백그라운드 텍스쳐에 머지될 수 없습니다. 그러나 경계선, 가격, 이름, 그리고 다른 요소들은 제품의 백그라운드에 머지될 수 있습니다. 아이콘의 크기와 개수에 따라, fill-rate 절약을 고려해 볼 수 있습니다.


레이어화된 요소들을 합치는 것은 몇 가지 단점을 가지고 있습니다. 특별한 요소들을 더 이상 재사용할 수 없으며, 그것을 생성하기 위해서는 추가적인 아티스트 자원을 요구합니다. 새로운 큰 텍스쳐의 추가는 UI 텍스쳐를 저장하는데 필요한 메모리 총량을 증가시킬 것이며, 특히 UI 텍스쳐가 on-demand 로 로드되거나 언로드되지 않는다면 문제는 심각해질 것입니다.


UI Shaders and low-spec devices


유니티 UI 에 의해 사용되는 내장 쉐이더는 마스킹, 클리핑, 그리고 여러 가지 복잡한 연산에 대한 지원을 포함합니다. 이는 복잡도를 증가시키므로, UI 쉐이더는 iPhone 4 와 같은 low-end 장치 상에서의 더 단순한 유니티 2D 쉐이더와 비교했을 때 매우 느립니다.


만약 마스킹, 클리핑, 그리고 다른 "화려한" 기능들이 low-end 장치에서 필요하지 않다면, 최소 UI 쉐이더와 같은 불필요한 연산을 제거하는 커스텀 쉐이더를 만드는 것이 가능합니다.



UI Canvas Rebuilds


어떤 UI 를 디스플레이하기 위해서, UI 시스템은 스크린상에 표현된 각 UI 컴포넌트를 위한 지오메트리를 생성해야 합니다. 이는 동적 레이아웃 코드를 실행하거나 글자를 UI 텍스트 문자열로 표현하기 위해서 폴리곤을 생성한다거나 드로 콜을 최소화하기 위해서 가능한한 많은 지오메트리를 하나의 메쉬로 통합한다거나 하는 일들을 포함합니다. 이것은 여러 단계의 절차로 구성되며, 이 가이드의 시작부분의 Fandamentals 섹션에서 세부적으로 기술하고 있습니다.


두 가지 주요 원인 때문에 Canvas 를 리빌드하는 것은 성능 문제를 일으킬 수 있습니다:


    • 만약 Canvas 상에서 그려질 수 있는 UI 요소의 개수가 너무 많다면, 자체적으로 배치를 계산하는 비용이 매우 비싸집니다. 이는 요소들을 정렬하고 분석하는 비용 때문이며, 이 비용은 요소의 개수에 따라 비선형적으로 증가합니다.
    • 만약 Canvas 가 자주 갱신되면, 상대적으로 적은 변화를 가지는 Canvas 보다 리프레쉬하는데 더 많은 비용을 소비해야 합니다.


이러한 문제들은 모두 Canvas 상의 요소 개수가 증가하기 때문에 심해지는 경향이 있습니다.


중요하게 기억할 점: 그릴수 있는 UI 요소가 변경될 때마다, Canvas 는 배치 빌딩 절차를 재수행해야만 합니다. 이 절차는 그릴 수 있는 UI 요소 전체를 그것이 변경되었는지 여부와 관계없이 다시 분석합니다. UI 오브젝트의 외형에 영향을 주는 모든 변경을 "변경"이라고 하며, 이는 스프라이트 렌더러에 할당된 스프라이트, transform 위치와 스케일, 텍스트 메쉬에 포함된 텍스트 등을 포함합니다.


Child order


유니티 UI 들은 뒤에서 앞으로 생성되는데, 계층 구조 상에서 오브젝트의 순서가 그것의 정렬 순서를 결정합니다. 계층에서 먼저 등장하는 오브젝트는 나중에 등장하는 오브젝트보다 뒤에 있다고 간주됩니다. 배치는 계층을 위에서 아래로 돌면서 빌드되며, 같은 머티리얼과 텍스쳐를 사용하지만 중간 레이어( intermediate layer, 서로 다른 머티리얼을 가진 그래피컬 오브젝트인데, 그것의 바운딩 박스는 배치 가능한 다른 오브젝트와 겹치며 배치 가능한 두 오브젝트 사이의 계층에 배치되어 있습니다 )를 가지지 않는 모든 오브젝트들이 수집됩니다. 중간 레이어들은 배치를 깨게 됩니다.


Unity Frame Debugger 섹션에서 언급했듯이, 프레임 디버거는 중간 레이어들을 위한 UI 를 확인하기 위해 사용될 수 있습니다. 이는 한 오브젝트가 배치 가능한 다른 두 오브젝트 사이에 끼어들고 있는 상황입니다.


이 문제는 텍스트나 스프라이트가 다른 것들 근처에 위치할 때 자주 발생합니다: 텍스트의 바운딩 박스는 인접한 스프라이트와 보이지는 않지만 겹치게 됩니다. 왜냐하면 텍스트 글리프의 대부분의 폴리곤이 투명하기 때문입니다. 이는 두 가지 방법으로 해결될 수 있습니다:


    • 배칭될 수 없는 오브젝트에 의해서 배칭될 수 있는 오브젝트가 방해받지 않도록 요소를 재정렬합니다: 즉, 배칭될 수 없는 오브젝트를 배칭될 수 있는 오브젝트의 위쪽이나 아래쪽으로 움직입니다.
    • 오브젝트의 위치를 수정해서 보이지 않는 겹침을 제거합니다.


이 두 가지 방법은 모두 유니티 프레임 디버거를 활성화시켜서 수행될 수 있습니다. 유니티 프레임 디버거에서 보이는 드로콜의 개수를 관찰함으로써, UI 요소가 겹침으로써 낭비되는 드로콜의 개수를 최소화하는 순서와 위치를 찾는 것이 가능합니다.


Splitting Canvases


사소하기는 하지만, Canvas 를 나누는 것도 일반적으로 좋은 생각입니다. 요소들을 sub-canvas 나 인접한 Canvas 로 옮기는 것입니다.


( 예를 들어, 튜토리얼 화살표처럼 ) UI 의 특정 부분이 그 UI 의 나머지와는 다르게 깊이가 제어되어야 하는 경우에는 인접한 Canvas 로 옮기는 전략을 사용하는 것이 최선입니다.


다른 경우에는, sub-canvas 가 더욱 편리합니다. 왜냐하면 그것은 부모 Canvas 의 디스플레이 세팅을 상속하기 때문입니다.


일견하기에는 UI 를 sub-canvas 들에 하위분할하는 것이 최상이라 생각할 수 있지만, Canvas 시스템은 분리된 Canvas 들을 배치에 합치지 않는다는 점에 주의하십시오. 성능을 고려한 UI 설계는 리빌드 비용을 최소화하는 것과 드로콜을 낭비하는 것을 최소화하는 것 사이의 균형을 요구합니다.


General guidelines


Canvas  는 Canvas 내부의 변경된 요소들을 아무때나 리배칭하기 때문에, 단순하지 않은 Canvas 는 적어도 두 부분으로 분리하는 것이 일반적으로 최상입니다. 더우기 만약 요소들이 동시에 변경될 것을 기대하고 있다면 같은 Canvas 상에 요소들을 같이 배치하려고 시도하는 것이 최상입니다. 그 예로는 프로그레스 바와 카운트다운 타이머를 들 수 있습니다. 이것들은 모두 같은 데이터에 기반하고 있으므로, 동시에 갱신되는 것을 요구할 것이며, 그것들은 같은 Canvas 에 배치되어야 합니다.


정적이거나 변하지 않는 백그라운드나 레이블과 같은 모든 요소들을 한 Canvas 에 배치하십시오. 이것들은 Canvas 가 처음 디스플레이될 때 한 번에 배칭됩니다. 그리고 앞으로는 더 이상 리배칭이 필요하지 않을 것입니다.


"동적인" 요소들을 다른 캔버스에 배치하십시오 - 그것들은 자주 변경되는 것들입니다. 이는 이 Canvas 가 자주 갱신되는 요소들을 리배칭하는 것을 보장할 것입니다. 만약 동적 요소의 개수가 매우 많아진다면, ( 예를 들어 프로그레스 바, 타이머 표시, 움직이는 요소와 같은 ) 지속적으로 변화하는 요소 집합과 가끔 변화하는 요소 집합으로 분리하십시오.


경험적으로 볼 때 이는 좀 더 어렵습니다. 특히 UI 컨트롤들을 프리팹으로 캡슐화할 때 어렵습니다. 대신에 많은 UI 들은 비용이 많이 드는 컨트롤들을 Sub-Canvas 로 옮김으로써 Canvas 를 하위분할하는 정책을 씁니다.


Unity 5.2 and Optimized Bathcing


유니티 5.2 에서는 배칭 코드가 지속적으로 재작성되었으며, 4.6, 5.0, 5.1 과 비교했을 때 더 많은 성능 개선이 있었습니다. 더우기 1 개 이상의 코어를 가진 장치에서, 유니티 UI 시스템은 대부분의 처리를 워커 스레드에 넘길 것입니다. 일반적으로, 유니티 5.2 는 UI 를 수십개의 Sub-canvas 로 공격적으로 분할할 필요성을 제거했습니다. 모빌 장치 상의 많은 UI 들은 이제 2 ~ 3 개의 Canvas 들만 가지고도 좋은 성능을 낼 수 있습니다.


유니티 5.2 에서의 최적화에 대한 더 많은 정보를 원하신다면, 이 블로그 포스트를 참고하십시오.


Input and Raycasting In Unity UI


기본적으로, 유니티 UI 는 Graphic Raycast 컴포넌트를 사용해서 터치나 마우스 이벤트와 같은 입력 이벤트들을 다룹니다. 이는 일반적으로 Standalone Input Manager 컴포넌트에 의해 제어됩니다. 이름 대문에 Standalone Input Manager 가 "universal" 입력 관리 시스템으로 보이는데, 이는 마우스와 터치를 모두 다룰 것입니다.


Erroneous mouse input detection on mobile (5.3)


5.4 전에는 Graphic Raycaster 가 붙은 각각의 활성화된 Canvas 가 마치 터치 입력이 비활성화 되어 있는 것처럼 마우스의 위치를 검사하기 위해 매프레임 레이캐스트를 수행했습니다. 이는 플랫폼을 구분하지 않고 발생했습니다; iOS 와 안드로이드 장치에는 마우스가 없는데 여전히 마우스 위치를 질의하고 UI 요소들이 마우스 포인터 아래에 있는지 확인하려고 시도했습니다.


이는 CPU 시간을 낭비했으며, 유니티 애플리케이션 CPU 프레임 시간의 5% 이상을 소비하는 것이 밝혀졌습니다.


이 이슈는 5.4 에서 해결되었습니다: 5.4 부터는 마우스를 가지지 않은 장치는 마우스 위치를 질의하지 않으면 불필요한 레이케이트를 수행하지 않을 것입니다.


5.4 전 버전을 사용하고 있다면 모빌 개발자들은 자신만의 Input Manager 클래스를 작성할 것을 강력히 권합니다. 이는 유니티의 Standard Input Manager 를 유니티 UI 소스로부터 복사함으로써 단순화될 수 있으며, ProcessMouseEvent 메서드를 그 메서드에 대한 호출과 함께 주석처리해 버리면 됩니다.


Raycast optimization


Graphic Raycast 는 상대적으로 직관적인 구현이며, 이는 "Raycast Target" 설정이 true 인 모든 Graphic 컴포넌트를 돕니다. 각 Raycast Target 에 대해, Raycaster 는 몇 개의 테스트를 수행합니다. 만약 Raycast Target 이 모든 테스트를 통과하면, 히트 리스트에 그것이 추가됩니다.


Raycast implementation details


테스트는 다음과 같은 시점에 수행됩니다:


    • Raycast Target 이 활성화되어 있고, 그려질 때( 예를 들어 지오메트리 )
    • 입력 포인트가 Raycast Target 이 붙어 있는 RectTransform 내에 존재할 때
    • Raycast Target 이 ICanvasRaycastFilter 컴포넌트를 가지고 있거나 ( 깊이 상관없이 ) 그것의 자식일 때, 그리고 그 Raycast Filter 컴포넌트가 레이캐스트를 허용할 때


히트된 Raycast Target 의 리스트는 깊이에 의해 정렬되며, 순서를 바꾸기 위해서 필터링되며, 카메라 뒤에 렌더링되는 ( 예를 들어 스크린에 보이지 않는 ) 요소들을 제거하기 위해서 필터링됩니다.


Graphic Raycaster 는 Graphic Raycast 의 "Blocking Objects" 속성에 각각의 플래그가 설정되어 있으면 3D 나 2D 물리 시스템으로 레이를 캐스트할 수도 있습니다( 스크립트에서 수행하려면, blockingObjects 라는 속성임 ).


만약 2D 혹은 3D 블락킹 오브젝트가 활성화되어 있다면, raycast-blocking 물리 레이어 상에서 2D 혹은 3D 오브젝트 아래에서 그려지는 모든 Raycast Target 은 히트 리스트로부터 제거될 것입니다.


그리고 나서 최종 리스트가 반환됩니다.


Raycasting optimization tips


모든 Raycast Target 들은 Graphic Raycaster 에 의해서 테스트되어야만 한다면, 포인터 이벤트를 받아야만 하는 IU 컴포넌트 상의 "Raycast Target" 만 활성화하는 것이 최상입니다. Raycast Target 의 리스트를 더 작게 만들고 더 얕은 계층이 순회될 수록, Raycast 테스트의 속도가 빨라질 것입니다.


백그라운드와 텍스트가 모두 색상을 변경할 필요가 있는 버튼과 같이, 포인터 이벤트에 응답해야만 하는 다중의 UI 오브젝트들을 포함하는 복합 UI 컨트롤의 경우에는, 복합 UI 컨트롤의 루트에 하나의 Raycast Target 만을 배치하는 것이 더 좋습니다. 그 단일 Raycast Target 이 포인터 이벤트를 받게 되면, 그 이벤트를 관심있는 컴포넌트들에게 전달할 수 있습니다.


Hierarchy depth and raycast filters


각각의 Graphic Raycast 는 raycast 필터를 찾을 때 루트까지 모든 Transform 계층을 순회합니다. 이 연산의 비용은 계층 내의 깊이에 따라 선형적으로 증가합니다. 계층 내의 각 Transform 에 붙어 있는 모든 컴포넌트들이 ICanvasRaycastFilter 를 구현했는지 확인하기 위해 테스트되어야만 하므로, 이는 싼 연산이 아닙니다.


ICanvasRaycastFilter 를 사용하는 CanvasGroup, Image, Mask, RectMask2D 와 같은 표준 유니티 UI 컴포넌트들이 몇 개 있습니다. 그러므로 이 순회 비용은 쉽게 제거되기 힘듭니다.


Sub-canvas and the OverridedSorting property


Sub-canvas 에 있는 overrideSorting 속성은 Graphic Raycast 테스트가 Transform 계층을 올라가는 것을 막습니다. 만약 정렬이나 레이캐스트 검출 이슈없이 이것이 활성화될 수 있다면, 레이캐스트 계층 순회의 비용을 감소시키기 위해서 이것을 사용하십시오.

+ Recent posts