공통 코드를 공유하는 셰이더 스니핏을 작성할 수 있지만, 특정 키워드가 활성화되거나 비활성화되면 다른 기능을 갖습니다. Unity가 이러한 셰이더 스니핏을 컴파일하면 활성화되거나 비활성화된 키워드의 다양한 조합에 대해 별도의 셰이더 프로그램을 생성합니다. 이러한 개별 셰이더 프로그램을 셰이더 배리언트라고 부릅니다.
셰이더 배리언트는 프로젝트 워크플로에 유용할 수 있습니다. 동일한 셰이더를 다른 머티리얼에 할당할 수 있지만, 키워드는 각각에 대해 다르게 설정할 수 있습니다. 즉 한 곳에서 셰이더 코드를 작성하고 유지하며, 프로젝트에서 더 적은 셰이더 에셋을 사용합니다. 또한 셰이더 배리언트를 사용하면 키워드를 활성화하거나 비활성화하여 런타임 시점에 셰이더 동작을 변경할 수 있습니다.
매우 많은 배리언트가 있는 셰이더는 “메가 셰이더” 또는 “우버 셰이더”라고 부릅니다. Unity의 스탠다드 셰이더는 그러한 셰이더의 한 예입니다.
셰이더 배리언트를 생성하려면 다음 지시문 중 하나를 셰이더 스니핏에 추가하십시오.
#pragma multi_compile
#pragma multi_compile_local
#pragma shader_feature
#pragma shader_feature_local
이러한 지시문을 일반 셰이더(표면 셰이더 포함)와 컴퓨트 셰이더에 사용할 수 있습니다.
그런 다음 Unity는 다양한 프리 프로세서 지시문을 사용하여 동일한 셰이더 코드를 여러 번 컴파일합니다.
셰이더 키워드를 활성화하거나 비활성화하려면 다음 API를 사용하십시오.
CommandBuffer
를 사용하여 전역 키워드 활성화CommandBuffer
를 사용하여 전역 키워드 비활성화키워드를 활성화하거나 비활성화하면 Unity는 그에 적합한 배리언트를 사용합니다.
예제 지시문:
#pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON
이 예제 지시문은 두 가지 셰이더 배리언트를 생성합니다. 하나는 FANCY_STUFF_OFF
가 정의되고 다른 하나는 FANCY_STUFF_ON
이 정의되어 있습니다. Unity는 머티리얼 또는 전역 셰이더 키워드에 따라 런타임 시점에 둘 중 하나를 활성화합니다. 두 키워드가 모두 활성화되지 않는 경우 Unity는 전자(이 예제에서는 FANCY_STUFF_OFF
)를 사용합니다.
multi_compile 줄에 두 개 이상의 키워드를 추가할 수 있습니다. 예를 들면 다음과 같습니다.
#pragma multi_compile SIMPLE_SHADING BETTER_SHADING GOOD_SHADING BEST_SHADING
이 예제 지시문은 네 가지 셰이더 배리언트, 즉 SIMPLE_SHADING
, BETTER_SHADING
, GOOD_SHADING
, 및 BEST_SHADING
을 생성합니다.
아무 전처리기 매크로도 정의되지 않은 셰이더 배리언트를 생성하려면 밑줄(__
)만 포함하는 이름을 추가하십시오. 프로젝트에서 사용할 수 있는 키워드 개수에는 제한이 있으며, 이렇게 하면 두 개의 키워드를 사용하는 것을 피할 수 있습니다(키워드 제한의 이후 섹션 참조). 예를 들면 다음과 같습니다.
#pragma multi_compile __ FOO_ON
이 지시문은 두 개의 셰이더 배리언트를 생성합니다. 하나는 아무것도 정의되어 있지 않고(__
) 다른 하나는 FOO_ON
이 정의되어 있습니다.
shader_feature
는 multi_compile
과 매우 유사합니다. 유일한 차이는 Unity가 shader_feature
셰이더의 미사용 배리언트를 최종 빌드에 포함하지 않는다는 점입니다. 이러한 이유로 머티리얼에서 설정된 키워드에는 shader_feature
를 사용하고 코드에서 전역으로 설정된 키워드에는 multi_compile
을 사용하는 것이 좋습니다.
또한 키워드가 하나뿐인 짧은 표기도 있습니다.
#pragma shader_feature FANCY_STUFF
이 표기는 #pragma shader_feature _ FANCY_STUFF
의 단축키로, 두 셰이더 배리언트로 확장됩니다(첫 번째는 정의가 없고 두 번째는 정의가 있음).
multi_compile
줄을 제공하는 경우 Unity는 줄의 모든 가능한 조합에 대한 결과 셰이더를 컴파일합니다. 예를 들어 다음과 같습니다.
#pragma multi_compile A B C
#pragma multi_compile D E
첫 번째 줄에서 세 가지 배리언트가 생성되고 두 번째 줄에서 두 가지 배리언트가 생성되어 총 여섯 가지의 셰이더 배리언트(A+D, B+D, C+D, A+E, B+E, C+E)가 생성됩니다.
각 multi_compile
줄이 셰이더 “기능” 하나를 제어한다고 생각하십시오. 이 방법을 사용하면 전체 셰이더 배리언트 수가 매우 빠르게 증가합니다. 예를 들어 옵션이 각각 두 개인 multi_compile
기능이 열 개가 있으면 셰이더 배리언트가 총 1,024개 생성됩니다.
셰이더 배리언트를 사용할 때는 Unity에서 키워드가 256개로 제한되고 이 중 약 60개가 내부적으로 사용되므로 실제로 사용 가능한 키워드는 더 적습니다. 또한 키워드는 Unity 프로젝트 전반에 걸쳐 전역으로 활성화되므로 여러 셰이더에 여러 키워드를 정의하는 경우에는 제한을 초과하지 않도록 주의해야 합니다.
shader_feature 와 multi_compile 의 주요 단점으로는 이 둘에서 정의된 모든 키워드가 Unity의 전역 키워드 개수 제한(256개 전역 키워드 + 64개 로컬)에 포함된다는 것을 들 수 있습니다. 이 문제를 피하려면 다른 셰이더 배리언트 지시문, 즉 shader_feature_local 과 multi_compile_local 을 사용할 수 있습니다.
로컬 지시문은 해당 지시문 아래에 정의된 키워드를 전체 프로젝트에 적용하는 대신, 해당 셰이더에 보관합니다. 이러한 이유로 전역 키워드가 아니라 로컬 키워드를 사용해야 합니다. 단, 전역 API를 통해 특정 키워드를 활성화하려는 경우는 예외입니다.
로컬 키워드 사용을 시작하면 성능상의 변화를 볼 수 있지만, 성능 차이는 프로젝트의 설정 방식에 따라 다릅니다. 셰이더당 로컬 및 전역 키워드의 총 개수는 성능에 영향을 미칩니다. 로컬 키워드는 많이 사용하고 전역 키워드는 적게 사용하여 셰이더당 총 키워드 개수를 줄이는 것이 이상적인 설정입니다.
동일한 이름의 전역 키워드와 로컬 키워드가 있는 경우 Unity는 로컬 키워드에 더 우선 순위를 둡니다.
전역 키워드를 변경하는 API가 포함된 로컬 키워드(예: Shader.EnableKeyword 또는 CommandBuffer.EnableShaderKeyword)는 사용할 수 없습니다.
셰이더당 최대 64개의 고유 로컬 키워드를 사용할 수 있습니다.
머티리얼에 활성화된 로컬 키워드가 있고 해당 셰이더가 더 이상 선언되지 않는 셰이더로 변경되면 Unity는 새로운 전역 키워드를 생성합니다.
# pragma multi_compile_local __ FOO_ON
이 지시문은 두 개의 셰이더 배리언트를 생성합니다. 하나는 아무것도 정의되어 있지 않고(__
) 다른 하나는 FOO_ON
이 로컬 키워드로 정의되어 있습니다.
로컬 키워드를 활성화하기 위한 프로세스는 전역 키워드와 동일합니다.
public Material mat;
Private void Start()
{
mat.EnableKeyword("FOO_ON");
}
여러 셰이더 배리언트를 컴파일하는 데 사용되는 “단축키” 표기가 몇 개 있으며, 주로 Unity의 다양한 광원, 그림자, 라이트맵 타입을 처리하는 데 사용됩니다. 자세한 내용은 렌더링 파이프라인 문서를 참조하십시오.
multi_compile_fwdbase
가 PassType.ForwardBase에 필요한 모든 배리언트를 컴파일합니다. 배리언트는 다양한 라이트맵 타입을 처리하며, 주요 방향 광원의 섀도우를 활성화하거나 비활성화합니다.multi_compile_fwdadd
는 PassType.ForwardAdd에 대한 배리언트를 컴파일합니다. 방향 광원, 스폿 광원 또는 점 광원 타입과 쿠키 텍스처가 있는 각각의 배리언트를 처리하도록 배리언트를 컴파일합니다.multi_compile_fwdadd_fullshadows
- multi_compile_fwdadd
와 동일하지만, 광원에 실시간 섀도우가 적용되는 기능도 포함합니다.multi_compile_fog
는 다양한 안개 타입(off/linear/exp/exp2)을 처리하는 여러 배리언트로 확장됩니다.대부분의 빌트인 단축키는 많은 셰이더 배리언트를 생성합니다. 프로젝트에 필요하지 않은 경우에는 #pragma skip_variants
를 사용하여 그중 일부에 대한 컴파일을 건너뛸 수 있습니다. 예를 들면 다음과 같습니다.
# pragma multi_compile_fwdadd
# pragma skip_variants POINT POINT_COOKIE
이 지시문은 POINT
또는 POINT_COOKIE
가 포함된 모든 배리언트를 건너뜁니다.
런타임 시점에 Unity는 GPU의 기능을 검사하고 해당하는 그래픽스 티어를 결정합니다. 빌트인 렌더 파이프라인에서는 각 그래픽스 티어에 대한 셰이더 배리언트 집합을 자동으로 생성할 수 있습니다. 이렇게 하려면 #pragma hardware_tier_variants
지시문을 사용하십시오.
이 기능은 빌트인 렌더 파이프라인과만 호환됩니다. 유니버설 렌더 파이프라인(URP), 고해상도 렌더 파이프라인(HDRP) 또는 커스텀 스크립터블 렌더 파이프라인과는 호환되지 않습니다.
이 기능을 활성화하려면 #pragma hardware_tier_variants renderer
를 추가하십시오. 여기서 renderer
는 다음과 같은 유효한 렌더링 플랫폼입니다.
# pragma hardware_tier_variants gles3
Unity는 다른 키워드 외에도 각 셰이더에 대해 세 가지의 셰이더 배리언트를 생성합니다. 생성된 각 배리언트에는 다음 정의 중 하나가 있으며, GraphicsTier 열거형의 동일한 번호가 매겨진 값에 해당합니다.
UNITY_HARDWARE_TIER1
UNITY_HARDWARE_TIER2
UNITY_HARDWARE_TIER3
이를 사용하여 저사양 또는 고사양 하드웨어에 대한 조건부 폴백 또는 추가 기능을 작성할 수 있습니다.
Unity는 애플리케이션을 처음 로드할 때 GraphicsTier
를 감지하고 결과를 Graphics.activeTier에 저장합니다. Graphics.activeTier
값을 오버라이드하려면 직접 설정하십시오. 단, 변경하려는 셰이더를 Unity가 로드하기 전에 이 작업을 수행해야 합니다. 이 값을 설정하기에 적합한 지점은 메인 씬을 로드하기 전의 사전 로드 씬입니다.
이러한 배리언트의 영향을 가능한 한 최소한으로 유지하기 위해 Unity는 하나의 셰이더 집합만 플레이어에 로드합니다. 또한, TIER1
에 대해서만 특수 버전을 작성하고 나머지는 모두 동일하게 한 경우처럼 결과적으로 동일한 셰이더는 디스크 공간을 추가로 사용하지 않습니다.
Unity 에디터에서 티어를 테스트하려면 Edit > Graphics tier로 이동한 후 Unity 에디터가 사용할 티어를 선택하십시오.
그래픽스 티어는 품질 설정과는 관계가 없으며, 이러한 설정에 추가됩니다.
빌트인 렌더 파이프라인에서 EditorGraphicsSettings.SetShaderSettingsForPlatform API를 사용하여 특정 BuildTarget 및 GraphicsTier에 대한 Unity의 내부 #defines를 오버라이드할 수 있습니다.
이 기능은 빌트인 렌더 파이프라인과만 호환됩니다. 유니버설 렌더 파이프라인(URP), 고해상도 렌더 파이프라인(HDRP) 또는 커스텀 스크립터블 렌더 파이프라인과는 호환되지 않습니다.
특정 BuildTarget
의 다른 GraphicsTier
에 대해 다른 TierSettings
값을 제공하는 경우 Unity는 해당 셰이더에 대한 배리언트를 생성합니다. 이는 셰이더 코드에 #pragma hardware_tier_variants
를 추가하지 않더라도 마찬가지입니다.