1. UI 역시 메시이다.
- 3D 그래픽에서 신경써야 하는 병목이 모두 똑같이 적용된다. Draw Call, Overdraw 등
- 드로우 콜(Draw Call) : 그래픽스 API가 GPU에게 화면에 무언가를 그리라고 명령하는 것, 드로우 콜이 많을 수록 CPU와 GPU 간의 통신 부하가 증가하여 게임의 렌더링 성능이 저하될 수 있음, 따라서 게임 개발에서는 도로우 콜의 수를 줄이기 위해 다양한 최적화 기법을 사용
- 오버드로우(Overdraw) : 화면의 특정 픽셀이 한 프래임 내에서 여러 번 렌더링되는 현상을 말함, 보통 여러 오브젝트가 같은 화면 공간을 공유할 때 발생하며, 특히 뒤에 있는 오브젝트가 앞에 있는 오브젝트에 의해 가려지는 경우에도 뒤에 있는 오브젝트가 렌더링되어 자원을 낭비하게 됨
- Canvas 역시 내부적으로 메시(Mesh) 렌더링 명령으로 생성됨, 즉 최종적으로 메시로 변환되어 GPU에 의해 렌더링됨
- Hierarchy에서 육안으로 분리가 되어 있어도 같은 Canvas안에 있으면 모두 리빌드(Rebuild)가 일어난다. 이는 Canvas가 UI 요소들의 렌더링을 최적화하고 관리하기 위해 디지안된 방식 때문이다.
- Tip : 정적인 객체가 있는 UI와 동적인 객체가 있는 UI와 캔버스를 나눠준다. 캔버스를 많이 나눠도 좋지 않음 , 갱신 비용이냐 혹은 드로우 콜 비용이라서 그때 그때 마다 다르다.
2. NestedCanvas
- NestedCanvas는 UI시스템에서 Canvas내에 다른 Canvas를 중첩하여 사용하는 기법, 이를 사용하여 UI요소의 렌더링과 조직을 더욱 효율적으로 관리할 수 있게 함
- 각 Canvas는 독립적인 렌더링 레이어를 형성하게 됨, 즉, 각 Canvas는 자신만의 렌더링 Mesh를 가지고 있어, 자식 Canvas에서 발생하는 변경 사항이 부모 Canvas나 다른 형제 Canvas에 영향을 미치지 않음, 이는 UI의 일부분만 자주 업데이트되는 경우, 해당 부분만 리빌드되도록 하여 전체적인 성능을 향상시킬 수 있음
- 자식 캔버스는 부모 캔버스의 특정 속성(스케일, 렌더모드 등)을 상속받을지 여부를 선택할 수 있습니다. 이를 통해 Canvas가 필요에 따라 독립적인 행동을 하거나, 부모로 부터 속성을 상속받아 일관된 행동을 할 수 있음
- Canvas 내에서 발생하는 리빌드가 다른 Canvas로 전파되지 않음, 이는 UI 요소 간의 독립성을 보장하며, 특정 부분의 업데이트가 전체 시스템에 불필요한 부하를 주지 않도록 함, 그러나 부모 Canvas의 크기 변경과 같이 전체 구조에 영향을 미치는 경우는 예외일 수 있음
- NestedCanvas 기법은 주로 Screen Space - Overlay 렌더 모드에서 사용됨
사용예시
예를 들어, 배경이 되는 정적인 UI 요소들은 별도의 정적인 Canvas에 배치하고, 사용자와의 상호작용이 잦은 버튼이나 동적인 요소들은 별도의 동적인 UI Canvas에 배치하는 방식입니다. 이렇게 하면 동적인 요소에 변경이 있을 때 정적인 요소가 포함된 Canvas를 리빌드할 필요가 없어, 성능을 최적화할 수 있습니다.
3. Dirty flag
Dirty Flag 시스템의 필요성
유니티에서 UI는 사용자의 입력, 게임 로직의 변경, 애니메이션 등으로 인해 계속해서 갱신될 필요가 있습니다. 이러한 지속적인 갱신은 특히 복잡한 UI 시스템에서 성능 이슈를 야기할 수 있습니다. Dirty Flag 시스템을 사용함으로써, 실제로 변경이 필요한 UI 요소만을 식별하고 업데이트함으로써 처리해야 하는 작업량을 줄일 수 있습니다.
작동 방식
- 변경 감지: UI 요소의 속성(예: 위치, 크기, 색상 등)에 변경이 발생하면 해당 요소에 Dirty Flag가 설정됩니다. 이는 시스템에게 해당 요소가 업데이트되어야 함을 알립니다.
- 조건부 업데이트: 렌더링 시스템은 Dirty Flag가 설정된 요소만을 대상으로 업데이트를 수행합니다. 즉, 변경이 없는 요소는 다시 렌더링되지 않아 성능을 절약할 수 있습니다.
- 계층 구조 영향: 유니티의 UI 요소는 계층 구조로 조직됩니다. 한 요소에 변경이 발생하면, 이는 상위 요소나 하위 요소에도 영향을 미칠 수 있습니다. 예를 들어, 하나의 UI 요소가 더티 상태가 되면, 이는 해당 요소를 포함하는 부모 패널도 더티 상태가 될 수 있습니다. 이러한 방식으로 Dirty Flag는 UI 계층 내에서 전파될 수 있습니다.
Dirty Flag의 장점
- 성능 최적화: 실제로 변경이 필요한 요소에만 집중함으로써 불필요한 렌더링 작업을 줄일 수 있습니다.
- 리소스 절약: CPU와 GPU 리소스를 절약할 수 있으며, 특히 모바일 같은 제한된 리소스를 가진 플랫폼에서 중요합니다.
- 동적 UI 관리 용이: 게임 내에서 동적으로 변경되는 UI 요소들을 효과적으로 관리할 수 있습니다.
4. RectTransform
- Transform이기 때문에 계층 구조가 가능
- RectTransform이 별개가 아니라 Transform이다. ( Transform을 상속받음)
- Transform이 변경될 때 (부모가 바뀔 때 ) - > 부모가 바뀌었어, 부모가 바뀌기전에도 해야하고 부모가 바뀌고 나서도 그리고 자식들도 (할 것이 많음)
- 단순히 활성, 비활성화 하는게 아니라 리페어런팅을 하는 경우도 있는게 이게 매우 싼 행위가 아니다.
5. Rebuild
- 모든 enabled 요소들의 메시를 재생성 (완전 투명 alpha ==0)이라도 생성된다. 그래서 계산을 함
- 캔버스 하나가 드로우 콜이 꼭 1개일 수 없음 각 상황에 따라 달라진다.
- 구조상으로 리페런싱(부모가 바뀔 때) 비싸다.
6. Batch building ( Canvas)
- 캔버스는 갱신이 없으면 계속 그대로 사용됨, 갱신이 있으면 그때 됨
- 갱신을 할 때 배칭 기준을 만들어놔야함 깊이 별로도 체크를 하고 머터리얼 같은걸 사용하는지와 같은 것을 고려하면서 한번에 드로우콜로 만들어 낼 수 있도록 데이터를 만들어낸다 그런 것을 Batch라고 한다. 그리고 멀티 스레드를 사용함
- 만드는 방법이 디바이스에 따라 다름, 휴대용, 게이밍 PC에 따라 다르다. (타겟 PC에서 프로파일링을 해야함)
7. Batching
- 동일한 캔버스
- 동일한 머티리얼 및 스프라이트 에셋
- 동일한 z 깊이의 RectTransform (만약 깊이가 달라지면 배치가 늘어남)
Rendering order : Front to Back vs Back to Front
- 블랜딩이 들어가면 뒤에 있는 애들 부터 그려야함
- UI이도 같이 Z에서 가장 멀리 있는 애를 그려야 하기 때문에 드로우 콜이 나눠져야 해서 배치가 늘어남
- 그래서 시각적으로 같더라도 z값이 나눠져 있으면 드로우 콜이 깨진다.
- 동일한 마스크 적용
- 스프라이트가 별도로 있으면 배칭이 안됨 -> 스프라이트 아틀라스 사용해서 묶자
- 프레임 프로파일링에서 3d 오브젝트는 드로우 콜이 깨진 이유를 알려줌 그런데 UI는 그런게 없음
- 기본 프로파일링에 UI 탭이 있다 거기서 배치가 깨진 이유에 대해서 알려준다.
- Allow Rotation , Tight Packing도 꺼져야 한다. 왜냐 하면 깨지기 때문에 외곽
8. Pixel Perfect
- RectTransform 변경 시
- Pixel Perfect를 체크하면 매우 큰 성능 이슈가 나타남 (느려짐) -> 성능을 많이 먹는 연산이다.
- Pixel Perfect를 꼭 해야 한다면 움직일 때는 Pixel Perfect를 끄는 그러한 개별적인 스크립트를 작성해야함
- NestedCanvas를 만들어서 Pixel Perfect를 상속안되도록 함 (그래서 티가 나거나 그리고 정적인 애들은 Pixel Pefect를 먹이고 동적인 애들은 안먹인다.)
9. Layout Componets (Vertical, Horizontal, Grid, Layout Element...)
- 정적으로 사용하는 애들은 문제가 없으나, 동적인 애들에게 사용하면 갱신 비용이 많이 든다.
- 간단하고 이쁘게 구조화 할 수 있지만 퍼포먼스 이슈가 있음, 그래서 남발하면 안됨
- Dirty flag 시스템에 의존적이다.
- 재구성이 필요 할 때 레이아웃을 활성화 (필요하지 않을 때는 비활성화)
- 재구축되는 하위 엘리먼트 수를 적게 구성
- UI 엘리먼트를 결합 ( ex : 버튼 전경과 배경 합치기) -> 작업이 비효율적일 수 있음. 버튼 전경만 바꾸고 싶은 경우도 있으니...
- 메모리 이슈 발생 가능 (모든 엘리먼트를 다시 갱신해야하기 때문에)
- 레이아웃 관리자 스크립트 제작
- 레이아웃이 업데이트 되는 타이밍 제어
- Object Pool 사용
Cavas Rebuilding - Animator
- idle 애니메이션은 키 프레임을 가져와서 보여주는 것이기 때문에 몇 프레임이 계속 돌기 때문에 갱신이 계속 이러함- UI에서는 최대한 Animator를 사용하지 말자.. 이벤트 성으로 나오는 애들은 닷트윈 같은것을 사용해라(스크립트 계열)
Raycaster
- 필요한 곳에서만 Raycaster 사용하라- 불필요한 곳에서는 비활성화해라- 인터렉션이 필요하다면 Canvas의 Raycaster를 비활성화 하거나 삭제하라 (캔버스에 있으면 하위에 있는 애들은 Raycater를 한다고 취급받는다.)
Full Screen UI- 전체 화면을 덮는 UI 보여줄 시에는 3D 씬 렌더링 불필요 이런 경우 활성화, 비활성화 보다는 카메라를 완전히 돌려버리거, 별도 카메라로 만들거나 하는 형태로 해야한다. 3D 렌더링이 안되도록 - 그리고 UI인 경우 프레임을 많이 먹을 필요가 없기 때문에 타겟 프레임을 30~20으로 나춰준다.
Text -> Icon- Text가 많을 수록 배칭이 깨질 확률이 높음- Text 대신 Icon 사용 권장 -> 다국어 처리에도 효과적
TextMeshPro- 영미권 언어는 큰 문제 없음- 한글은 초성 중성 종성 조합 방식- 꾩 <- 이런 거를 다 지원할 것인가 만약 다 지원하면 큰 Mesh에 그려넣어야하기 때문에 메모리 이슈가 있음- 4096으로 하면 모바일에서 대역폭 이슈가 생김
- Static vs DynamicStatic- 사용될 모든 문자 아틀라스를 미리 생성- 런타임시 원본 폰트가 필요 없음- NPC 대화, UI 관련 등은 Static으로 해도 문제없음Dynamic- 실시간으로 필요한 폰트 아틀라스 갱신- 런타임시 원본 폰트 필요- 사용 문자들의 범위 예측 불가능 시 유용
Multi Atlas Textures- Draw call vs 대역폭(Atlas Resolution)