1. 程式人生 > 其它 >聊聊Enum型別的儲存技巧和HDRP的渲染架構

聊聊Enum型別的儲存技巧和HDRP的渲染架構

Enum型別的儲存技巧

前言

在HDRP裡經常在管線程式碼,或者在Shader裡面會看到有將若干個Enum儲存在一個uint或者一個int的操作,然後通過用位運算判斷Enum,所以在這裡記錄一下,水一篇博文。

Enum 列舉型別

在平時使用Enum的時候,直接用if判斷
例如:

    public enum LightCategory
    {
        Punctual,
        Area,
        Env,
        Decal,
        LocalVolumetricFog, // WARNING: Currently lightlistbuild.compute assumes Local Volumetric Fog is the last element in the LightCategory enum. Do not append new LightCategory types after LocalVolumetricFog. TODO: Fix .compute code.
        Count
    }

    void Test(){
        LightCategory m_LightCategory=LightCategory.Punctual;
        if(m_LightCategory==LightCategory.Punctual){
            .....
        }
    }

如果說在同一個上下文的情況下,好幾個Enum一併使用,就會多好多個Enum變數,不太方便傳參,當然也可以寫成一個Struct,儲存若干個Enum。
但是沒有這個必要,因為有點浪費記憶體,可以把這幾個Enum塞進一個uint裡面,(一個uint有32位,int31位存數值剩餘1位儲存符號)

作用一:重要性排序

接下來以HDRP的燈光SortKey來舉個實際一點的例子

    public enum LightCategory
    {
        Punctual,
        Area,
        Env,
        Decal,
        LocalVolumetricFog, // WARNING: Currently lightlistbuild.compute assumes Local Volumetric Fog is the last element in the LightCategory enum. Do not append new LightCategory types after LocalVolumetricFog. TODO: Fix .compute code.
        Count
    }
    public enum GPULightType
    {
        Directional,
        Point,
        Spot,
        ProjectorPyramid,
        ProjectorBox,

        // AreaLight
        Tube, // Keep Line lights before Rectangle. This is needed because of a compiler bug (see LightLoop.hlsl)
        Rectangle,
        // Currently not supported in real time (just use for reference)
        Disc,
        // Sphere,
    };
    public enum LightVolumeType
    {
        Cone,
        Sphere,
        Box,
        Count
    }
    
    //Packs a sort key for a light
    public static uint PackLightSortKey(LightCategory lightCategory, GPULightType gpuLightType, LightVolumeType lightVolumeType, int lightIndex)
    {
        //We sort directional lights to be in the beginning of the list.
        //This ensures that we can access directional lights very easily after we sort them.
        uint isDirectionalMSB = gpuLightType == GPULightType.Directional ? 0u : 1u;//確保平行光在陣列的最開端0<<31=0
        uint sortKey = (uint)isDirectionalMSB << 31 | (uint)lightCategory << 27 | (uint)gpuLightType << 22 | (uint)lightVolumeType << 17 | (uint)lightIndex;
        return sortKey;
    }

可以看到這裡有三個Enum,這裡的三個Enum外加一個lightIndex通過與運算全塞進到這個sortKey裡面

與運算例子:
0000 1000 0000 0000 0000 0000 0000 0000 ||  (32位)
0000 0000 0000 0000 0000 0000 0000 0001     (32位)
等於:
0000 1000 0000 0000 0000 0000 0000 0001     (32位)

比較明顯的一點,在英文的註釋裡說,希望通過isDirectionalMSB確保平行光在陣列的最開端0<<31=0
而對這個SortKey陣列排序的過程是在HDProcessedVisibleLightsBuilder.LightLoop.cs的Build函式的SortLightKeys()

    private void SortLightKeys()
    {
        using (new ProfilingScope(null, ProfilingSampler.Get(HDProfileId.SortVisibleLights)))
        {
            //Tunning against ps4 console,
            //32 items insertion sort has a workst case of 3 micro seconds.
            //200 non recursive merge sort has around 23 micro seconds.
            //From 200 and more, Radix sort beats everything.
            var sortSize = sortedLightCounts;
            //public int sortedLightCounts => m_ProcessVisibleLightCounts[(int)ProcessLightsCountSlots.ProcessedLights];
            //排序燈光陣列長度,會根據實際處理了多少燈光的實體來排序陣列(如果不出意外應該是m_SortKeys陣列長度等於sortSize)
            if (sortSize <= 32)
                CoreUnsafeUtils.InsertionSort(m_SortKeys, sortSize);
            else if (m_Size <= 200)
                CoreUnsafeUtils.MergeSort(m_SortKeys, sortSize, ref m_SortSupportArray);
            else
                CoreUnsafeUtils.RadixSort(m_SortKeys, sortSize, ref m_SortSupportArray);
        }
    }

    private static unsafe void InsertionSort(uint* arr, int length)
    {
        for (int i = 0; i < length; ++i)
        {
            for (int j = i; j >= 1; --j)
            {
                if (arr[j] >= arr[j - 1])
                    break;

                var tmp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = tmp;
            }
        }
    }
    private static unsafe void MergeSort(uint* array, uint* support, int length)
    {
        for (int k = 1; k < length; k *= 2)
        {
            for (int left = 0; left + k < length; left += k * 2)
            {
                int right = left + k;
                int rightend = right + k;
                if (rightend > length)
                    rightend = length;
                int m = left;
                int i = left;
                int j = right;
                while (i < right && j < rightend)
                {
                    if (array[i] <= array[j])
                    {
                        support[m] = array[i++];
                    }
                    else
                    {
                        support[m] = array[j++];
                    }
                    m++;
                }
                while (i < right)
                {
                    support[m] = array[i++];
                    m++;
                }
                while (j < rightend)
                {
                    support[m] = array[j++];
                    m++;
                }
                for (m = left; m < rightend; m++)
                {
                    array[m] = support[m];
                }
            }
        }
    }
    private static unsafe void RadixSort(uint* array, uint* support, int radixBits, int bitStates, int length)
    {
        uint mask = (uint)(bitStates - 1);
        CalculateRadixSortSupportArrays(bitStates, length, support, out uint* bucketIndices, out uint* bucketSizes, out uint* bucketPrefix, out uint* arrayOutput);

        int buckets = (sizeof(uint) * 8) / radixBits;
        uint* targetBuffer = arrayOutput;
        uint* inputBuffer = array;
        for (int b = 0; b < buckets; ++b)
        {
            int shift = b * radixBits;
            for (int s = 0; s < 3 * bitStates; ++s)
                bucketIndices[s] = 0;//bucketSizes and bucketPrefix get zeroed, since we walk 3x the bit states

            for (int i = 0; i < length; ++i)
                bucketSizes[((inputBuffer[i] >> shift) & mask)]++;

            for (int s = 1; s < bitStates; ++s)
                bucketPrefix[s] = bucketPrefix[s - 1] + bucketSizes[s - 1];

            for (int i = 0; i < length; ++i)
            {
                uint val = inputBuffer[i];
                uint bucket = (val >> shift) & mask;
                targetBuffer[bucketPrefix[bucket] + bucketIndices[bucket]++] = val;
            }

            uint* tmp = inputBuffer;
            inputBuffer = targetBuffer;
            targetBuffer = tmp;
        }
    }

這裡是三種排序分別是歸併排序,插入排序,桶排序,這裡就不再展開說了,可以看到在插入排序裡面\(arr[j] >= arr[j - 1]\)
也就是說只要位於前面的那個陣列元素\(a[j-1]\)小於後面的那個元素\(a[j]\)就不用進行後面的互換位置的操作。
所以得出來的陣列確實如之前的英文註釋說的是從小到大的,所以上面的m_SortKeys陣列排完序之後,平行光的光源會排在前面。
注:(uint isDirectionalMSB = m_GPULightType == GPULightType.Directional ? 0u : 1u;)
而其他標誌位如:LightCategory,GPULightType,LightVolumeType也會按照大小進行排序
所以上面這裡可以看到我們可以通過對儲存在uint中,可以作重要性排序。

那麼我們應該怎麼去閱讀這些位操作的程式碼呢?
我做了幾個測試:

uint isDirectionalMSB = m_GPULightType == GPULightType.Directional ? 0u : 1u;//確保平行光在陣列的最開端0<<31=0
uint sortKey = (uint)isDirectionalMSB << 31 | (uint)m_LightCategory << 27 | (uint)m_GPULightType << 22 | (uint)m_LightVolumeType << 17 | (uint)lightIndex;
Debug.Log("isDirectionalMSB:"+Convert.ToString((uint)isDirectionalMSB << 31,2));1
Debug.Log("m_LightCategory:"+Convert.ToString((uint)m_LightCategory << 27,2));4
Debug.Log("m_GPULightType:"+Convert.ToString((uint)m_GPULightType << 22,2));5
Debug.Log("m_LightVolumeType:"+Convert.ToString((uint)m_LightVolumeType << 17,2));5
Debug.Log(Convert.ToString(sortKey,2));

normal:
0000 0000 0000 0000 0000 0000 0000 0000

isDirectionalMSB:0
m_LightCategory:5 =Count 101
m_GPULightType:0 =directional
m_LightVolumeType:0 =Cone
index:1
0010 1000 0000 0000 0000 0000 0000 0000
0|010 1|000 0000 0000 0000 0000 0000 0001

isDirectionalMSB:0
m_LightCategory:5 =Count 101
m_GPULightType: [Num]1/7 = [Enum]Point/Disc = [Bit]1/111
m_LightVolumeType:0 =Cone
1|010 1|000 01|00 0000 0000 0000 0000 0001
1|010 1|001 11|00 0000 0000 0000 0000 0001

isDirectionalMSB:0
m_LightCategory:5 =Count 101
m_GPULightType:7 = directional
m_LightVolumeType:[Num]0/4 = [Enum]Cone/Count = [Bit]0/11
1|010 1|001 11|00 000|0 0000 0000 0000 0001
1|010 1|001 11|00 011|0 0000 0000 0000 0001

可以看到32位就是用來標識是不是平行光,所以左移了31位,而後面的GPULightType左移了27位,說明用了4位(27[左移的位數]+4+1=32,31-27=4)
使用如此類推,如果要關注一個uint的Flag的分佈就只要看左移間隔了多少位。

作用二:Flag Mask(主要用來作為標識位作判斷)

另外一種作用體比較明顯的是在HDRP的Shader裡面針對不同的MaterialFeature進行取樣,填充引數,當然上面的SortKey最前面的那一位也是可以作為標識位來判斷是不是平行光。

在說這個MaterialFeature之前先說一下HDRP管線架構的一個特性,就是對於不同光照模型(不同材質,Lit/Hair/Fabic,Eye)它們的取樣要用到的函式都會不一樣,為了做統一做抽象,共用同一套LightLoop函式,同一套Frag(前向渲染的ShaderPassForward,DepthOnly,DepthNormal....)都統一做出了規定,不同材質必需定義各自的SurfaceData,BSDFData,PreLightData...需要實現各自的ConvertSurfaceDataToBSDFData/GetPreLightData...
當然如果你要自己寫Shader也是可以不用按照他的框架走的,只是說HDRP的這個框架已經把能公用的部分基本都抽離出來了,就不用每個Shader單獨寫LightLoop了。除非是NPR,需要高度自定義Lighting修改。

而這個MaterialFeature就位於SurfaceData
舉個例子:Lit.cs.hlsl

//
// This file was automatically generated. Please don't edit by hand. Execute Editor command [ Edit > Rendering > Generate Shader Includes ] instead
//

#ifndef LIT_CS_HLSL
#define LIT_CS_HLSL
//
// UnityEngine.Rendering.HighDefinition.Lit+MaterialFeatureFlags:  static fields
//
#define MATERIALFEATUREFLAGS_LIT_STANDARD (1)
#define MATERIALFEATUREFLAGS_LIT_SPECULAR_COLOR (2)
#define MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING (4)
#define MATERIALFEATUREFLAGS_LIT_TRANSMISSION (8)
#define MATERIALFEATUREFLAGS_LIT_ANISOTROPY (16)
#define MATERIALFEATUREFLAGS_LIT_IRIDESCENCE (32)
#define MATERIALFEATUREFLAGS_LIT_CLEAR_COAT (64)

.....
struct SurfaceData
{
    uint materialFeatures;
    real3 baseColor;
    ...
};

struct BSDFData
{
    uint materialFeatures;
    real3 diffuseColor;
    ....
};

可以看到這裡前面有一段註釋,大概的意思是說這個hlsl檔案是由指令碼生成的,不要手動修改,而這個hlsl檔案是由Lit.cs生成的。

    partial class Lit : RenderPipelineMaterial
    {
        // Currently we have only one materialId (Standard GGX), so it is not store in the GBuffer and we don't test for it

        // If change, be sure it match what is done in Lit.hlsl: MaterialFeatureFlagsFromGBuffer
        // Material bit mask must match the size define LightDefinitions.s_MaterialFeatureMaskFlags value
        [GenerateHLSL(PackingRules.Exact)]
        public enum MaterialFeatureFlags
        {
            LitStandard = 1 << 0,   // For material classification we need to identify that we are indeed use as standard material, else we are consider as sky/background element
            LitSpecularColor = 1 << 1,   // LitSpecularColor is not use statically but only dynamically
            LitSubsurfaceScattering = 1 << 2,
            LitTransmission = 1 << 3,
            LitAnisotropy = 1 << 4,
            LitIridescence = 1 << 5,
            LitClearCoat = 1 << 6
        };

        //-----------------------------------------------------------------------------
        // SurfaceData
        //-----------------------------------------------------------------------------

        // Main structure that store the user data (i.e user input of master node in material graph)
        [GenerateHLSL(PackingRules.Exact, false, false, true, 1000)]
        public struct SurfaceData
        {
            [SurfaceDataAttributes("Material Features")]
            public uint materialFeatures;

            // Standard
            [MaterialSharedPropertyMapping(MaterialSharedProperty.Albedo)]
            [SurfaceDataAttributes("Base Color", false, true, FieldPrecision.Real)]
            public Vector3 baseColor;
            ....
        };
        
        [GenerateHLSL(PackingRules.Exact, false, false, true, 1050)]
        public struct BSDFData
        {
            public uint materialFeatures;

            [SurfaceDataAttributes("", false, true, FieldPrecision.Real)]
            public Vector3 diffuseColor;
            ...
        };

可以看到這兩段程式碼結構體基本差不多。MaterialFeatureFlags生成了 MATERIALFEATUREFLAGS_LIT_XXXXX的MeterialFeatureFlag巨集定義,將Standarad定義成1,SpecularColor定義成2,SSS定義成4...跟上面的Enum定義一致。然後再Shader程式碼裡面是怎麼判斷當前的光照模型執行對應的取樣函式呢?
答案就是對materialFeatures作位運算

在Lit.shader我們能夠看到有以下Shader Feature的巨集

    // MaterialFeature are used as shader feature to allow compiler to optimize properly
    #pragma shader_feature_local_fragment _MATERIAL_FEATURE_SUBSURFACE_SCATTERING
    #pragma shader_feature_local_fragment _MATERIAL_FEATURE_TRANSMISSION
    #pragma shader_feature_local_fragment _MATERIAL_FEATURE_ANISOTROPY
    #pragma shader_feature_local_fragment _MATERIAL_FEATURE_CLEAR_COAT
    #pragma shader_feature_local_fragment _MATERIAL_FEATURE_IRIDESCENCE
    #pragma shader_feature_local_fragment _MATERIAL_FEATURE_SPECULAR_COLOR
    #pragma shader_feature_local_raytracing _MATERIAL_FEATURE_SUBSURFACE_SCATTERING
    #pragma shader_feature_local_raytracing _MATERIAL_FEATURE_TRANSMISSION
    #pragma shader_feature_local_raytracing _MATERIAL_FEATURE_ANISOTROPY
    #pragma shader_feature_local_raytracing _MATERIAL_FEATURE_CLEAR_COAT
    #pragma shader_feature_local_raytracing _MATERIAL_FEATURE_IRIDESCENCE
    #pragma shader_feature_local_raytracing _MATERIAL_FEATURE_SPECULAR_COLOR

在LitData.hlsl的GetSurfaceAndBuiltinData中的GetSurfaceData(在LitDataIndividualLayer.hlsl的ADD_IDX(GetSurfaceData))
(說個題外話,HDRP的多層材質[LayeredLitData]是用這個ADD_IDX巨集來與普通的Lit共用一份的GetSurfaceData函式定義。實際在使用的時候是呼叫GetSurfaceData0/1/2/3)

LitDataIndividualLayer.hlsl
// This part of the code is not used in case of layered shader but we keep the same macro system for simplicity
#if !defined(LAYERED_LIT_SHADER)

// These static material feature allow compile time optimization
surfaceData.materialFeatures = MATERIALFEATUREFLAGS_LIT_STANDARD;

#ifdef _MATERIAL_FEATURE_SUBSURFACE_SCATTERING
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING;
#endif
#ifdef _MATERIAL_FEATURE_TRANSMISSION
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_LIT_TRANSMISSION;
#endif
#ifdef _MATERIAL_FEATURE_ANISOTROPY
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_LIT_ANISOTROPY;
#endif
#ifdef _MATERIAL_FEATURE_CLEAR_COAT
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_LIT_CLEAR_COAT;
#endif
#ifdef _MATERIAL_FEATURE_IRIDESCENCE
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_LIT_IRIDESCENCE;
#endif
#ifdef _MATERIAL_FEATURE_SPECULAR_COLOR
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_LIT_SPECULAR_COLOR;
#endif
==============================

(LayeredLitData.hlsl)
// Layered shader support SSS and Transmission features
surfaceData.materialFeatures = MATERIALFEATUREFLAGS_LIT_STANDARD;

#ifdef _MATERIAL_FEATURE_SUBSURFACE_SCATTERING
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING;
#endif
#ifdef _MATERIAL_FEATURE_TRANSMISSION
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_LIT_TRANSMISSION;
#endif

可以看到上面的materialFeature賦值是普通Lit的Shader用的#if !defined(LAYERED_LIT_SHADER)
下面的則是分層材質使用的,不過只支援SSS和透射材質
通過這裡的與運算,把對應的位設定成1,然後ConvertSurfaceDataToBSDFData/GetPreLightData中只需要判斷HasFlag(surfaceData.materialFeatures, MATERIALFEATUREFLAGS_LIT_XXXXX)執行對應的函式。

(SRP.Core-Common.hlsl)
// Simple function to test a bitfield
bool HasFlag(uint bitfield, uint flag)
{
    return (bitfield & flag) != 0;
}

如果光照模型跟標準的LightLoop要有自定義的修改就需要自己定義類似操作,用巨集控制,讓LightLoop插入自定義修改函式

(Lit.hlsl)
#define MODIFY_BAKED_DIFFUSE_LIGHTING

// This function allow to modify the content of (back) baked diffuse lighting when we gather builtinData
// This is use to apply lighting model specific code, like pre-integration, transmission etc...
// It is up to the lighting model implementer to chose if the modification are apply here or in PostEvaluateBSDF
void ModifyBakedDiffuseLighting(float3 V, PositionInputs posInput, PreLightData preLightData, BSDFData bsdfData, inout BuiltinData builtinData)
{
    // In case of deferred, all lighting model operation are done before storage in GBuffer, as we store emissive with bakeDiffuseLighting

    // Add GI transmission contribution to bakeDiffuseLighting, we then drop backBakeDiffuseLighting (i.e it is not used anymore, this save VGPR in forward and in deferred we can't store it anyway)
    if (HasFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_LIT_TRANSMISSION))
    {
        builtinData.bakeDiffuseLighting += builtinData.backBakeDiffuseLighting * bsdfData.transmittance;
    }

    // For SSS we need to take into account the state of diffuseColor
    if (HasFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING))
    {
        bsdfData.diffuseColor = GetModifiedDiffuseColorForSSS(bsdfData);
    }

    // Premultiply (back) bake diffuse lighting information with DisneyDiffuse pre-integration
    // Note: When baking reflection probes, we approximate the diffuse with the fresnel0
    builtinData.bakeDiffuseLighting *= preLightData.diffuseFGD * GetDiffuseOrDefaultColor(bsdfData, _ReplaceDiffuseForIndirect).rgb;
}


(LightLoop.hlsl)
#ifdef MODIFY_BAKED_DIFFUSE_LIGHTING
#ifdef DEBUG_DISPLAY
            // When the lux meter is enabled, we don't want the albedo of the material to modify the diffuse baked lighting
            if (_DebugLightingMode != DEBUGLIGHTINGMODE_LUX_METER)
#endif
                ModifyBakedDiffuseLighting(V, posInput, preLightData, bsdfData, tempBuiltinData);
#endif

同樣的在HDRP裡面Lit.hlsl有一個靜態常量uint陣列kFeatureVariantFlags,長度定義為NUM_FEATURE_VARIANTS,而NUM_FEATURE_VARIANTS這個巨集的定義也同樣是用指令碼生成的。

(LightLoop.cs)
[GenerateHLSL]
internal enum LightFeatureFlags
{
    // Light bit mask must match LightDefinitions.s_LightFeatureMaskFlags value
    Punctual = 1 << 12,
    Area = 1 << 13,
    Directional = 1 << 14,
    Env = 1 << 15,
    Sky = 1 << 16,
    SSRefraction = 1 << 17,
    SSReflection = 1 << 18,
    // If adding more light be sure to not overflow LightDefinitions.s_LightFeatureMaskFlags
}

[GenerateHLSL]
class LightDefinitions
{
    public static int s_MaxNrBigTileLightsPlusOne = 512;      // may be overkill but the footprint is 2 bits per pixel using uint16.
    public static float s_ViewportScaleZ = 1.0f;
    public static int s_UseLeftHandCameraSpace = 1;

    public static int s_TileSizeFptl = 16;
    public static int s_TileSizeClustered = 32;
    public static int s_TileSizeBigTile = 64;

    // Tile indexing constants for indirect dispatch deferred pass : [2 bits for eye index | 15 bits for tileX | 15 bits for tileY]
    public static int s_TileIndexMask = 0x7FFF;
    public static int s_TileIndexShiftX = 0;
    public static int s_TileIndexShiftY = 15;
    public static int s_TileIndexShiftEye = 30;

    // feature variants
    public static int s_NumFeatureVariants = 29;

    // light list limits
    public static int s_LightListMaxCoarseEntries = 64;
    public static int s_LightListMaxPrunedEntries = 24;
    public static int s_LightClusterMaxCoarseEntries = 128;

    // Following define the maximum number of bits use in each feature category.
    public static uint s_LightFeatureMaskFlags = 0xFFF000;
    public static uint s_LightFeatureMaskFlagsOpaque = 0xFFF000 & ~((uint)LightFeatureFlags.SSRefraction); // Opaque don't support screen space refraction
    public static uint s_LightFeatureMaskFlagsTransparent = 0xFFF000 & ~((uint)LightFeatureFlags.SSReflection); // Transparent don't support screen space reflection
    public static uint s_MaterialFeatureMaskFlags = 0x000FFF;   // don't use all bits just to be safe from signed and/or float conversions :/

    // Screen space shadow flags
    public static uint s_RayTracedScreenSpaceShadowFlag = 0x1000;
    public static uint s_ScreenSpaceColorShadowFlag = 0x100;
    public static uint s_InvalidScreenSpaceShadow = 0xff;
    public static uint s_ScreenSpaceShadowIndexMask = 0xff;
}

對應生成的hlsl檔案

#define LIGHTFEATUREFLAGS_PUNCTUAL (4096)
#define LIGHTFEATUREFLAGS_AREA (8192)
#define LIGHTFEATUREFLAGS_DIRECTIONAL (16384)
#define LIGHTFEATUREFLAGS_ENV (32768)
#define LIGHTFEATUREFLAGS_SKY (65536)
#define LIGHTFEATUREFLAGS_SSREFRACTION (131072)
#define LIGHTFEATUREFLAGS_SSREFLECTION (262144)

....
#define NUM_FEATURE_VARIANTS (29)
....

說到這個陣列是用來幹什麼用的,先看看這個陣列長啥樣

// Combination need to be define in increasing "comlexity" order as define by FeatureFlagsToTileVariant
static const uint kFeatureVariantFlags[NUM_FEATURE_VARIANTS] =
{
    // Precomputed illumination (no dynamic lights) with standard
    /*  0 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_STANDARD,
    // Precomputed illumination (no dynamic lights) with standard, SSS and transmission
    /*  1 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION | MATERIALFEATUREFLAGS_LIT_STANDARD,
    // Precomputed illumination (no dynamic lights) for all material types
    /*  2 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIAL_FEATURE_MASK_FLAGS,

    /*  3 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /*  4 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /*  5 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /*  6 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /*  7 */ LIGHT_FEATURE_MASK_FLAGS_OPAQUE | MATERIALFEATUREFLAGS_LIT_STANDARD,

    // Standard with SSS and Transmission
    /*  8 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /*  9 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 10 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 11 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 12 */ LIGHT_FEATURE_MASK_FLAGS_OPAQUE | MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION | MATERIALFEATUREFLAGS_LIT_STANDARD,

    // Anisotropy
    /* 13 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_ANISOTROPY | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 14 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_ANISOTROPY | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 15 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_ANISOTROPY | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 16 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_ANISOTROPY | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 17 */ LIGHT_FEATURE_MASK_FLAGS_OPAQUE | MATERIALFEATUREFLAGS_LIT_ANISOTROPY | MATERIALFEATUREFLAGS_LIT_STANDARD,

    // Standard with clear coat
    /* 18 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 19 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 20 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 21 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 22 */ LIGHT_FEATURE_MASK_FLAGS_OPAQUE | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT | MATERIALFEATUREFLAGS_LIT_STANDARD,

    // Standard with Iridescence
    /* 23 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_IRIDESCENCE | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 24 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_IRIDESCENCE | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 25 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_IRIDESCENCE | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 26 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SSREFLECTION | MATERIALFEATUREFLAGS_LIT_IRIDESCENCE | MATERIALFEATUREFLAGS_LIT_STANDARD,
    /* 27 */ LIGHT_FEATURE_MASK_FLAGS_OPAQUE | MATERIALFEATUREFLAGS_LIT_IRIDESCENCE | MATERIALFEATUREFLAGS_LIT_STANDARD,

    /* 28 */ LIGHT_FEATURE_MASK_FLAGS_OPAQUE | MATERIAL_FEATURE_MASK_FLAGS, // Catch all case with MATERIAL_FEATURE_MASK_FLAGS is needed in case we disable material classification
};

uint FeatureFlagsToTileVariant(uint featureFlags)
{
    for (int i = 0; i < NUM_FEATURE_VARIANTS; i++)
    {
        if ((featureFlags & kFeatureVariantFlags[i]) == featureFlags)
            return i;
    }
    return NUM_FEATURE_VARIANTS - 1;
}

#ifdef USE_INDIRECT

uint TileVariantToFeatureFlags(uint variant, uint tileIndex)
{
    if (variant == NUM_FEATURE_VARIANTS - 1)
    {
        // We don't have any compile-time feature information.
        // Therefore, we load the feature classification data at runtime to avoid
        // entering every single branch based on feature flags.
        return g_TileFeatureFlags[tileIndex];
    }
    else
    {
        // Return the compile-time feature flags.
        return kFeatureVariantFlags[variant];
    }
}
#endif // USE_INDIRECT

如果你接著搜尋FeatureFlagsToTileVariant和TileVariantToFeatureFlags其實是能夠看出來跟HDRP的DefferLighting有關係的。
未完待續