從Built-in到URP

從Built-in到URP

HLSL語法

變量

  • bool – true or false.
  • float – 32位浮點數(shù)。通常用于世界空間位置,紋理坐標或涉及復雜函數(shù)(例如三角函數(shù)或冪/冪)的標量計算。
  • half – 16位浮點數(shù)。通常用于短向量,方向,對象空間位置,顏色。
  • double – 64位浮點數(shù)。不能用作輸入/輸出
  • fixed – 僅在內(nèi)置著色器中使用,在URP中不支持,請改用half 。
  • real – 僅用于URP嗎?我認為這只是half的默認值(假設(shè)平臺上支持它們),除非著色器指定“ #define PREFER_HALF 0”,否則它將使用浮點精度。
  • int – 32位有符號整數(shù)
  • uint – 32位無符號整數(shù)(GLES2除外,不支持此整數(shù),而是將其定義為int)。

向量

  • float4 –包含4個浮點的向量
  • half3
  • int2
  • ……

矩陣

  • float4x4 – 4行,4列
  • int4x3 – 4行,3列
  • half2x1 – 2行,1列
  • float1x4 – 1行,4列
float3x3 matrix = {0,1,2,
                   3,4,5,
                   6,7,8};
float3 row0 = matrix[0]; // (0, 1, 2)
float3 row1 = matrix[1]; // (3, 4, 5)
float3 row2 = matrix[2]; // (6, 7, 8)
float row1column2 = matrix[1][2]; // 5
// 注意我們也可以這樣做
float row1column2 = matrix[1].z;

矩陣通常用于不同坐標空間之間的轉(zhuǎn)換。為此,我們需要進行矩陣乘法,可以使用mul函數(shù)來完成(而不是*運算符,該運算符不適用于矩陣和向量類型)

數(shù)組

可以在著色器中指定數(shù)組,盡管Shaderlab屬性或材質(zhì)檢查器不支持它們,并且必須從C#腳本中進行設(shè)置。必須在著色器中指定數(shù)組的大小,并且數(shù)組大小應(yīng)保持恒定以防止出現(xiàn)問題。如果我們不知道數(shù)組的大小,則需要設(shè)置最大值并以0s傳入數(shù)組填充。我們可以指定另一個float來作為需要遍歷數(shù)組的長度,例如此處的示例。

float _Array[10]; // Float array
float4 _Array[10]; // Vector array
float4x4 _Array[10]; // Matrix array

設(shè)置浮點數(shù)組時,請使用material.SetFloatArrayShader.SetGlobalFloatArray。還有SetVectorArraySetMatrixArray及其全局版本。

其他種類

HLSL還包括其他類型,例如“紋理”和“采樣器”,可以使用URP中的以下宏進行定義:

TEXTURE2D(textureName);
SAMPLER(sampler_textureName);

還有緩沖區(qū),盡管我從未真正使用過它們,所以對它們的用法并不熟悉。它們是使用material.SetBufferShader.SetGlobalBuffer從C#設(shè)置的。

#ifdef SHADER_API_D3D11
StructuredBuffer<float3> buffer;
#endif
// I think this is only supported in Direct3D 11?
// and also require #pragma target 4.5 or higher?
// see https://docs.unity3d.com/Manual/SL-ShaderCompileTargets.html

你可能還希望研究HLSL的其他部分,例如流控制 (if,for,while等),但是如果我們熟悉語法,則其語法基本上與C#相同。我們還可以在此處找到HLSL支持的所有運算符的列表。

函數(shù)

HLSL中的函數(shù)聲明與C#非常相似。這是一個例子:

float3 example(float3 a, float3 b){
    return a * b;
}

其中float3是返回類型,示例是函數(shù)名稱,括號內(nèi)是傳遞給函數(shù)的參數(shù)。在沒有返回類型的情況下,將使用void。您還可以在參數(shù)類型之前使用“ out”來指定輸出參數(shù),如果希望它成為可編輯并回傳的輸入,則可以使用“ inout”來指定輸出參數(shù)。

宏在編譯著色器之前進行處理,并且在使用宏時將替換為帶有替換參數(shù)的定義。例如

#define EXAMPLE(x, y) ((x) * (y))
float f = EXAMPLE(3, 5);
float3 a = float3(1,1,1);
float3 f2 = EXAMPLE(a, float3(0,1,0));
 
// becomes :
float f = ((3) * (5));
float a = float(1,1,1);
float3 f2 = ((a) * (float3(0,1,0)));
// then the shader is compiled.
 
// Note that the macro has () around x and y.
// This is because we could do :
float b = EXAMPLE(1+2, 3+4);
// becomes :
float b = ((1+2) * (3+4)); // 3 * 7, so 21
// If those () wasn't included, it would instead be :
float b = (1+2*3+4)
// which equals 11 due to * taking precedence over +

他們還可以做一些功能無法做到的事情。例如 :

#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
 
// Usage :
OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex)
 
// becomes :
OUT.uv = (IN.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw);

“##”運算符是一種特殊情況,其中宏可能很有用。它使我們可以將名稱和_ST部分連接起來,從而為此用法輸入生成_MainTex_ST。如果省略##部分,它將僅生成“name_ST”,從而導致錯誤,因為尚未定義。(當然,仍然需要定義_MainTex_ST,但這是預期的行為,因為在紋理名稱后附加_ST是Unity處理紋理的平鋪和偏移值的方式)。

Tags

URP LIGHTMODE TAGS :

  • UniversalForward – 用于前向渲染
  • ShadowCaster – 用于投射陰影
  • DepthOnly – 似乎在為場景視圖渲染深度紋理時使用,而不是在運行中使用嗎?不過,某些渲染器功能可能會使用它。
  • Meta – 僅在光照貼圖烘焙期間使用
  • Universal2D – 在啟用 2D 渲染器時使用,而不是前向渲染器。
  • UniversalGBuffer – 與延遲渲染有關(guān)。我認為這是測試功能。
Tags { "LightMode" = "UniversalForward" }

可以在子著色器中定義多個Pass塊,但是每個都應(yīng)該用一個特定的LightMode標記(見下面)。URP使用了單通道前向渲染器,所以只有第一個“通用前向”通道(GPU支持的)將用于渲染對象——你不能同時渲染多個對象。雖然我們可以讓其他傳遞沒有標記,但要注意它們將中斷SRP批處理程序的批處理。相反,我們建議使用單獨的著色器/材質(zhì),無論是在單獨的MeshRenderers上,還是使用Forward Renderer上的Render Objects特性,用一個overrideMaterial在一個特定的圖層上重新渲染對象。

屬性

在Shaderlab示例中,我們有一個HLSLINCLUDE,它會自動將代碼包含在Subshader內(nèi)部的每個Pass中。

我們可以使用UnityPerMaterial CBUFFER來確保著色器兼容SRP批處理。這個CBUFFER需要包括所有公開的屬性(與Shaderlab屬性塊中的相同)。但它不能包括其他未公開的變量,紋理也不需要被包括。

HLSLINCLUDE
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
 
    CBUFFER_START(UnityPerMaterial)
    float4 _BaseMap_ST;
    float4 _BaseMap_TexelSize;
    float4 _BaseColor;
    //float4 _ExampleDir;
    //float _ExampleFloat;
    CBUFFER_END
ENDHLSL

需要注意的是_BaseMap_ST_BaseMap_TexelSize是兩個東西,前者是紋理的縮放與偏移,而后者代表紋理的大小。

結(jié)構(gòu)體

在定義頂點或片段著色器功能之前,我們需要定義一些用于將數(shù)據(jù)傳入和傳出的結(jié)構(gòu)。在內(nèi)置函數(shù)中,它們通常被命名為“appdata”和“v2f”(頂點到片段的縮寫),而URP著色器則傾向于使用“ Attributes”和“ Varyings ”。這些只是名稱,可能不太重要。

struct Attributes {
    float4 positionOS   : POSITION;
    float2 uv           : TEXCOORD0;
    float4 color        : COLOR;
};

該屬性結(jié)構(gòu)將輸入到頂點著色器。它允許我們使用大寫字母中被稱為語義的部分從網(wǎng)格中獲取每個頂點的數(shù)據(jù)。其中包括:頂點位置(POSITION),頂點顏色(COLOR)和UV(又稱為紋理坐標)。網(wǎng)格具有8個不同的UV通道,可以通過TEXCOORD0TEXCOORD7進行訪問。

我們還可以通過NORMAL訪問頂點法線,并通過TANGENT訪問切線。

在這些結(jié)構(gòu)之后,您通常還會看到已定義了紋理采樣器(雖然紋理位于著色器屬性中,但尚未在hlsl中定義。其他屬性包括在CBUFFER中)。在URP中,我們使用以下內(nèi)容:

TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);

頂點著色器

我們的頂點著色器需要做的主要事情是將網(wǎng)格從對象空間位置轉(zhuǎn)換為剪輯空間位置。為了在目標屏幕位置正確渲染片元/像素。

在內(nèi)置著色器中,您可以使用UnityObjectToClipPos函數(shù)執(zhí)行此操作,但是URP已將其重命名為TransformObjectToHClip(可以在函數(shù)庫SpaceTransforms.hlsl中找到)。也就是說,還有另一種方法來處理URP中的轉(zhuǎn)換,如下所示。

Varyings vert(Attributes IN) {
    Varyings OUT;
 
    VertexPositionInputs positionInputs = GetVertexPositionInputs(IN.positionOS.xyz);
    OUT.positionCS = positionInputs.positionCS;
    // Or this :
    //OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
 
    OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
    OUT.color = IN.color;
    return OUT;
}
  1. 我們從Attributes中輸入對象空間的位置,并獲得一個VertexPositionInputs結(jié)構(gòu),其中包含:

    • positionWS,在世界空間中的位置
    • positionVS,視圖空間中的位置
    • positionCS,裁剪空間中的位置
    • positionNDC,標準化設(shè)備坐標中的位置
  2. 頂點著色器還負責將數(shù)據(jù)傳遞到片段。對于頂點顏色,這只是一個簡單的OUT.color = IN.color;。

  3. 如果我們希望能夠?qū)y理進行采樣,則還需要傳遞模型的UV(紋理坐標)。雖然我們可以做OUT.uv = IN.uv;(假設(shè)兩者均為float2),通常會使用TRANSFORM_TEX宏,該宏采用uv和texture屬性名稱,并應(yīng)用材質(zhì)檢查器的偏移和平鋪進行矯正(存儲在“ _BaseMap” +“ _ ST”中,S用于比例尺和T))。此宏位于內(nèi)置和URP中(在core / ShaderLibrary / Macros.hlsl內(nèi)部,應(yīng)自動包含在Core.hlsl中)。

    實際上,這只是IN.uv.xy * _BaseMap_ST.xy + _BaseMap_ST.z的簡寫,因此您也可以這樣寫(將_BaseMap換成預期的紋理屬性。(texture)_STfloat4變量還必須添加到UnityPerMaterial CBUFFER(已在屬性部分中討論過)。

VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);

GetVertexNormalInputs可用于將對象空間的法線和切線轉(zhuǎn)換為世界空間。它包含:

  • normalWS,在世界空間中的法線向量
  • tangentWS,在世界空間中的切線向量
  • bitangentWS,在世界空間中的副切線向量

還有一個僅將法線作為輸入的版本,將tangentWS保留為(1,0,0),bitangentWS保留為(0,1,0),或者您也可以改用TransformObjectToWorldNormal(IN.normalOS)。

片元著色器

half4 frag(Varyings IN) : SV_Target {
    half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
 
    return baseMap * _BaseColor * IN.color;
}

這將生成一個著色器,該著色器基于_BaseMap紋理輸出一個Half4顏色,該著色器還由_BaseColor和頂點顏色(IN.color)進行著色。

SV_Target部分是與half4輸出一起使用的語義,它告訴著色器它是顏色輸出。

還有一個SV_Depth輸出,它是一個浮點數(shù),用于覆蓋每個像素的Z緩沖區(qū)值。(可以將它們放入一個結(jié)構(gòu)中以同時輸出SV_TargetSV_Depth)。在大多數(shù)情況下,不需要覆蓋它,對于許多GPU,它都會關(guān)閉某些基于深度緩沖區(qū)的優(yōu)化,因此除非您知道自己在做什么和需要做什么,否則不要覆蓋它。

我們的片段著色器使用URP ShaderLibrary提供的SAMPLE_TEXTURE2D宏對_BaseMap紋理進行采樣,該宏將紋理,采樣器和UV作為輸入。

我們可能還想做的是,如果像素的alpha值低于某個閾值,則將其丟棄,以使整個網(wǎng)格都不可見。

例如,對于四邊形上的草/葉紋理。既可以在不透明著色器中也可以在透明著色器中完成此操作,通常將其稱為Alpha裁剪。如果您熟悉shadergraph,可以使用主節(jié)點上的“Alpha Clip Threshold”輸入來處理它。

解決此問題的常用方法是提供_Cutoff屬性以控制閾值,然后執(zhí)行以下操作。(此屬性必須添加到我們的Shaderlab屬性以及UnityPerMaterial CBUFFER中以實現(xiàn)SRP Batcher兼容性)。

if (_BaseMap.a < _Cutoff){
    discard;
}
// OR
clip(_BaseMap.a - _Cutoff);
// inside the fragment function, before returning

環(huán)境光在URP下用_GlossyEnvironmentColor獲取,但得到的效果可能與Builit-in下的結(jié)果相差較大,這時候可以考慮用球諧函數(shù)獲取

//URP使用的環(huán)境光
half3 ambient = _GlossyEnvironmentColor
//使用球諧函數(shù)獲取
half3 ambient = SampleSH(worldNormal);

//--Builit-in
half3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

關(guān)鍵字和著色器變體

著色器變體

在著色器中,我們可以指定更多的#pragma指令,其中一些指令包括multi_compileshader_feature。這些可用于指定用于將“著色器”代碼的某些部分“打開”或“關(guān)閉”的關(guān)鍵字。著色器實際上被編譯為多個版本的著色器,稱為著色器變體。

MULTI_COMPILE

#pragma multi_compile _A _B _C (...etc)

在此示例中,我們將生成著色器的三個變體,其中_A,_B和_C是關(guān)鍵字。

在著色器代碼中,我們可以使用以下內(nèi)容:

#ifdef _A
// 如果A啟用,編譯此代碼
#endif
 
#ifndef _B
// 當B被禁用時編譯此代碼,也就是只在A和C中。
// 注意#ifndef中額外的“n”表示“如果沒有定義”
#else
// 如果B啟用,編譯此代碼
#endif
 
#if defined(_A) || defined(_C)
// 用A或c (aka與上面的相同,假設(shè)沒有其他關(guān)鍵字)編譯此代碼
// 如果需要多個條件,則必須使用長形式的"#if defined()"
// 其中|| = or, && = and
// 注意,因為關(guān)鍵字是在一個multi_compile語句中定義的
// 實際上不可能同時啟用兩者,所以&&在這里沒有意義。
#endif
 
// 還有#elif,用于else if語句。

SHADER_FEATURE

#pragma shader_feature _A _B

這與multi_compile完全相同,但是未使用的變體將不包括在最終版本中。因此,在運行時啟用/禁用這些關(guān)鍵字是不好的,因為它所需的著色器可能未包含在構(gòu)建中!如果需要在運行時處理關(guān)鍵字,請改用multi_compile

這些指令還有“頂點”和“片元”版本,可用于僅針對頂點或片段程序編譯著色器變體,從而減少了變體的總數(shù)。例如 :

#pragma multi_compile_vertex _ _A
#pragma multi_compile_fragment _ _B
// also shader_feature_vertex and shader_feature_fragment

在此示例中,_A關(guān)鍵字僅用于頂點程序,_B僅用于片元。不能同時啟用_A和_B的變體。Unity告訴我們,這會產(chǎn)生2個著色器變體,盡管當您查看實際的編譯代碼時,它更像是一個禁用兩個著色器的著色器變體和兩個“half”的變體。

著色器變體的增長

每增加一個multi_compile和shader_feature,它就會為啟用/禁用關(guān)鍵字的每種可能組合生成越來越多的著色器變體。以以下為例:

#pragma multi_compile _A _B _C
#pragma multi_compile _D _E
#pragma shader_feature _F _G

在這里,第一行將生成3個著色器變體。但是第二行需要為已啟用_D或_E的那些變體生成2個著色器變體。

因此,A&D,A&E,B&D,B&E,C&D和C&E?,F(xiàn)在有6個變體。

第三行,是這6個中的每一個的另外2個變體,因此我們現(xiàn)在總共有12個著色器變體。由于該行是shader_feature,因此某些變體可能不會包含在構(gòu)建中。

每個添加了2個關(guān)鍵字的multi_compile都會使產(chǎn)生的變體數(shù)量加倍,因此包含10個變體的著色器將產(chǎn)生1024個著色器變體!它需要編譯最終構(gòu)建中需要包含的每個著色器變體,因此將增加構(gòu)建時間以及構(gòu)建大小。

如何查看著色器的變體個數(shù)

如果要查看一個著色器產(chǎn)生多少個著色器變體,請單擊該著色器,然后在檢查器中有一個“Compile and Show Code”按鈕,旁邊是一個小的下拉箭頭,其中列出了所包含的變體數(shù)。如果單擊“skip unused shader_features”,則可以切換以查看變體的總數(shù)。

關(guān)鍵字

每個項目最多還有256個關(guān)鍵字,因此最好遵循其他著色器的命名約定。

您還會注意到,對于許多multi_compileshader_features而言,第一個關(guān)鍵字通常僅保留為“ _”。實際上,這實際上不會產(chǎn)生關(guān)鍵字,因此會為256個最大值的其他關(guān)鍵字留出更多空間。

#pragma multi_compile _ _KEYWORD
 
#pragma shader_feature _KEYWORD
// 僅是shader_features的簡寫
#pragma shader_feature _ _KEYWORD
 
// 如果您需要知道該關(guān)鍵字是否已禁用
// 然后我們可以這樣做:
#ifndef _KEYWORD
// 或#if!defined(_KEYWORD)
// 或#ifdef _KEYWORD #else
// code
#endif

我們還可以通過使用multi_compile和shader_feature的本地版本來避免耗盡最大的關(guān)鍵字數(shù)。這些生成的關(guān)鍵字對于該著色器來說是本地的,但是每個著色器最多也有64個本地關(guān)鍵字。

#pragma multi_compile_local _ _KEYWORD
#pragma shader_feature_local _KEYWORD
 
// 還有l(wèi)ocal_fragment/vertex !
#pragma multi_compile_local_fragment _ _KEYWORD
#pragma shader_feature_local_vertex _KEYWORD

光照

Universal RP不支持表面著色器,但是ShaderLibrary確實提供了幫助我們處理大量光照計算的功能。這些包含在Lighting.hlsl

Lighting.hlsl中,有一個GetMainLight函數(shù),如果您熟悉著色器圖中的自定義照明,您可能已經(jīng)知道。為了使用此功能,我們首先在HLSLPROGRAM的頂部引用Lighting.hlsl文件,我還將添加一些multi_compile指令,這些指令提供了接收陰影所需的關(guān)鍵字。

#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
 
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

接下來,我們將需要頂點法線來處理陰影/光照,因此我們將它們添加到AttributesVaryings結(jié)構(gòu)中,并更新頂點著色器。在這里,我僅顯示基于上一節(jié)中制作的Unlit著色器添加的代碼。

struct Attributes {
    ...
    float4 normalOS     : NORMAL;
};
 
struct Varyings {
    ...
    float3 normalWS     : NORMAL;
    float3 positionWS   : TEXCOORD2;
};
...
Varyings vert(Attributes IN) {
    Varyings OUT;
    VertexPositionInputs positionInputs = GetVertexPositionInputs(IN.positionOS.xyz);
    ...
    OUT.positionWS = positionInputs.positionWS;
 
    VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS.xyz);
    OUT.normalWS = normalInputs.normalWS;
 
    return OUT;
}

在片元著色器中,我們現(xiàn)在可以采用世界空間法線,并使用世界空間位置來計算陰影坐標。

half4 frag(Varyings IN) : SV_Target {
    half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
    half4 color = baseMap * _BaseColor * IN.color;
 
    float4 shadowCoord = TransformWorldToShadowCoord(IN.positionWS.xyz);
    Light light = GetMainLight(shadowCoord);
 
    half3 diffuse = LightingLambert(light.color, light.direction, IN.normalWS);
 
    return half4(color.rgb * diffuse * light.shadowAttenuation, color.a);
}

雖然我們的著色器將從其他著色器接收陰影,但是請注意,它沒有ShadowCaster傳遞,因此不會將陰影投射到自身或其他對象上。請參見ShadowCaster部分。

如果我們需要陰影,但對象上沒有漫反射陰影,則也可以刪除漫反射陰影計算,而只需使用light.shadowAttenuation。

如果要進一步擴展以包括環(huán)境/烘焙GI和其他光源,請以Lighting.hlsl中的UniversalFragmentBlinnPhong方法為例,或者讓它為您處理照明。它使用InputData結(jié)構(gòu),下一部分討論的PBR示例也將使用該結(jié)構(gòu)。

PBR光照

基于物理的渲染(PBR)是Unity的“Standard”著色器使用的著色/照明模型,以及UPR的“ Lit”著色器和ShaderGraph中的PBR主節(jié)點。

如前一節(jié)所述,內(nèi)置管道中的陰影/照明通常由Surface Shaders處理,其中“Standard”選用是PBR模型。它們使用了一個曲面函數(shù),該函數(shù)輸出了反照率,法線,發(fā)射,平滑度,遮擋,Alpha和Metallic(如果使用“ StandardSpecular”工作流程,則為Specular)。Unity將采用這些并在幕后生成一個頂點和片段著色器,為您處理某些計算,例如PBR陰影/照明和陰影。

Universal RP不支持表面著色器,但是ShaderLibrary確實提供了幫助我們處理大量光照計算的功能。這些包含在Lighting.hlsl中。在本節(jié)中,我們將重點介紹UniversalFragmentPBR

half4 UniversalFragmentPBR(InputData inputData, half3 albedo, half metallic, half3 specular, half smoothness, half occlusion,  half3 emission, half alpha)
 
// 在v10.xx中添加了帶有SurfaceData結(jié)構(gòu)的版本
// 對于之前的版本,需要改用以上版本。
//(但是您仍然可以使用SurfaceData結(jié)構(gòu)來組織/保存數(shù)據(jù))
half4 UniversalFragmentPBR(InputData inputData, SurfaceData surfaceData)
 
// 還有:
half4 UniversalFragmentBlinnPhong(InputData inputData, half3 diffuse, half4 specularGloss, half smoothness, half3 emission, half alpha)
//復制Unity v4之前的“舊”表面著色器,
//并由URP的“ SimpleLit”著色器使用
//使用Lambert(漫反射)和BlinnPhong(鏡面反射)照明模型

首先,我們應(yīng)該添加PBR照明模型使用的一些屬性。我省去了金屬/高光貼圖和遮擋貼圖,主要是因為它們沒有很好的功能來為您處理采樣(除非您從LitInput.hlsl中復制它們,這是URP提供的Lit shader的一部分) ,而不是實際的ShaderLibrary),并且此部分已經(jīng)相當長且足夠復雜。實際上我?guī)缀鯚o法解釋,因為它主要是知道在哪里使用哪個函數(shù)。您以后總是可以使用LitInput作為示例來添加它們。

Properties {
    _BaseMap ("Base Texture", 2D) = "white" {}
    _BaseColor ("Example Colour", Color) = (0, 0.66, 0.73, 1)
    _Smoothness ("Smoothness", Float) = 0.5
 
    [Toggle(_ALPHATEST_ON)] _EnableAlphaTest("Enable Alpha Cutoff", Float) = 0.0
    _Cutoff ("Alpha Cutoff", Float) = 0.5
 
    [Toggle(_NORMALMAP)] _EnableBumpMap("Enable Normal/Bump Map", Float) = 0.0
    _BumpMap ("Normal/Bump Texture", 2D) = "bump" {}
    _BumpScale ("Bump Scale", Float) = 1
 
    [Toggle(_EMISSION)] _EnableEmission("Enable Emission", Float) = 0.0
    _EmissionMap ("Emission Texture", 2D) = "white" {}
    _EmissionColor ("Emission Colour", Color) = (0, 0, 0, 0)
    }
...
// And need to adjust the CBUFFER to include these too
CBUFFER_START(UnityPerMaterial)
    float4 _BaseMap_ST; // Texture tiling & offset inspector values
    float4 _BaseColor;
    float _BumpScale;
    float4 _EmissionColor;
    float _Smoothness;
    float _Cutoff;
CBUFFER_END

我們還需要對Unlit著色器代碼進行大量更改,包括添加一些multi_compileshader_features以及對AttributesVaryings結(jié)構(gòu)進行調(diào)整,因為我們需要來自網(wǎng)格的法線和切線數(shù)據(jù)并將其發(fā)送到片元中以便使用它們用于照明計算。

“屬性”塊中的這些TOGGLE特性使我們能夠從材質(zhì)檢查器啟用/禁用shader_feature關(guān)鍵字。(或者,我們可以為著色器編寫自定義編輯器/檢查器GUI或使用調(diào)試檢查器)。

如果要支持烘焙的光照貼圖,我們還需要在TEXCOORD1通道中傳遞的光照貼圖UV。

我還使用了來自ShaderLibrary的SurfaceInput.hlsl來幫助完成某些事情,它可以幫助SurfaceData結(jié)構(gòu)保存PBR所需的數(shù)據(jù)以及一些用于采樣的反射率,法線和發(fā)射貼圖的函數(shù)(請注意,該結(jié)構(gòu)似乎已經(jīng)移動了到URP v10中的SurfaceData.hlsl,但SurfaceInput.hlsl會自動包含它)

// Material Keywords
#pragma shader_feature _NORMALMAP
#pragma shader_feature _ALPHATEST_ON
#pragma shader_feature _ALPHAPREMULTIPLY_ON
#pragma shader_feature _EMISSION
//#pragma shader_feature _METALLICSPECGLOSSMAP
//#pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//#pragma shader_feature _OCCLUSIONMAP
 
//#pragma shader_feature _SPECULARHIGHLIGHTS_OFF
//#pragma shader_feature _ENVIRONMENTREFLECTIONS_OFF
//#pragma shader_feature _SPECULAR_SETUP
#pragma shader_feature _RECEIVE_SHADOWS_OFF
 
// URP Keywords
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile _ _SHADOWS_SOFT
#pragma multi_compile _ _MIXED_LIGHTING_SUBTRACTIVE
 
// Unity defined keywords
#pragma multi_compile _ DIRLIGHTMAP_COMBINED
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile_fog
 
// Some added includes, required to use the Lighting functions
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
// And this one for the SurfaceData struct and albedo/normal/emission sampling functions.
// Note : It also defines the _BaseMap, _BumpMap and _EmissionMap textures for us, so we should use these as Shaderlab Properties too.
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
 
struct Attributes {
    float4 positionOS   : POSITION;
    float3 normalOS     : NORMAL;
    float4 tangentOS    : TANGENT;
    float4 color        : COLOR;
    float2 uv           : TEXCOORD0;
    float2 lightmapUV   : TEXCOORD1;
};
 
struct Varyings {
    float4 positionCS               : SV_POSITION;
    float4 color                    : COLOR;
    float2 uv                       : TEXCOORD0;
    DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 1);
    // Note this macro is using TEXCOORD1
#ifdef REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
    float3 positionWS               : TEXCOORD2;
#endif
    float3 normalWS                 : TEXCOORD3;
#ifdef _NORMALMAP
    float4 tangentWS                : TEXCOORD4;
#endif
    float3 viewDirWS                : TEXCOORD5;
    half4 fogFactorAndVertexLight   : TEXCOORD6;
    // x: fogFactor, yzw: vertex light
#ifdef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
    float4 shadowCoord              : TEXCOORD7;
#endif
};
 
//TEXTURE2D(_BaseMap);
//SAMPLER(sampler_BaseMap);
// Removed, since SurfaceInput.hlsl now defines the _BaseMap for us

我們的“變量”現(xiàn)在還包含正在使用的光照貼圖UV,法線和切線,但是我們還添加了“視圖方向”,這對于照明計算,霧,頂點照明支持和接收陰影的陰影坐標是必不可少的。

現(xiàn)在我們需要更新頂點著色器以處理所有這些更改,這主要是僅知道要使用的功能:

#if SHADER_LIBRARY_VERSION_MAJOR < 9
    // This function was added in URP v9.x.x versions
    // If we want to support URP versions before, we need to handle it instead.
    // Computes the world space view direction (pointing towards the viewer).
    float3 GetWorldSpaceViewDir(float3 positionWS) {
        if (unity_OrthoParams.w == 0) {
            // Perspective
            return _WorldSpaceCameraPos - positionWS;
        } else {
            // Orthographic
            float4x4 viewMat = GetWorldToViewMatrix();
            return viewMat[2].xyz;
        }
    }
#endif
 
Varyings vert(Attributes IN) {
    Varyings OUT;
 
    // Vertex Position
    VertexPositionInputs positionInputs = GetVertexPositionInputs(IN.positionOS.xyz);
    OUT.positionCS = positionInputs.positionCS;
#ifdef REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
    OUT.positionWS = positionInputs.positionWS;
#endif
    // UVs & Vertex Colour
    OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
    OUT.color = IN.color;
 
    // View Direction
    OUT.viewDirWS = GetWorldSpaceViewDir(positionInputs.positionWS);
 
    // Normals & Tangents
    VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);
    OUT.normalWS =  normalInputs.normalWS;
#ifdef _NORMALMAP
    real sign = IN.tangentOS.w * GetOddNegativeScale();
    OUT.tangentWS = half4(normalInputs.tangentWS.xyz, sign);
#endif
 
    // Vertex Lighting & Fog
    half3 vertexLight = VertexLighting(positionInputs.positionWS, normalInputs.normalWS);
    half fogFactor = ComputeFogFactor(positionInputs.positionCS.z);
    OUT.fogFactorAndVertexLight = half4(fogFactor, vertexLight);
 
    // Baked Lighting & SH (used for Ambient if there is no baked)
    OUTPUT_LIGHTMAP_UV(IN.lightmapUV, unity_LightmapST, OUT.lightmapUV);
    OUTPUT_SH(OUT.normalWS.xyz, OUT.vertexSH);
 
    // Shadow Coord
#ifdef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
    OUT.shadowCoord = GetShadowCoord(positionInputs);
#endif
    return OUT;
}

現(xiàn)在,我們還可以更新該片元著色器以實際使用UniversalFragmentPBR函數(shù)。由于它需要InputData結(jié)構(gòu)輸入,因此我們需要創(chuàng)建和設(shè)置它。代替在片元著色器中執(zhí)行此操作,我們將創(chuàng)建另一個函數(shù)來幫助組織事物。

類似地,要處理所有反照率,金屬,鏡面,平滑度,遮擋,發(fā)射和Alpha輸入,我們將使用SurfaceData結(jié)構(gòu)(由我們之前包含的SurfaceInput.hlsl提供),并創(chuàng)建另一個函數(shù)來處理它。

InputData InitializeInputData(Varyings IN, half3 normalTS){
    InputData inputData = (InputData)0;
 
#if defined(REQUIRES_WORLD_SPACE_POS_INTERPOLATOR)
    inputData.positionWS = IN.positionWS;
#endif
                 
    half3 viewDirWS = SafeNormalize(IN.viewDirWS);
#ifdef _NORMALMAP
    float sgn = IN.tangentWS.w; // should be either +1 or -1
    float3 bitangent = sgn * cross(IN.normalWS.xyz, IN.tangentWS.xyz);
    inputData.normalWS = TransformTangentToWorld(normalTS, half3x3(IN.tangentWS.xyz, bitangent.xyz, IN.normalWS.xyz));
#else
    inputData.normalWS = IN.normalWS;
#endif
 
    inputData.normalWS = NormalizeNormalPerPixel(inputData.normalWS);
    inputData.viewDirectionWS = viewDirWS;
 
#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
    inputData.shadowCoord = IN.shadowCoord;
#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
    inputData.shadowCoord = TransformWorldToShadowCoord(inputData.positionWS);
#else
    inputData.shadowCoord = float4(0, 0, 0, 0);
#endif
 
    inputData.fogCoord = IN.fogFactorAndVertexLight.x;
    inputData.vertexLighting = IN.fogFactorAndVertexLight.yzw;
    inputData.bakedGI = SAMPLE_GI(IN.lightmapUV, IN.vertexSH, inputData.normalWS);
    return inputData;
}
 
SurfaceData InitializeSurfaceData(Varyings IN){
    SurfaceData surfaceData = (SurfaceData)0;
    // Note, we can just use SurfaceData surfaceData; here and not set it.
    // However we then need to ensure all values in the struct are set before returning.
    // By casting 0 to SurfaceData, we automatically set all the contents to 0.
         
    half4 albedoAlpha = SampleAlbedoAlpha(IN.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap));
    surfaceData.alpha = Alpha(albedoAlpha.a, _BaseColor, _Cutoff);
    surfaceData.albedo = albedoAlpha.rgb * _BaseColor.rgb * IN.color.rgb;
 
    // Not supporting the metallic/specular map or occlusion map
    // for an example of that see : https://github.com/Unity-Technologies/Graphics/blob/master/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl
 
    surfaceData.smoothness = _Smoothness;
    surfaceData.normalTS = SampleNormal(IN.uv, TEXTURE2D_ARGS(_BumpMap, sampler_BumpMap), _BumpScale);
    surfaceData.emission = SampleEmission(IN.uv, _EmissionColor.rgb, TEXTURE2D_ARGS(_EmissionMap, sampler_EmissionMap));
    surfaceData.occlusion = 1;
    return surfaceData;
}
 
half4 frag(Varyings IN) : SV_Target {
    SurfaceData surfaceData = InitializeSurfaceData(IN);
    InputData inputData = InitializeInputData(IN, surfaceData.normalTS);
                 
    // In URP v10+ versions we could use this :
    // half4 color = UniversalFragmentPBR(inputData, surfaceData);
 
    // But for other versions, we need to use this instead.
    // We could also avoid using the SurfaceData struct completely, but it helps to organise things.
    half4 color = UniversalFragmentPBR(inputData, surfaceData.albedo, surfaceData.metallic, 
      surfaceData.specular, surfaceData.smoothness, surfaceData.occlusion, 
      surfaceData.emission, surfaceData.alpha);
                 
    color.rgb = MixFog(color.rgb, inputData.fogCoord);
 
    // color.a = OutputAlpha(color.a);
    // Not sure if this is important really. It's implemented as :
    // saturate(outputAlpha + _DrawObjectPassData.a);
    // Where _DrawObjectPassData.a is 1 for opaque objects and 0 for alpha blended.
    // But it was added in URP v8, and versions before just didn't have it.
    // And I'm writing thing for v7.3.1 currently
    // We could still saturate the alpha to ensure it doesn't go outside the 0-1 range though :
    color.a = saturate(color.a);
 
    return color;
}

當前,雖然我們的著色器可以接收陰影,但它不包含ShadowCaster傳遞,因此不會投射任何陰影。這將在下一部分中處理。

ShadowCaster & DepthOnly Passes

SHADOWCASTER

如果我們希望著色器投射陰影,則需要通過標簽“ LightMode” =“ ShadowCaster”的傳遞。可以在“Unlit”和“Lit”著色器上進行此操作,但要注意,盡管它們會投射陰影,但如果您不在UniversalForwardPass中處理陰影,它們將不會接收陰影。

除了使用UsePass時(在Shaderlab部分中已討論過)。盡管我們可以使用其他著色器中的陰影投射器,例如UsePass“Universal Render Pipeline/Lit/ShadowCaster”,但由于該著色器中使用的CBUFFER可能不同,因此SRP Batcher兼容性可能會丟失。

相反,您應(yīng)該自己定義這些Pass,有一個取巧的解決方法,我們可以執(zhí)行以下操作:

Pass {
    Name "ShadowCaster"
    Tags { "LightMode"="ShadowCaster" }
 
    ZWrite On
    ZTest LEqual
 
    HLSLPROGRAM
    // Required to compile gles 2.0 with standard srp library
    #pragma prefer_hlslcc gles
    #pragma exclude_renderers d3d11_9x gles
    //#pragma target 4.5
 
    // Material Keywords
    #pragma shader_feature _ALPHATEST_ON
    #pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
 
    // GPU Instancing
    #pragma multi_compile_instancing
    #pragma multi_compile _ DOTS_INSTANCING_ON
             
    #pragma vertex ShadowPassVertex
    #pragma fragment ShadowPassFragment
     
    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
 
    ENDHLSL
}

我們使用了ShadowCasterPass.hlsl中的函數(shù),意味著定義該Pass較為容易,但是它需要使用_BaseMap,_BaseColor_Cutoff屬性,我們也需要將它們添加到UnityPerMaterial CBUFFER中。陰影投射器中的fragment函數(shù)僅在需要陰影的位置返回0,并丟棄不應(yīng)有陰影的像素(請注意,僅在啟用了_ALPHATEST_ON關(guān)鍵字的情況下才會發(fā)生裁剪)

如果我們的常規(guī)著色器通道也進行頂點位移,則也需要將其添加到ShadowCaster通道中,以便正確投射位移的陰影。為了解決這個問題,我們要么將ShadowCasterPass的內(nèi)容復制到我們的過程中,要么只是定義一個新的頂點函數(shù)并交換#pragma頂點ShadowPassVertex。例如 :

#pragma vertex vert
 
...
 
// function copied from ShadowCasterPass and edited slightly.
Varyings vert(Attributes input) {
    Varyings output;
    UNITY_SETUP_INSTANCE_ID(input);
 
    // Example Displacement
    input.positionOS += float4(0, _SinTime.y, 0, 0);
 
    output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap);
    output.positionCS = GetShadowPositionHClip(input);
    return output;
}

DEPTHONLY

著色器還應(yīng)包含標記為“ LightMode” =“ DepthOnly”的過程。此過程與ShadowCaster非常相似,但沒有陰影偏差偏移。我不完全確定URP中使用DepthOnly傳遞的用途。場景視圖似乎在渲染深度紋理時使用了它(由ShaderGraph中的“Scene Depth”節(jié)點使用),而“游戲視圖”深度紋理在沒有此傳遞的情況下似乎可以正常工作。但是,可能還有其他一些東西,例如自定義渲染功能(用于前向渲染器)依賴于DepthOnly傳遞。

我們可以以類似的方式處理DepthOnly傳遞,但有一些細微差異:

Pass {
    Name "DepthOnly"
    Tags { "LightMode"="DepthOnly" }
 
    ZWrite On
    ColorMask 0
 
    HLSLPROGRAM
    // Required to compile gles 2.0 with standard srp library
    #pragma prefer_hlslcc gles
    #pragma exclude_renderers d3d11_9x gles
    //#pragma target 4.5
 
    // Material Keywords
    #pragma shader_feature _ALPHATEST_ON
    #pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
 
    // GPU Instancing
    #pragma multi_compile_instancing
    #pragma multi_compile _ DOTS_INSTANCING_ON
             
    #pragma vertex DepthOnlyVertex
    #pragma fragment DepthOnlyFragment
             
    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
 
    // Again, using this means we also need _BaseMap, _BaseColor and _Cutoff shader properties
    // Also including them in cbuffer, except _BaseMap as it's a texture.
 
    ENDHLSL
}

這次使用Unity的URP著色器提供的DepthOnlyPass 。同樣,如果需要頂點位移,我們應(yīng)該將DepthOnlyVertex函數(shù)復制到我們的代碼中,將其重命名為vert,然后像上面的ShadowCaster示例中一樣添加位移代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容