Version: 2023.1
언어: 한국어
드로우 커맨드 생성
렌더링 통계 창

DOTS 인스턴싱 셰이더

많은 수의 인스턴스를 효율적으로 렌더링하기 위해 BRG는 DOTS 인스턴싱이라는 새로운 셰이더 인스턴싱 모드를 사용합니다.BRG가 사용하는 모든 셰이더는 DOTS 인스턴싱을 지원해야 합니다.기존의 인스턴스화 셰이더에서 셰이더는 각 인스턴스화 프로퍼티에 대한 배열이 상수 또는 균일 버퍼에 전달하므로 각 배열의 각 요소는 드로우의 단일 인스턴스에 대한 프로퍼티 값을 포함합니다.DOTS 인스턴스화 셰이더에서 Unity는 각 DOTS 인스턴스화 프로퍼티에 대해 하나의 32비트 정수를 셰이더에 전달합니다.이 32비트 정수를 메타데이터 값이라고 합니다.이 정수는 원하는 모든 것을 나타낼 수 있지만, 일반적으로 셰이더가 렌더링 중인 인스턴스에 대한 프로퍼티 데이터를 로드하는 버퍼의 오프셋을 나타냅니다.

DOTS 인스턴싱에는 다음과 같이 기존 인스턴싱에 비해 많은 장점이 있습니다.

  • 인스턴스 데이터는 GraphicsBuffer에 저장되고 GPU에 지속적으로 유지되므로, Unity가 인스턴스를 렌더링할 때마다 다시 설정할 필요가 없습니다.인스턴스가 실제로 변경될 때만 데이터를 설정하면 인스턴스 데이터가 거의 변경되지 않거나 전혀 변경되지 않는 경우 성능을 상당히 향상시킬 수 있습니다.이는 엔진이 매 프레임마다 모든 인스턴스에 대한 모든 데이터를 설정해야 하는 기존 인스턴싱보다 훨씬 더 효율적입니다.
  • 인스턴스 데이터를 설정하는 프로세스는 드로우 콜 설정과는 별개입니다.이렇게 하면 드로우 콜을 가볍고 효율적으로 설정할 수 있습니다.BRG는 각 드로우 콜에 대해 최소한의 작업만 수행하는 SRP 배처의 특별한 빠른 경로(fast path)를 통해 이렇게 할 수 있습니다.이 작업에 대한 책임은 사용자에게 있으며 각 드로우 콜에서 렌더링할 것을 더 잘 제어할 수 있습니다.
  • 드로우 콜의 크기는 더 이상 상수 또는 균일 버퍼에 넣을 수 있는 인스턴스 데이터 양에 의해 제한되지 않습니다.따라서 BRG는 한 번의 드로우 콜로 더 많은 수의 인스턴스를 렌더링할 수 있습니다.
    참고:여전히 각 인덱스에는 일부 데이터가 필요하기 때문에 인스턴스 인덱스 수에 따라 드로우 콜 크기가 제한됩니다.하지만 인덱스는 전체 인스턴스화 프로퍼티 세트보다 메모리를 덜 사용하므로 더 많은 인스턴스를 상수 또는 균일 버퍼에 넣을 수 있습니다.에를 들어 각 인덱스에는 16바이트가 필요하므로 특정 플랫폼에서 버퍼의 메모리 제한이 64kb인 경우 4,096개의 인덱스를 버퍼에 넣을 수 있습니다.
  • 모든 인스턴스가 주어진 프로퍼티에 대해 동일한 값을 사용하는 경우 모든 인스턴스가 메모리의 동일한 위치에서 값을 로드하도록 할 수 있습니다.이렇게 하면 각 인스턴스 값을 복제하는 데 사용되는 메모리와 GPU 주기 수를 줄일 수 있습니다.

DOTS 인스턴싱 지원

DOTS 인스턴싱을 지원하려면 셰이더가 다음과 같아야 합니다.

  • 셰이더 모델 4.5 이상을 사용해야 합니다.#pragma target 4.5 이상을 지정해야 합니다.
  • DOTS_INSTANCING_ON 키워드를 지원해야 합니다.#pragma multi_compile _ DOTS_INSTANCING_ON으로 이를 선언해야 합니다.
  • 하나 이상의 프로퍼티가 있는 각 DOTS 인스턴스화 프로퍼티 블록을 하나 이상 선언해야 합니다.자세한 내용은 DOTS 인스턴스화 프로퍼티 선언을 참조하십시오.

참고:Unity가 URP와 HDRP에서 제공하는 셰이더 그래프와 셰이더는 DOTS 인스턴싱을 지원합니다.

DOTS 인스턴스화 프로퍼티 선언

트랜스폼 매트릭스와 같은 인스턴스 데이터를 로드하려면 셰이더가 DOTS 인스턴스화 프로퍼티를 정의해야 합니다.다음은 간단한 DOTS 인스턴스화 프로퍼티 블록 예시입니다.

UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata)
    UNITY_DOTS_INSTANCED_PROP(float4, Color)
UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata)

프로퍼티 블록의 시작과 끝을 표시하려면 블록 이름 앞에 UNITY_DOTS_INSTANCING_STARTUNITY_DOTS_INSTANCING_END 매크로를 사용합니다.예시에서는 이름을 MaterialPropertyMetadata로 사용했습니다.블록 이름에는 다음 세 가지가 허용됩니다.

  • BuiltinPropertyMetadata
  • MaterialPropertyMetadata
  • UserPropertyMetadata

셰이더는 각각 하나씩 선언할 수 있으므로 DOTS 인스턴스화 셰이더는 이러한 블록을 03개 가질 수 있습니다.Unity가 정의한 셰이더 코드는 UserPropertyMetadata를 사용하지 않으므로 이 이름은 자유롭게 사용할 수 있습니다.URP와 HDRP는 제공하는 모든 셰이더에 BuiltinPropertyMetadata를 정의하고 대부분의 셰이더에는 MaterialPropertyMetadata를 정의하므로 UserPropertyMetadata를 사용하는 것이 가장 좋습니다.커스텀 셰이더에는 세 가지 이름을 모두 사용할 수 있으며, 한 번에 모두 사용할 수도 있습니다.

블록에는 다음과 같은 형식의 DOTS 인스턴스화 프로퍼티 정의를 얼마든지 포함할 수 있습니다.

UNITY_DOTS_INSTANCED_PROP(PropertyType, PropertyName)

PropertyType은 부울 벡터를 제외한 어느 HLSL 빌트인 타입(예: unit, float4, float4x4, int2x4)이든 될 수 있으며, PropertyName은 DOTS 인스턴스화 프로퍼티의 이름입니다.DOTS 인스턴스화 프로퍼티는 일반 머티리얼 프로퍼티와는 완전히 별개이며, 다른 일반 머티리얼 프로퍼티와 동일한 이름을 지정할 수 있습니다.이는 UNITY_DOTS_INSTANCED_PROP 매크로가 Unity에서 다른 프로퍼티 이름과 상충하지 않는다고 인식하는 특별한 상수 이름을 생성하기 때문에 가능합니다.Unity가 제공하는 셰이더는 DOTS 인스턴스화 프로퍼티에 일반 머티리얼 프로퍼티와 동일한 이름을 부여하지만, 이 규칙을 따를 필요는 없습니다.

Unity는 내부적으로 셰이더가 선언하는 모든 DOTS 인스턴스화 프로퍼티에 대해 32비트 정수 메타데이터 값을 셰이더에 제공합니다.Unity는 코드가 BatchRendererGroup.AddBatch를 호출하여 드로우와 관련된 배치를 생성할 때 메타데이터 값을 설정합니다.Unity가 메타데이터 값을 설정하지 않은 경우 메타데이터의 기본값은 0입니다.또한 셰이더는 BatchRendererGroup.AddBatch에 인자로 전달한 GraphicsBuffer에 Unity가 설정하는 ByteAddressBuffer unity_DOTSInstanceData에도 액세스할 수 있습니다.이 버퍼는 일반적으로 셰이더가 인스턴스 데이터를 로드하는 위치입니다.여러 배치가 하나의 GraphicsBuffer를 공유할 수 있지만, 각 배치는 unity_DOTSInstanceData에 대해 별도의 GraphicsBuffer를 사용할 수도 있습니다.

참고:Unity는 DOTS 인스턴스화 데이터를 자동으로 제공하지 않습니다.각 배치의 unity_DOTSInstanceData 버퍼에 올바른 데이터가 포함되어 있는지 확인하는 것은 사용자의 책임입니다.인스턴스 데이터에는 트랜스폼 매트릭스, 라이트 프로브 계수, 라이트맵 텍스처 좌표와 같이 일반적으로 Unity가 게임 오브젝트용으로 제공하는 여러 프로퍼티가 포함되어 있어야 합니다.

DOTS 인스턴스화 프로퍼티 액세스

DOTS 인스턴스화 프로퍼티에 액세스하기 위해 셰이더가 Unity에서 제공하는 액세스 매크로 중 하나를 사용할 수 있습니다.액세스 매크로는 unity_DOTSInstanceData 내의 인스턴스 데이터가 다음 레이아웃을 사용한다고 가정합니다.

  • 메타데이터 값의 31개 최하위 비트는 unity_DOTSInstanceData 버퍼 내 배치에 있는 첫 번째 인스턴스의 바이트 주소를 포함합니다.
  • 메타데이터 값의 최상위 비트가 0인 경우 모든 인스턴스는 인스턴스 인덱스 0의 값을 사용합니다.즉, 각 인스턴스는 메타데이터 값의 바이트 주소에서 바로 로드됩니다.이 경우 버퍼는 인스턴스당 하나의 값 대신 단일 값만 저장하면 됩니다.
  • 메타데이터 값의 최상위 비트가 1인 경우 주소에는 AddressOfInstance0 + sizeof(PropertyType) * instanceID를 사용하여 인스턴스 인덱스 instanceID의 값을 찾을 수 있는 배열이 포함되어야 합니다.이 경우 렌더링된 모든 인스턴스 인덱스가 버퍼에 유효한 데이터를 포함했는지 확인해야 합니다.그렇지 않으면 범위를 벗어난 액세스와 정의되지 않은 동작이 발생할 수 있습니다.

또한 텍스처와 같이 위의 레이아웃을 사용하지 않는 커스텀 데이터 소스를 사용하려는 경우 유용한 메타데이터 값을 직접 설정할 수도 있습니다.

Unity는 다음과 같은 액세스 매크로를 제공합니다.

액세스 매크로 설명
UNITY_ACCESS_DOTS_INSTANCED_PROP(PropertyType, PropertyName) 위에 설명한 레이아웃을 사용하여 unity_DOTSInstanceData에서 로드된 값을 반환합니다.Unity가 제공하는 셰이더는 되돌아갈 기본값이 없는 DOTS 인스턴스화 빌트인 프로퍼티에 이 버전을 사용합니다.
UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(PropertyType, PropertyName) 메타데이터 값의 최상위 비트가 0인 경우 기본값을 반환한다는 점을 제외하면 UNITY_ACCESS_DOTS_INSTANCED_PROP과 동일하게 반환합니다.기본값은 DOTS 인스턴스화 프로퍼티와 동일한 이름을 가진 일반 머티리얼 프로퍼티의 값이며, 따라서 Unity가 제공하는 셰이더는 DOTS 인스턴스화 프로퍼티가 일반 머티리얼 프로퍼티와 동일한 이름을 갖는다는 규칙을 사용합니다.기본값을 사용하면 액세스 매크로는 unity_DOTSInstanceData에 전혀 액세스하지 않습니다.Unity가 제공하는 셰이더는 DOTS 인스턴스화 머티리얼 프로퍼티에 이 액세스 매크로를 사용하므로 로드는 머티리얼에 설정된 값으로 되돌아갑니다.
UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_CUSTOM_DEFAULT(PropertyType, PropertyName, DefaultValue) 메타데이터 값의 최상위 비트가 0이 아니면 UNITY_ACCESS_DOTS_INSTANCED_PROP과 동일하게 반환합니다. 메타데이터 값의 최상위 비트가 0인 경우 이 매크로는 DefaultValue를 대신 반환하며 unity_DOTSInstanceData에 액세스하지 않습니다.
UNITY_DOTS_INSTANCED_METADATA_NAME(PropertyType, PropertyName) 어디에도 액세스하지 않고 메타데이터 값을 직접 반환합니다.이는 커스텀 인스턴스 데이터 로딩 체계에 유용합니다.

이러한 매크로를 사용하는 방법에 대한 예시는 매크로 액세스 예시를 참조하십시오.

Unity는 드로우 커맨드 데이터에서 상수 값을 직접 로드하는 셰이더 함수를 액세스 매크로와 함께 제공합니다.Unity가 제공하는 셰이더는 이러한 함수를 사용합니다.

Unity는 다음과 같은 셰이더 함수를 제공합니다.

셰이더 함수 설명
LoadDOTSInstancedData_RenderingLayer 드로우 커맨드에 대해 renderingLayerMask를 반환합니다.
LoadDOTSInstancedData_MotionVectorsParams 드로우 커맨드에 대해 모션 벡터 생성 모드를 반환합니다.이는 Unity 셰이더가 기대하는 float4 형식으로 지정됩니다.
LoadDOTSInstancedData_WorldTransformParams 뒤집힌 삼각형 와인딩으로 인스턴스를 그릴지 여부를 반환합니다.FlipWinding을 참조하십시오.
LoadDOTSInstancedData_LightData 인스턴스에 대한 씬의 주요 방향 광원 활성화 여부를 반환합니다.주요 광원은 여러 이유(예: 광원이 라이트맵에 이미 포함된 경우)로 비활성화될 수 있습니다.
LoadDOTSInstancedData_LODFade LODCrossFade flag가 설정된 경우 사용자가 설정한 8비트 크로스페이드 값을 반환합니다.플래그가 설정되지 않은 경우 반환 값은 정의되지 않습니다.

액세스 매크로 예시

이 섹션에는 Unity가 제공하는 액세스 매크로 예시와 이러한 매크로를 사용하여 인스턴스당 데이터 및 상수 데이터에 액세스하는 방법에 대한 지침이 포함되어 있습니다.

인스턴스당

이 예제에서 다음을 참고하십시오.

  • Color의 메타데이터 값은 0x80001000입니다
  • 인스턴스 인덱스는 5입니다.
  • 인스턴스 0의 데이터는 주소 0x1000에서 시작합니다.
  • 인스턴스 5의 데이터는 주소 0x1000 + 5 * sizeof(float4) = 0x1050에 있습니다.

최상위 비트가 이미 설정되어 있으므로 액세서 매크로는 기본값을 로드하지 않습니다.즉, c0, c1, c2는 모두 unity_DOTSInstanceData 주소 0x1050에서 로드된 동일한 값을 가집니다.

void ExamplePerInstance()
{
    // rawMetadataValue will contain 0x80001000
    uint rawMetadataValue = UNITY_DOTS_INSTANCED_METADATA_NAME(float4, Color);

    float4 c0 = UNITY_ACCESS_DOTS_INSTANCED_PROP(float4, Color);
    float4 c1 = UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(float4, Color);
    float4 c2 = UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_CUSTOM_DEFAULT(float4, Color, float4(1, 2, 3, 4));
}

상수

이 예제에서 다음을 참고하십시오.

  • Color의 메타데이터 값은 0x00001000입니다
  • 인스턴스 인덱스는 5입니다.
  • 인스턴스 0의 데이터는 주소 0x1000에서 시작합니다.
  • 최상위 비트가 설정되지 않았으므로 인스턴스 5의 데이터는 인스턴스 0과 동일한 주소에 있습니다.

최상위 비트가 설정되지 않았으므로 기본값으로 되돌아가는 액세서 매크로는 unity_DOTSInstanceData에 액세스하지 않습니다.이는 다음을 의미합니다.

  • c0unity_DOTSInstanceData 주소 0x1000의 값을 포함합니다.
  • c1은 일반 머티리얼 프로퍼티 Color의 값을 포함하며, Color 프로퍼티가 없는 경우 컴파일 오류가 발생합니다.
  • c2는 명시적 기본값으로 전달되었으므로 (1, 2, 3, 4)를 포함합니다.
void ExampleConstant()
{
    // rawMetadataValue will contain 0x00001000
    uint rawMetadataValue = UNITY_DOTS_INSTANCED_METADATA_NAME(float4, Color);
    float4 c0 = UNITY_ACCESS_DOTS_INSTANCED_PROP(float4, Color);
    float4 c1 = UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(float4, Color);
    float4 c2 = UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_CUSTOM_DEFAULT(float4, Color, float4(1, 2, 3, 4));
}

세부 정보

모든 unity_DOTSInstanceData 버퍼의 첫 64바이트를 0으로 초기화하고 사용하지 않는 것이 가장 좋습니다.이는 배치 생성 시 지정되지 않은 모든 메타데이터 값에 대해 Unity가 사용하는 기본 메타데이터 값이 0이기 때문입니다.특히 셰이더가 UNITY_ACCESS_DOTS_INSTANCED_PROP 매크로에서 메타데이터 값 0을 로드할 때 인스턴스 인덱스가 무시되므로 셰이더는 이 값을 주소 zero에서 로드합니다.가장 큰 값 타입(float4x4 매트릭스)의 크기인 첫 64바이트가 0이인지 확인하면 이러한 로드에서 예측한 대로 0의 결과가 반환됩니다.그렇지 않으면 셰이더는 주소 0에 발생하는 상황에 따라 예측할 수 없는 것을 로드할 수 있습니다.

DOTS 인스턴싱을 사용할 때 Unity가 제공하는 셰이더 그래프와 셰이더는 트랜스폼 매트릭스에 특별한 규칙을 사용합니다.GPU 메모리와 대역폭의 사용을 줄이기 위해 4개의 플로트는 항상 일정하기 때문에 16개 전체가 아닌 12개의 플로트만 사용하여 이러한 매트릭스를 저장합니다.이러한 셰이더는 매트릭스에서 각 열의 x, y, z가 순서대로 저장되는 방식으로 지정된 형식의 플로트를 기대합니다.즉, 첫 3개의 플로트는 첫 번째 열의 x, y, z이고, 그 다음 3개의 플로트는 두 번째 열의 x, y, z인 방식입니다.매트릭스는 각 열의 w 요소를 저장하지 않습니다.이에 영향을 받는 트랜스폼 매트릭스는 다음과 같습니다.

  • unity_ObjectToWorld
  • unity_WorldToObject
  • unity_MatrixPreviousM
  • unity_MatrixPreviousMI

다음 코드 샘플에는 일반 4x4 매트릭스를 12개의 플로트 규칙으로 변환하는 구조체가 포함되어 있습니다.

struct PackedMatrix
{
    public float c0x;
    public float c0y;
    public float c0z;
    public float c1x;
    public float c1y;
    public float c1z;
    public float c2x;
    public float c2y;
    public float c2z;
    public float c3x;
    public float c3y;
    public float c3z;

    public PackedMatrix(Matrix4x4 m)
    {
        c0x = m.m00;
        c0y = m.m10;
        c0z = m.m20;
        c1x = m.m01;
        c1y = m.m11;
        c1z = m.m21;
        c2x = m.m02;
        c2y = m.m12;
        c2z = m.m22;
        c3x = m.m03;
        c3y = m.m13;
        c3z = m.m23;
    }
}
드로우 커맨드 생성
렌더링 통계 창