셰이더 순열이라고도 하는 셰이더 배리언트는 조건부 동작을 셰이더 코드에 도입하는 방법 중 하나입니다.
Unity는 셰이더 소스 파일을 셰이더 프로그램으로 컴파일합니다. 각 컴파일된 셰이더 프로그램에는 여러 조건에 대한 서로 다른 셰이더 프로그램 버전과 같이 하나 이상의 배리언트가 있습니다. 런타임 시 Unity는 현재 요구 사항과 일치하는 배리언트를 사용합니다. 배리언트는 셰이더 키워드를 사용하여 설정합니다.
셰이더 코드의 조건부 및 언제 어떤 기법을 사용해야 하는지에 관한 전반적 내용은 셰이더 코드의 조건부를 참조하십시오. Unity가 셰이더 배리언트를 로드하는 방법에 관해서는 셰이더 로딩을 참조하십시오.
수많은 배리언트가 있는 셰더를 ‘메가 셰이더’ 또는 ’우버 셰이더’라고 부릅니다. Unity의 스탠다드 셰이더가 바로 그러한 셰이더입니다.
셰이더 배리언트의 주요 장점은 GPU 성능에 영향을 미치는 동적 브랜치 없이도 셰이더 프로그램에서 런타임 조건부를 사용할 수 있다는 점입니다. 셰이더 배리언트의 주요 단점은 셰이더 배리언트의 수가 많을 경우 빌드 시간이 길어지고 런타임 성능이 저하될 수 있다는 점입니다.
Unity는 셰이더 배리언트를 생성할 때 정적 브랜치를 사용하여 여러 개의 작은 특수 셰이더 프로그램을 생성합니다. 런타임 시 Unity는 조건과 일치하는 셰이더 프로그램을 사용합니다. 이는 동적 브랜치에서 GPU 성능을 저하할 수 있는 코드에 대해 셰이더 배리언트를 사용함으로써 CPU 성능 하락을 막을 수 있다는 의미입니다.
하지만 배리언트가 너무 많으면 빌드 시간, 파일 크기, 런타임 메모리 사용량, 로딩 시간이 늘어날 수 있습니다. 이는 또한 셰이더를 수동으로 미리 로드(‘예열’)하는 경우 복잡성을 높일 수 있습니다. 프로젝트에 매우 많은 수의 셰이더 배리언트가 포함된 경우 이러한 문제는 성능 및 워크플로 측면에서 상당한 문제를 일으킬 수 있습니다.
경고: 셰이더 배리언트는 자칫하면 과대하게 늘어날 수 있으며 이는 심각한 성능 문제로 이어질 수 있습니다. 따라서 Unity가 셰이더 배리언트 개수를 결정하는 방법, 컴파일에서 불필요한 배리언트를 제외(“스트리핑”)하는 방법, 셰이더에서 다른 유형의 조건부를 사용해야 하는 경우를 반드시 이해해야 합니다.
빌드 시 Unity는 현재 빌드 타겟의 각 그래픽스 API에 대해 하나의 셰이더 배리언트 세트를 컴파일합니다. 그래픽스 API와 빌드 타겟의 각 조합에 대한 배리언트의 개수는 셰이더 소스 파일과 셰이더 키워드 사용 방식에 따라 결정됩니다.
셰이더 배리언트의 개수를 확인할 수 있습니다.
Unity는 현재 빌드 타겟의 리스트에 있는 각 그래픽스 API에 대해 하나의 셰이더 배리언트 세트를 컴파일합니다. 셰이더는 빌드 타겟과 그래픽스 API의 조합마다 달라집니다. 예를 들어 Unity는 Metal iOS와 Metal macOS를 위해 각각 다른 셰이더를 컴파일합니다.
일부 셰이더 프로그램이나 키워드는 특정 그래픽스 API나 특정 빌드 타겟만 타게팅할 수 있으므로, 그래픽스 API와 빌드 타겟의 각 조합을 위한 배리언트 수의 합계는 다를 수 있습니다. 단, 이러한 배리언트를 컴파일하는 프로세스는 동일합니다.
현재 빌드 타겟을 위한 그래픽스 API의 리스트를 확인하고 수정하려면 Player Settings 창이나 PlayerSettings API를 사용하십시오.
Unity는 현재 빌드 타겟과 그래픽스 API의 조합을 위해 컴파일할 셰이더 프로그램의 수를 정해야 합니다.
Unity는 빌드에 포함된 셰이더 소스 파일마다 몇 개의 고유한 셰이더 프로그램을 정의할지 다음과 같이 정합니다.
참고: 셰이더 소스 파일은 빌드의 씬에서 참조되었거나, Resources 폴더의 요소에 의해 참조되었거나, Graphics Settings 창의 Always-included shaders 섹션에 포함된 경우 해당 빌드에 포함된 것입니다.
Unity는 현재 빌드 타겟과 그래픽스 API를 위해 컴파일해야 하는 셰이더 프로그램을 정한 후, 각 셰이더 프로그램을 위해 컴파일해야 하는 셰이더 배리언트 수를 정합니다.
Unity는 각 셰이더 프로그램에 대해 서로 다른 배리언트를 생성하는 셰이더 키워드 조합을 결정합니다. 이는 다음으로 구성됩니다.
Unity가 셰이더 프로그램을 위해 컴파일하는 셰이더 배리언트의 수는 키워드 세트의 산물입니다. 즉, Unity는 각 세트의 요소 하나를 포함하는 각 조합마다 하나의 배리언트를 컴파일합니다.
예를 들어, 이 세트는 3가지 셰이더 배리언트 키워드를 포함합니다.
이 세트는 4가지 셰이더 배리언트 키워드를 포함합니다.
해당 셰이더 배리언트 키워드에 영향을 받는 셰이더 프로그램으로 인해 다음의 12가지 배리언트가 생성됩니다.
Unity가 컴파일하는 배리언트의 개수는 셰이더 배리언트 키워드를 추가함에 따라 급격히 늘어날 수 있습니다. 이러한 매우 급격한 증가를 조합적 폭발이라고 부릅니다.
예를 들어 하나의 셰이더에 대하여 두 개씩의 키워드(<feature name>_ON 및 <feature name>_OFF)를 포함하는 셰이더 배리언트 키워드 세트가 여럿 존재하는 매우 일반적인 사용 사례를 생각해 보겠습니다. 해당 셰이더에 대하여 이러한 키워드 세트가 2개 있다면 네 가지 배리언트가 생성됩니다. 해당 셰이더에 대하여 이러한 키워드 세트가 10개 있다면 1024개의 배리언트가 생성됩니다.
컴파일 후 Unity는 같은 패스 내 동일한 배리언트들을 자동으로 식별하여 동일 배리언트들이 같은 바이트코드를 가리키도록 합니다. 이를 중복 제거라고 합니다.
중복 제거는 같은 패스의 동일한 배리언트들로 인해 파일 크기가 늘어나는 것을 방지합니다. 단, 동일한 배리언트는 여전히 컴파일 시 불필요한 작업을 유발하며 런타임 시 메모리 사용량과 셰이더 로딩 시간도 증가시킵니다. 이를 감안하여 항상 불필요한 배리언트를 스트리핑하는 것이 좋습니다.