Unity에 대한 성능 권장 사항

이 문서는 혼합 현실에 대한 성능 권장 사항을 기반으로 작성되지만 Unity 관련 개선 사항에 중점을 둡니다.

최근 HoloLens 2 앱에 대한 일반적인 성능, 디자인 및 환경 문제와 솔루션을 다루는 품질 기본 사항이라는 응용 프로그램을 출시했습니다. 이 앱은 다음 콘텐츠를 위한 훌륭한 시각적 데모입니다.

Unity에서 혼합 현실 앱의 성능을 최적화할 때 가장 중요한 첫 번째 단계는 Unity에 권장되는 환경 설정을 사용하고 있는지 확인하는 것입니다. 해당 문서에는 성능이 뛰어난 Mixed Reality 앱을 빌드하기 위한 가장 중요한 장면 구성에 대한 콘텐츠가 포함되어 있습니다. 다음 섹션에서는 이러한 권장 설정 중 일부를 중점적으로 설명합니다.

Unity를 사용하여 프로파일링하는 방법

Unity는 특정 앱에 대한 귀중한 성능 인사이트를 수집할 수 있는 훌륭한 리소스인 Unity 프로파일러를 기본 제공합니다. 편집기 내에서 프로파일러를 실행할 수 있지만 이러한 메트릭이 실제 런타임 환경을 나타내지는 않으므로 결과를 신중하게 사용해야 합니다. 가장 정확하고 실행 가능한 통찰력을 위해 장치에서 실행하는 동안 원격으로 애플리케이션을 프로파일링합니다.

Unity는 다음에 대한 유용한 설명서를 제공합니다.

  1. UWP 응용 프로그램에 Unity 프로파일러를 원격으로 연결하는 방법
  2. Unity 프로파일러의 성능 문제를 효과적으로 진단하는 방법

GPU 프로파일링

Unity 프로파일러

Unity 프로파일러를 연결하고 GPU 프로파일러를 추가한 후(오른쪽 위 모서리의 프로파일러 추가 참조) 프로파일러 중간에 각각 CPU & GPU에 소요되는 시간을 확인할 수 있습니다. 이렇게 하면 개발자가 응용 프로그램이 CPU 또는 GPU에 바인딩된 경우 빠른 근사치를 얻을 수 있습니다.

Unity CPU 대 GPU

참고

GPU 프로파일링을 사용하려면 Unity 플레이어 설정에서 그래픽 작업을 비활성화해야 합니다. 자세한 내용은 Unity의 GPU 사용량 프로파일러 모듈을 참조하세요.

Unity 프레임 디버거

Unity의 프레임 디버거도 강력하고 통찰력 있는 도구입니다. GPU가 각 프레임에서 수행하는 작업에 대한 좋은 개요를 제공합니다. HoloLens에서는 비용이 많이 들기 때문에 다른 렌더링 대상과 복사할 blit 명령도 주의해야 합니다. 이상적으로는 HoloLens에서 화면 밖 렌더링 대상을 사용하지 않아야 합니다. 이는 피해야 하는 값비싼 렌더링 기능(예: MSAA, HDR 또는 블룸과 같은 전체 화면 효과)을 활성화할 때 추가됩니다.

HoloLens 프레임 속도 오버레이

장치 포털 시스템 성능 페이지에는 장치의 CPU 및 GPU 성능에 대한 적절한 요약이 있습니다. 헤드셋의 프레임 속도 카운터 표시와 헤드셋의 프레임 속도 그래프 표시를 사용하도록 설정할 수 있습니다. 이러한 옵션을 사용하면 장치에서 실행 중인 응용 프로그램에서 즉각적인 피드백을 제공하는 FPS 카운터와 그래프를 각각 활성화할 수 있습니다.

PIX

PIX를 사용하여 Unity 애플리케이션을 프로파일링할 수도 있습니다. 또한 HoloLens 2용 PIX를 사용하고 설치하는 방법에 대한 자세한 지침도 있습니다. 개발 빌드에서는 Unity의 프레임 디버거에 표시되는 것과 동일한 범위가 PIX에도 표시되며 더 자세히 검사하고 프로파일링할 수 있습니다.

참고

Unity는 XRSettings.renderViewportScale 속성을 통해 런타임에 애플리케이션의 렌더링 대상 해상도를 쉽게 수정할 수 있는 기능을 제공합니다. 장치에 표시되는 최종 이미지는 고정된 해상도를 갖습니다. 플랫폼은 디스플레이에서 렌더링하기 위해 더 낮은 해상도의 출력을 샘플링하여 더 높은 해상도의 이미지를 구축합니다.

UnityEngine.XR.XRSettings.renderViewportScale = 0.7f;

CPU 성능 권장 사항

아래 콘텐츠에서는 특히 Unity & C# 개발을 대상으로 하는 보다 심층적인 성능 사례를 다룹니다.

캐시 참조

GetComponent,<T>() 및 Camera.main과 같은 반복 함수 호출은 포인터를 저장하는 메모리 비용에 비해 비용이 더 많이 들기 때문에 초기화 시 모든 관련 구성 요소 및 GameObject에 대한 참조를 캐싱하는 것이 좋습니다. Camera.main은 아래에 FindGameObjectsWithTag() 를 사용하여 장면 그래프에서 "MainCamera" 태그가 있는 카메라 개체를 검색합니다.

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour
{
    private Camera cam;
    private CustomComponent comp;

    void Start() 
    {
        cam = Camera.main;
        comp = GetComponent<CustomComponent>();
    }

    void Update()
    {
        // Good
        this.transform.position = cam.transform.position + cam.transform.forward * 10.0f;

        // Bad
        this.transform.position = Camera.main.transform.position + Camera.main.transform.forward * 10.0f;

        // Good
        comp.DoSomethingAwesome();

        // Bad
        GetComponent<CustomComponent>().DoSomethingAwesome();
    }
}

참고

GetComponent(string) 피하기
GetComponent()를 사용할 때 몇 가지 다른 오버로드가 있습니다. 항상 형식 기반 구현을 사용하고 문자열 기반 검색 오버로드를 사용하지 않는 것이 중요합니다. 장면에서 문자열로 검색하는 것은 유형으로 검색하는 것보다 비용이 더 많이 듭니다.
(양호) 구성 요소 GetComponent(Type type)
(양호) T GetComponent<T>()
(나쁨) 구성 요소 GetComponent(string)>

비용이 많이 드는 작업 방지

  1. LINQ 사용 방지

    LINQ는 클린하고 읽고 쓰기 쉽지만 일반적으로 알고리즘을 수동으로 작성하는 경우보다 더 많은 계산과 메모리가 필요합니다.

    // Example Code
    using System.Linq;
    
    List<int> data = new List<int>();
    data.Any(x => x > 10);
    
    var result = from x in data
                 where x > 10
                 select x;
    
  2. 일반적인 Unity API

    특정 Unity API는 유용하지만 실행 비용이 많이 들 수 있습니다. 이러한 작업의 대부분은 전체 장면 그래프에서 일치하는 게임 개체 목록을 검색하는 작업입니다. 일반적으로 참조를 캐싱하거나 GameObject의 관리자 구성 요소를 구현하여 런타임에 참조를 추적함으로써 이러한 작업을 방지할 수 있습니다.

        GameObject.SendMessage()
        GameObject.BroadcastMessage()
        UnityEngine.Object.Find()
        UnityEngine.Object.FindWithTag()
        UnityEngine.Object.FindObjectOfType()
        UnityEngine.Object.FindObjectsOfType()
        UnityEngine.Object.FindGameObjectsWithTag()
        UnityEngine.Object.FindGameObjectsWithTag()
    

참고

SendMessage() 및 BroadcastMessage() 는 어떤 대가를 치르더라도 제거해야 합니다. 이러한 함수는 직접 함수 호출보다 1000배 느릴 수 있습니다.

  1. 권투를 조심하세요

    복싱 은 C# 언어 및 런타임의 핵심 개념입니다. , , intbool등과 같은 char값 유형 변수를 참조 유형 변수로 래핑하는 프로세스입니다. 값 형식 변수가 "boxed"되면 관리되는 힙에 저장되는 로 래핑 System.Object됩니다. 메모리가 할당되고 결국 폐기될 때 가비지 수집기에서 처리해야 합니다. 이러한 할당 및 할당 취소에는 성능 비용이 발생하며 많은 시나리오에서 불필요하거나 더 저렴한 대안으로 쉽게 대체할 수 있습니다.

    상자 지정을 방지하려면 숫자 형식 및 구조체( 포함Nullable<T>)를 저장하는 변수, 필드 및 속성이 개체를 사용하는 대신 , , float? 또는 MyStruct와 같은 int특정 형식으로 형식화되어 있는지 확인합니다. 이러한 개체를 목록에 넣는 경우 또는 ArrayList와 같은 List<int>List<object> 강력한 형식의 목록을 사용해야 합니다.

    C로 된 복싱의 예#

    // boolean value type is boxed into object boxedMyVar on the heap
    bool myVar = true;
    object boxedMyVar = myVar;
    

반복되는 코드 경로

초당 여러 번 및/또는 프레임에 여러 번 실행되는 반복적인 Unity 콜백 함수(예: 업데이트)는 신중하게 작성해야 합니다. 여기에 있는 비용이 많이 드는 작업은 성능에 크고 일관된 영향을 미칩니다.

  1. 빈 콜백 함수

    특히 모든 Unity 스크립트가 Update 메서드를 사용하여 자동 초기화되기 때문에 아래 코드를 애플리케이션에 남겨두기에는 무해해 보일 수 있지만, 이러한 빈 콜백은 비용이 많이 들 수 있습니다. Unity는 Unmanaged 코드와 관리 코드 경계, UnityEngine 코드와 애플리케이션 코드 간에 오가며 작동합니다. 이 브리지를 통한 컨텍스트 전환은 실행할 것이 없더라도 상당히 비용이 많이 듭니다. 이는 앱에 빈 반복 Unity 콜백이 있는 구성 요소가 포함된 100개 이상의 GameObject가 있는 경우 특히 문제가 됩니다.

    void Update()
    {
    }
    

참고

Update()는 이 성능 문제의 가장 일반적인 징후이지만 FixedUpdate(), LateUpdate(), OnPostRender", OnPreRender(), OnRenderImage() 등과 같은 다른 반복되는 Unity 콜백은 그만큼 나쁠 수 있습니다.

  1. 프레임당 한 번 실행하는 것을 선호하는 작업

    다음 Unity API는 많은 홀로그램 앱에서 일반적인 작업입니다. 항상 가능한 것은 아니지만 이러한 함수의 결과는 일반적으로 한 번 계산할 수 있으며 결과는 지정된 프레임에 대해 응용 프로그램 전체에서 다시 활용될 수 있습니다.

    1. 각 구성 요소에서 반복적이고 동일한 레이캐스트 작업을 수행하는 대신 장면에 대한 응시 레이캐스트를 처리한 다음 이 결과를 다른 모든 장면 구성 요소에서 다시 사용하는 전용 싱글톤 클래스 또는 서비스를 사용하는 것이 좋습니다. 일부 애플리케이션에는 다른 원본 또는 다른 레이어 마스크에 대한 레이캐스트가 필요할 수 있습니다.
        UnityEngine.Physics.Raycast()
        UnityEngine.Physics.RaycastAll()
    
    1. Start() 또는 Awake()에서 참조를 캐싱하여 Update()와 같은 반복되는 Unity 콜백에서 GetComponent() 작업을 방지합니다.
        UnityEngine.Object.GetComponent()
    
    1. 가능하면 초기화 시 모든 개체를 인스턴스화하고 개체 풀링 을 사용하여 애플리케이션의 런타임 내내 GameObject를 재활용하고 재사용하는 것이 좋습니다
        UnityEngine.Object.Instantiate()
    
  2. 인터페이스 및 가상 구문 피하기

    인터페이스 대 직접 개체를 통해 함수 호출을 호출하거나 가상 함수를 호출하는 것은 직접 구문이나 직접 함수 호출을 사용하는 것보다 비용이 훨씬 더 많이 들 수 있습니다. 가상 함수 또는 인터페이스가 필요하지 않은 경우 제거해야 합니다. 그러나 이러한 방법을 사용하면 개발 공동 작업, 코드 가독성 및 코드 유지 관리성이 단순화된다면 이러한 접근 방식의 성능 저하를 감당할 가치가 있습니다.

    일반적으로 이 멤버를 덮어써야 한다는 명확한 예상이 없는 한 필드와 함수를 가상으로 표시하지 않는 것이 좋습니다. 프레임당 여러 번 또는 프레임당 한 번씩 호출되는 고주파수 코드 경로(예: 메서드 UpdateUI() )에 특히 주의해야 합니다.

  3. 구조체를 값으로 전달하지 마세요.

    클래스와 달리 구조체는 값 형식이며 함수에 직접 전달되면 해당 내용이 새로 생성된 instance에 복사됩니다. 이렇게 복사하면 CPU 비용뿐만 아니라 스택에 더 많은 메모리가 추가됩니다. 작은 구조체의 경우 효과가 최소화되므로 허용 가능합니다. 그러나 모든 프레임에서 반복적으로 호출되는 함수와 큰 구조체를 사용하는 함수의 경우 가능한 경우 참조로 전달되도록 함수 정의를 수정합니다. 여기에서 자세히 알아보세요.

기타

  1. 물리학

    1. 일반적으로 물리학을 향상시키는 가장 쉬운 방법은 물리학에 소요되는 시간 또는 초당 반복 횟수를 제한하는 것입니다. 이렇게 하면 시뮬레이션 정확도가 떨어집니다. Unity의 TimeManager를 참조하세요.

    2. Unity의 충돌기 유형은 매우 다양한 성능 특성을 가지고 있습니다. 아래의 순서는 성능이 가장 우수한 콜라이더부터 성능이 가장 낮은 콜라이더까지 왼쪽에서 오른쪽으로 나열된 순서입니다. 메시 충돌기는 기본 충돌기보다 훨씬 더 비싼 경우 피하는 것이 중요합니다.

    구형 < 캡슐 < 상자 <<< 메쉬(볼록) < 메쉬(비볼록)

  2. 애니메이션

    애니메이터 구성 요소를 사용하지 않도록 설정하여 유휴 애니메이션을 사용하지 않도록 설정합니다(게임 개체를 사용하지 않도록 설정해도 동일한 효과가 없음). 애니메이터가 루프에 앉아 값을 동일한 항목으로 설정하는 디자인 패턴을 피합니다. 이 기술에는 상당한 오버헤드가 있으며 애플리케이션에는 영향을 주지 않습니다. 자세한 내용은 여기에 있습니다.

  3. 복잡한 알고리즘

    애플리케이션에서 역운동학, 경로 찾기 등과 같은 복잡한 알고리즘을 사용하는 경우 더 간단한 접근 방식을 찾거나 성능에 대한 관련 설정을 조정합니다.

CPU-GPU 성능 권장 사항

일반적으로 CPU-GPU 성능은 그래픽 카드에 제출된 그리기 호출에 따라 결정됩니다. 성능을 개선하려면 최적의 결과를 위해 그리기 호출을 전략적으로 a) 줄이거나b) 재구성 해야 합니다. 드로우 콜 자체가 리소스를 많이 사용하므로 드로우 콜을 줄이면 필요한 전체 작업이 줄어듭니다. 또한 그리기 호출 간의 상태 변경에는 그래픽 드라이버에서 비용이 많이 드는 유효성 검사 및 변환 단계가 필요하므로 상태 변경(즉, 다른 재질 등)을 제한하기 위해 애플리케이션의 그리기 호출을 재구성하면 성능이 향상될 수 있습니다.

Unity에는 개요를 제공하고 플랫폼에 대한 드로우 콜 일괄 처리에 대해 자세히 설명하는 훌륭한 문서가 있습니다.

단일 패스 인스턴스 렌더링

Unity의 단일 패스 인스턴스 렌더링을 사용하면 각 눈에 대한 그리기 호출을 하나의 인스턴스 드로우 호출로 줄일 수 있습니다. 두 그리기 호출 간의 캐시 일관성으로 인해 GPU의 성능도 일부 향상됩니다.

Unity 프로젝트에서 이 기능을 사용하도록 설정하려면

  1. OpenXR 설정을 엽니다(프로젝트 설정>편집>,XR 플러그 인 관리>,OpenXR로 이동).
  2. 렌더링 모드 드롭다운 메뉴에서 단일 패스 인스턴스화를 선택합니다.

이 렌더링 접근 방식에 대한 자세한 내용은 Unity의 다음 문서를 읽으세요.

참고

단일 패스 인스턴스화된 렌더링의 일반적인 문제 중 하나는 개발자가 인스턴싱용으로 작성되지 않은 기존 사용자 지정 셰이더를 이미 가지고 있는 경우에 발생합니다. 이 기능을 활성화한 후 개발자는 일부 GameObject가 한쪽 눈에서만 렌더링되는 것을 확인할 수 있습니다. 이는 연결된 사용자 지정 셰이더에 인스턴스화에 적합한 속성이 없기 때문입니다.

정적 일괄 처리

Unity는 GPU에 대한 그리기 호출을 줄이기 위해 많은 정적 개체를 일괄 처리할 수 있습니다. 정적 일괄 처리는 1) 동일한 재질을 공유하고 2) 모두 정적(Unity에서 개체를 선택하고 검사기의 오른쪽 상단에 있는 확인란을 선택)으로 표시된 Unity의 대부분의 렌더러 개체에서 작동합니다. 정적으로 표시된 게임 개체는 응용 프로그램의 런타임 전체에 이동할 수 없습니다. 따라서 거의 모든 개체를 배치, 이동, 크기 조정 등이 필요한 HoloLens에 정적 일괄 처리를 적용하기 어려울 수 있습니다. 몰입형 헤드셋의 경우 정적 일괄 처리를 사용하면 그리기 호출을 크게 줄여 성능을 향상시킬 수 있습니다.

자세한 내용은 Unity의 그리기 호출 일괄 처리 아래의 정적 일괄 처리를 참조하세요.

동적 일괄 처리

HoloLens 개발을 위해 개체를 정적 으로 표시하는 것은 문제가 되므로 동적 일괄 처리는 이 부족한 기능을 보완하는 훌륭한 도구가 될 수 있습니다. 몰입형 헤드셋에서도 유용할 수 있습니다. 그러나 게임 오브젝트는 a) 동일한 재질을 공유하고 b) 기타 긴 기준 목록을 충족해야 하기 때문에 Unity의 동적 일괄 처리를 활성화하기 어려울 수 있습니다.

전체 목록은 Unity의 그리기 호출 일괄 처리에서 동적 일괄 처리를 읽어보세요. 가장 일반적으로 GameObject는 연결된 메시 데이터가 300개 이하의 꼭짓점일 수 있기 때문에 동적으로 일괄 처리할 수 없게 됩니다.

기타 기술

일괄 처리는 여러 GameObject가 동일한 자료를 공유할 수 있는 경우에만 발생할 수 있습니다. 일반적으로 게임 오브젝트가 각 재질에 고유한 텍스처를 가져야 하기 때문에 이것이 차단됩니다. 일반적으로 텍스처를 하나의 큰 텍스처로 결합하는데, 이를 텍스처 아틀래싱이라고 합니다.

또한 가능하고 합리적인 경우 메시를 하나의 GameObject로 결합하는 것이 좋습니다. Unity의 각 렌더러에는 연결된 그리기 호출이 있습니다. 반면 하나의 렌더러 아래에서 결합된 메시를 제출하는 것입니다.

참고

런타임에 Renderer.material의 속성을 수정하면 재질의 복사본이 만들어져 잠재적으로 일괄 처리가 중단됩니다. Renderer.sharedMaterial을 사용하여 GameObject 간에 공유 재질 속성을 수정합니다.

GPU 성능 권장 사항

대역폭 및 채우기 속도

GPU에서 프레임을 렌더링할 때 애플리케이션은 메모리 대역폭 또는 채우기 속도에 의해 바인딩됩니다.

  • 메모리 대역폭 은 GPU가 메모리에서 수행할 수 있는 읽기 및 쓰기 속도입니다
    • Unity의 프로젝트설정>편집>품질 설정에서 텍스처 품질을 변경합니다.
  • 채우기 속도 는 GPU에서 초당 그릴 수 있는 픽셀을 나타냅니다.

깊이 버퍼 공유 최적화

홀로그램 안정성을 최적화하려면 깊이 버퍼 공유를 사용하도록 설정하는 것이 좋습니다. 이 설정으로 깊이 기반 후기 단계 재투영을 사용하는 경우 24비트 깊이 형식 대신 16비트 깊이 형식을 선택하는 것이 좋습니다. 16비트 깊이 버퍼는 깊이 버퍼 트래픽과 관련된 대역폭(따라서 전력)을 크게 줄입니다. 이는 전력 감소와 성능 모두에서 크게 개선될 수 있습니다. 그러나 16비트 깊이 형식을 사용하면 두 가지 부정적인 결과가 발생할 수 있습니다.

Z-파이팅

깊이 범위 충실도가 감소하면 24비트보다 16비트에서 z 파이팅 이 발생할 가능성이 더 높아집니다. 이러한 아티팩트를 방지하려면 낮은 정밀도를 고려하여 Unity 카메라의 근거리/원거리 클립 평면을 수정합니다. HoloLens 기반 애플리케이션의 경우 Unity의 기본 1,000m 대신 50m의 원거리 클립 평면은 일반적으로 z-fighting을 제거할 수 있습니다.

비활성화된 스텐실 버퍼

Unity에서 16비트 깊이의 렌더링 텍스처를 만들 때 스텐실 버퍼가 생성되지 않습니다. Unity 설명서에 설명된 대로 24비트 깊이 형식을 선택하면 24비트 z 버퍼와 8비트 스텐실 버퍼가 생성됩니다(일반적으로 그렇듯이 장치(예: HoloLens)에 32비트가 적용되는 경우).

전체 화면 효과 방지

전체 화면에서 작동하는 기술은 프레임마다 수백만 번의 연산이 이루어지기 때문에 비용이 많이 들 수 있습니다. 앤티앨리어싱, 블룸 등과 같은 후처리 효과 는 피하는 것이 좋습니다.

최적의 조명 설정

Unity의 실시간 글로벌 조명은 뛰어난 시각적 결과를 제공할 수 있지만 비싼 조명 계산을 포함합니다. 창>렌더링>조명 설정을> 통해 모든 Unity 장면 파일에 대해 실시간 글로벌 조명을 비활성화하고 실시간 글로벌 조명을 선택 취소하는 것이 좋습니다.

또한 모든 섀도 캐스팅을 비활성화하는 것이 좋습니다. 이는 또한 Unity 장면에 값비싼 GPU 패스를 추가하기 때문입니다. 그림자는 광원별로 비활성화할 수 있지만 품질 설정을 통해 전체적으로 제어할 수도 있습니다.

편집>프로젝트 설정을 선택한 다음 품질 범주 > 를 선택하고 UWP 플랫폼에 대해 낮은 품질을 선택합니다. Shadows 속성을 Disable Shadows로 설정할 수도 있습니다.

Unity의 모델에 베이킹된 조명을 사용하는 것이 좋습니다.

폴리 개수 줄이기

다각형 수가 다음 값 중 하나로 줄어듭니다.

  1. 장면에서 개체 제거
  2. 지정된 메시에 대한 다각형 수를 줄이는 자산 데시메이션
  3. 동일한 기하 도형의 하위 다각형 버전으로 멀리 있는 개체를 렌더링하는 애플리케이션에 LOD(디테일 수준) 시스템 구현

Unity의 셰이더 이해

성능에서 셰이더를 비교하는 쉬운 근사치는 런타임에 각 작업이 실행되는 평균 작업 수를 식별하는 것입니다. 이 작업은 Unity에서 쉽게 수행할 수 있습니다.

  1. 셰이더 자산을 선택하거나 재료를 선택한 다음 검사기 창의 오른쪽 위 모서리에서 선택합니다. 톱니바퀴 아이콘과 "셰이더 선택"을 차례로 선택합니다.

    Unity에서 셰이더 선택

  2. 셰이더 자산을 선택한 상태에서 검사기 창 아래에서 "코드 컴파일 및 표시" 단추를 선택합니다.

    Unity에서 셰이더 코드 컴파일

  3. 컴파일한 후 꼭짓점 셰이더와 픽셀 셰이더 모두에 대한 다양한 작업 수가 포함된 결과에서 통계 섹션을 찾습니다(참고: 픽셀 셰이더는 조각 셰이더라고도 함).

    셰이더 작업 Unity Standard

픽셀 셰이더 최적화

위의 방법을 사용하여 컴파일된 통계 결과를 살펴보면 조각 셰이더 는 일반적으로 평균적으로 꼭짓점 셰이더보다 더 많은 작업을 실행합니다. 픽셀 셰이더라고도 하는 조각 셰이더는 화면 출력에서 픽셀 단위로 실행되는 반면 꼭짓점 셰이더는 화면에 그려지는 모든 메시의 꼭짓점별로만 실행됩니다.

따라서 조각 셰이더는 모든 조명 계산으로 인해 꼭짓점 셰이더보다 더 많은 명령을 가질 뿐만 아니라 조각 셰이더는 거의 항상 더 큰 데이터 세트에서 실행됩니다. 예를 들어 화면 출력이 2k x 2k 이미지인 경우 조각 셰이더는 2,000*2,000 = 4,000,000번 실행될 수 있습니다. 두 개의 눈을 렌더링하는 경우 화면이 두 개 있으므로 이 숫자는 두 배가 됩니다. 혼합 현실 애플리케이션에 여러 패스, 전체 화면 후처리 효과가 있거나 여러 메시를 동일한 픽셀로 렌더링하는 경우 이 숫자가 크게 증가합니다.

따라서 조각 셰이더에서 작업 수를 줄이면 일반적으로 꼭짓점 셰이더의 최적화에 비해 훨씬 더 큰 성능 향상을 얻을 수 있습니다.

Unity Standard 셰이더 대안

PBR(물리적 기반 렌더링) 또는 다른 고품질 셰이더를 사용하는 대신 더 성능이 좋고 저렴한 셰이더를 활용하는 것이 좋습니다. Mixed Reality Toolkit은 혼합 현실 프로젝트에 최적화된 MRTK 표준 셰이더를 제공합니다.

또한 Unity는 Unity Standard 셰이더에 비해 빠른 무조명, 정점 조명, 확산 및 기타 간소화된 셰이더 옵션을 제공합니다. 자세한 내용은 기본 제공 셰이더의 사용 및 성능을 참조하세요.

셰이더 미리 로드

셰이더 미리 로드 및 기타 트릭을 사용하여 셰이더 로드 시간을 최적화합니다. 특히 셰이더 미리 로드는 런타임 셰이더 컴파일로 인한 장애가 표시되지 않음을 의미합니다.

오버드로 제한

Unity에서는 장면 보기의 왼쪽 위 모서리에 있는 그리기 모드 메뉴를 전환하고 오버드로를 선택하여 장면에 대한 덮어쓰기를 표시할 수 있습니다.

일반적으로 오버드로는 개체가 GPU로 전송되기 전에 미리 컬링하여 완화할 수 있습니다. Unity는 엔진에 대한 오클루전 컬링 구현에 대한 세부 정보를 제공합니다.

메모리 권장 사항

과도한 메모리 할당 & 할당 취소 작업은 홀로그램 애플리케이션에 부정적인 영향을 미쳐 일관되지 않은 성능, 고정된 프레임 및 기타 해로운 동작을 초래할 수 있습니다. 메모리 관리는 가비지 수집기에 의해 제어되므로 Unity에서 개발할 때 메모리 고려 사항을 이해하는 것이 특히 중요합니다.

가비지 수집

홀로그램 앱은 실행 중에 더 이상 scope에 속하지 않는 개체를 분석하기 위해 GC가 활성화되고 다시 사용할 수 있도록 메모리를 해제해야 할 때 GC(가비지 수집기)에 대한 처리 컴퓨팅 시간을 손실합니다. 지속적인 할당과 할당 취소는 일반적으로 가비지 수집기를 더 자주 실행해야 하므로 성능과 사용자 환경이 저하됩니다.

과도한 가비지 수집으로 이어지는 가장 일반적인 방법 중 하나는 Unity 개발에서 구성 요소 및 클래스에 대한 참조를 캐싱하지 않는 것입니다. 모든 참조는 Start() 또는 Awake() 중에 캡처하고 Update() 또는 LateUpdate()와 같은 이후 함수에서 다시 사용해야 합니다.

기타 빠른 팁:

  • StringBuilder C# 클래스를 사용하여 런타임에 복잡한 문자열을 동적으로 빌드합니다.
  • Debug.Log()는 앱의 모든 빌드 버전에서 계속 실행되므로 더 이상 필요하지 않은 경우 호출을 제거합니다.
  • 홀로그램 앱이 일반적으로 많은 메모리를 필요로 하는 경우 로드 또는 전환 화면을 표시할 때와 같은 로드 단계에서 System.GC.Collect() 를 호출하는 것이 좋습니다.

개체 풀링

개체 풀링은 지속적인 개체 할당 및 할당 취소 비용을 줄이는 데 널리 사용되는 기술입니다. 이것은 시간이 지남에 따라 개체를 지속적으로 생성하고 파괴하는 대신 동일한 개체의 큰 풀을 할당하고 이 풀에서 사용 가능한 비활성 인스턴스를 다시 사용하여 수행됩니다. 개체 풀은 앱 중에 수명이 가변적인 재사용 가능한 구성 요소에 적합합니다.

시작 성능

더 작은 장면으로 앱을 시작한 다음 SceneManager.LoadSceneAsync 를 사용하여 나머지 장면을 로드하는 것이 좋습니다. 이렇게 하면 앱이 가능한 한 빨리 대화형 상태에 도달할 수 있습니다. 새 장면이 활성화되는 동안 CPU 급증이 증가할 수 있으며 렌더링된 콘텐츠가 끊기거나 차질질 수 있습니다. 이 문제를 해결하는 한 가지 방법은 로드 중인 장면에서 AsyncOperation.allowSceneActivation 속성을 "false"로 설정하고, 장면이 로드될 때까지 기다린 다음, 화면을 검은색으로 지운 다음, 다시 "true"로 설정하여 장면 활성화를 완료하는 것입니다.

시작 장면이 로드되는 동안 홀로그램 시작 화면이 사용자에게 표시됩니다.