從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的默認值(假設平臺上支持它們),除非著色器指定“ #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;
矩陣通常用于不同坐標空間之間的轉換霍狰。為此抡草,我們需要進行矩陣乘法饰及,可以使用mul函數(shù)來完成(而不是*運算符,該運算符不適用于矩陣和向量類型)
數(shù)組
可以在著色器中指定數(shù)組康震,盡管Shaderlab屬性或材質(zhì)檢查器不支持它們燎含,并且必須從C#腳本中進行設置。必須在著色器中指定數(shù)組的大小腿短,并且數(shù)組大小應保持恒定以防止出現(xiàn)問題屏箍。如果我們不知道數(shù)組的大小,則需要設置最大值并以0s傳入數(shù)組填充橘忱。我們可以指定另一個float來作為需要遍歷數(shù)組的長度赴魁,例如此處的示例。
float _Array[10]; // Float array
float4 _Array[10]; // Vector array
float4x4 _Array[10]; // Matrix array
設置浮點數(shù)組時钝诚,請使用material.SetFloatArray或Shader.SetGlobalFloatArray颖御。還有SetVectorArray和SetMatrixArray及其全局版本。
其他種類
HLSL還包括其他類型敲长,例如“紋理”和“采樣器”郎嫁,可以使用URP中的以下宏進行定義:
TEXTURE2D(textureName);
SAMPLER(sampler_textureName);
還有緩沖區(qū),盡管我從未真正使用過它們祈噪,所以對它們的用法并不熟悉泽铛。它們是使用material.SetBuffer或Shader.SetGlobalBuffer從C#設置的。
#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 – 與延遲渲染有關盯串。我認為這是測試功能氯檐。
Tags { "LightMode" = "UniversalForward" }
可以在子著色器中定義多個Pass塊,但是每個都應該用一個特定的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是兩個東西流强,前者是紋理的縮放與偏移痹届,而后者代表紋理的大小呻待。
結構體
在定義頂點或片段著色器功能之前,我們需要定義一些用于將數(shù)據(jù)傳入和傳出的結構队腐。在內(nèi)置函數(shù)中蚕捉,它們通常被命名為“appdata”和“v2f”(頂點到片段的縮寫),而URP著色器則傾向于使用“ Attributes”和“ Varyings ”柴淘。這些只是名稱迫淹,可能不太重要。
struct Attributes {
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
該屬性結構將輸入到頂點著色器为严。它允許我們使用大寫字母中被稱為語義的部分從網(wǎng)格中獲取每個頂點的數(shù)據(jù)敛熬。其中包括:頂點位置(POSITION),頂點顏色(COLOR)和UV(又稱為紋理坐標)第股。網(wǎng)格具有8個不同的UV通道应民,可以通過TEXCOORD0到TEXCOORD7進行訪問。
我們還可以通過NORMAL訪問頂點法線夕吻,并通過TANGENT訪問切線诲锹。
在這些結構之后,您通常還會看到已定義了紋理和采樣器(雖然紋理位于著色器屬性中涉馅,但尚未在hlsl中定義归园。其他屬性包括在CBUFFER中)。在URP中控漠,我們使用以下內(nèi)容:
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
頂點著色器
我們的頂點著色器需要做的主要事情是將網(wǎng)格從對象空間位置轉換為剪輯空間位置蔓倍。為了在目標屏幕位置正確渲染片元/像素。
在內(nèi)置著色器中盐捷,您可以使用UnityObjectToClipPos函數(shù)執(zhí)行此操作偶翅,但是URP已將其重命名為TransformObjectToHClip(可以在函數(shù)庫SpaceTransforms.hlsl中找到)。也就是說碉渡,還有另一種方法來處理URP中的轉換聚谁,如下所示。
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;
}
-
我們從Attributes中輸入對象空間的位置滞诺,并獲得一個VertexPositionInputs結構形导,其中包含:
- positionWS,在世界空間中的位置
- positionVS习霹,視圖空間中的位置
- positionCS朵耕,裁剪空間中的位置
- positionNDC,標準化設備坐標中的位置
頂點著色器還負責將數(shù)據(jù)傳遞到片段淋叶。對于頂點顏色阎曹,這只是一個簡單的
OUT.color = IN.color;
。-
如果我們希望能夠?qū)y理進行采樣,則還需要傳遞模型的UV(紋理坐標)处嫌。雖然我們可以做
OUT.uv = IN.uv;
(假設兩者均為float2)栅贴,通常會使用TRANSFORM_TEX宏,該宏采用uv和texture屬性名稱熏迹,并應用材質(zhì)檢查器的偏移和平鋪進行矯正(存儲在“ _BaseMap” +“ _ ST”中檐薯,S用于比例尺和T))。此宏位于內(nèi)置和URP中(在core / ShaderLibrary / Macros.hlsl內(nèi)部注暗,應自動包含在Core.hlsl中)坛缕。實際上,這只是
IN.uv.xy * _BaseMap_ST.xy + _BaseMap_ST.z
的簡寫友存,因此您也可以這樣寫(將_BaseMap換成預期的紋理屬性祷膳。(texture)_ST
float4變量還必須添加到UnityPerMaterial CBUFFER(已在屬性部分中討論過)。
VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);
GetVertexNormalInputs可用于將對象空間的法線和切線轉換為世界空間屡立。它包含:
- 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ū)值坡脐。(可以將它們放入一個結構中以同時輸出SV_Target和SV_Depth)泄私。在大多數(shù)情況下,不需要覆蓋它备闲,對于許多GPU晌端,它都會關閉某些基于深度緩沖區(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下的結果相差較大渐裸,這時候可以考慮用球諧函數(shù)獲取
//URP使用的環(huán)境光
half3 ambient = _GlossyEnvironmentColor
//使用球諧函數(shù)獲取
half3 ambient = SampleSH(worldNormal);
//--Builit-in
half3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
關鍵字和著色器變體
著色器變體
在著色器中,我們可以指定更多的#pragma指令装悲,其中一些指令包括multi_compile和shader_feature昏鹃。這些可用于指定用于將“著色器”代碼的某些部分“打開”或“關閉”的關鍵字。著色器實際上被編譯為多個版本的著色器诀诊,稱為著色器變體洞渤。
MULTI_COMPILE
#pragma multi_compile _A _B _C (...etc)
在此示例中,我們將生成著色器的三個變體属瓣,其中_A载迄,_B和_C是關鍵字。
在著色器代碼中抡蛙,我們可以使用以下內(nèi)容:
#ifdef _A
// 如果A啟用护昧,編譯此代碼
#endif
#ifndef _B
// 當B被禁用時編譯此代碼,也就是只在A和C中溜畅。
// 注意#ifndef中額外的“n”表示“如果沒有定義”
#else
// 如果B啟用捏卓,編譯此代碼
#endif
#if defined(_A) || defined(_C)
// 用A或c (aka與上面的相同,假設沒有其他關鍵字)編譯此代碼
// 如果需要多個條件慈格,則必須使用長形式的"#if defined()"
// 其中|| = or怠晴, && = and
// 注意,因為關鍵字是在一個multi_compile語句中定義的
// 實際上不可能同時啟用兩者浴捆,所以&&在這里沒有意義蒜田。
#endif
// 還有#elif,用于else if語句选泻。
SHADER_FEATURE
#pragma shader_feature _A _B
這與multi_compile完全相同冲粤,但是未使用的變體將不包括在最終版本中美莫。因此,在運行時啟用/禁用這些關鍵字是不好的梯捕,因為它所需的著色器可能未包含在構建中厢呵!如果需要在運行時處理關鍵字,請改用multi_compile傀顾。
這些指令還有“頂點”和“片元”版本襟铭,可用于僅針對頂點或片段程序編譯著色器變體,從而減少了變體的總數(shù)短曾。例如 :
#pragma multi_compile_vertex _ _A
#pragma multi_compile_fragment _ _B
// also shader_feature_vertex and shader_feature_fragment
在此示例中寒砖,_A關鍵字僅用于頂點程序,_B僅用于片元嫉拐。不能同時啟用_A和_B的變體哩都。Unity告訴我們,這會產(chǎn)生2個著色器變體婉徘,盡管當您查看實際的編譯代碼時漠嵌,它更像是一個禁用兩個著色器的著色器變體和兩個“half”的變體。
著色器變體的增長
每增加一個multi_compile和shader_feature判哥,它就會為啟用/禁用關鍵字的每種可能組合生成越來越多的著色器變體献雅。以以下為例:
#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〖現(xiàn)在有6個變體。
第三行伊脓,是這6個中的每一個的另外2個變體府寒,因此我們現(xiàn)在總共有12個著色器變體。由于該行是shader_feature报腔,因此某些變體可能不會包含在構建中株搔。
每個添加了2個關鍵字的multi_compile都會使產(chǎn)生的變體數(shù)量加倍,因此包含10個變體的著色器將產(chǎn)生1024個著色器變體纯蛾!它需要編譯最終構建中需要包含的每個著色器變體纤房,因此將增加構建時間以及構建大小。
如何查看著色器的變體個數(shù)
如果要查看一個著色器產(chǎn)生多少個著色器變體翻诉,請單擊該著色器炮姨,然后在檢查器中有一個“Compile and Show Code”按鈕捌刮,旁邊是一個小的下拉箭頭,其中列出了所包含的變體數(shù)舒岸。如果單擊“skip unused shader_features”绅作,則可以切換以查看變體的總數(shù)。
關鍵字
每個項目最多還有256個關鍵字蛾派,因此最好遵循其他著色器的命名約定棚蓄。
您還會注意到,對于許多multi_compile和shader_features而言碍脏,第一個關鍵字通常僅保留為“ _”。實際上稍算,這實際上不會產(chǎn)生關鍵字典尾,因此會為256個最大值的其他關鍵字留出更多空間。
#pragma multi_compile _ _KEYWORD
#pragma shader_feature _KEYWORD
// 僅是shader_features的簡寫
#pragma shader_feature _ _KEYWORD
// 如果您需要知道該關鍵字是否已禁用
// 然后我們可以這樣做:
#ifndef _KEYWORD
// 或#if糊探!defined(_KEYWORD)
// 或#ifdef _KEYWORD #else
// code
#endif
我們還可以通過使用multi_compile和shader_feature的本地版本來避免耗盡最大的關鍵字數(shù)钾埂。這些生成的關鍵字對于該著色器來說是本地的,但是每個著色器最多也有64個本地關鍵字科平。
#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指令妓湘,這些指令提供了接收陰影所需的關鍵字查蓉。
#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"
接下來,我們將需要頂點法線來處理陰影/光照榜贴,因此我們將它們添加到Attributes和Varyings結構中豌研,并更新頂點著色器。在這里唬党,我僅顯示基于上一節(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結構砂心,下一部分討論的PBR示例也將使用該結構。
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結構的版本
// 對于之前的版本,需要改用以上版本锥涕。
//(但是您仍然可以使用SurfaceData結構來組織/保存數(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(鏡面反射)照明模型
首先,我們應該添加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_compile和shader_features以及對Attributes和Varyings結構進行調(diào)整舰绘,因為我們需要來自網(wǎng)格的法線和切線數(shù)據(jù)并將其發(fā)送到片元中以便使用它們用于照明計算蹂喻。
“屬性”塊中的這些TOGGLE特性使我們能夠從材質(zhì)檢查器啟用/禁用shader_feature關鍵字。(或者捂寿,我們可以為著色器編寫自定義編輯器/檢查器GUI或使用調(diào)試檢查器)口四。
如果要支持烘焙的光照貼圖,我們還需要在TEXCOORD1通道中傳遞的光照貼圖UV秦陋。
我還使用了來自ShaderLibrary的SurfaceInput.hlsl來幫助完成某些事情窃祝,它可以幫助SurfaceData結構保存PBR所需的數(shù)據(jù)以及一些用于采樣的反射率,法線和發(fā)射貼圖的函數(shù)(請注意踱侣,該結構似乎已經(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結構輸入绳瘟,因此我們需要創(chuàng)建和設置它雕憔。代替在片元著色器中執(zhí)行此操作,我們將創(chuàng)建另一個函數(shù)來幫助組織事物糖声。
類似地斤彼,要處理所有反照率,金屬蘸泻,鏡面琉苇,平滑度,遮擋悦施,發(fā)射和Alpha輸入并扇,我們將使用SurfaceData結構(由我們之前包含的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兼容性可能會丟失。
相反矢炼,您應該自己定義這些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,并丟棄不應有陰影的像素(請注意资昧,僅在啟用了_ALPHATEST_ON關鍵字的情況下才會發(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
著色器還應包含標記為“ 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 棺耍。同樣,如果需要頂點位移种樱,我們應該將DepthOnlyVertex函數(shù)復制到我們的代碼中蒙袍,將其重命名為vert,然后像上面的ShadowCaster示例中一樣添加位移代碼嫩挤。