自從2019Unity發(fā)布URP以來芋忿,我就一直想嘗試下。最近公司項目遷移到了URP管線下疾棵,終于可以開始折騰URP了戈钢。
接觸一個未知的東西,當(dāng)然是從它的文檔開始啦是尔!首先映入眼簾的是URP只支持單個pass殉了,如下圖
再仔細看看,這個圖表的意思是對于實時光光照來說拟枚,built-in管線能支持多pass而URP不支持薪铜。那么众弓,合理的推測就是說ForwardAdd
這個LightMode
沒有了,額外的動態(tài)光源只會在一個pass中被計算隔箍。那么谓娃,也就是說,我在URP中可以用多pass渲染物體?
帶著這個疑問蜒滩,我想來實際寫個東西確認下滨达。想來想去,最簡單的多pass的物體就是半透明物體了俯艰,為了渲染復(fù)雜結(jié)構(gòu)的半透明物體并且保持遮擋正確捡遍,我們需要一個pass先寫入深度,再在另一個pass進行渲染竹握;或者為了看到半透明物體的內(nèi)部結(jié)構(gòu)画株,我們需要兩個pass,一個畫背面啦辐,一個畫正面谓传,然后這個物體還能進行描邊,這樣就有三個pass了芹关。那么良拼,這樣一個三pass的物體在URP中能正確繪制么?
先來看寫入深度的雙pass半透明物體的情況充边,代碼如下,基本上抄下馮樂樂前輩書里的內(nèi)容常侦,再把它翻譯成hlsl就行了浇冰。
注意cg語言可以用,只是不能參與SRP Batch聋亡,有時還會編譯錯誤造成粉色材質(zhì)肘习,所以最好用hlsl語言寫shader
Pass
{
Tags {"LightMode"="SRPDefaultUnlit"}
ZWrite On
ColorMask 0
}
Pass
{
Tags {"LightMode"="UniversalForward"}
Blend SrcAlpha OneMinusSrcAlpha
Cull Back
ZWrite Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
#pragma enable_cbuffer
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct appdata
{
float4 vertex : POSITION;
// float2 uv : TEXCOORD0;
};
struct v2f
{
// float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float fogCoord : TEXCOORD1;
float4 shadowCoord : TEXCOORD2;
};
// TEXTURE2D(_MainTex);
// SAMPLER(sampler_MainTex);
CBUFFER_START(UnityPerMaterial)
half4 _MainCol;
float _Alpha;
CBUFFER_END
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex);
float3 worldPos = TransformObjectToWorld(v.vertex);
// o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.fogCoord = ComputeFogFactor(o.vertex.z);
o.shadowCoord = TransformWorldToShadowCoord(worldPos);
return o;
}
half4 frag (v2f i) : SV_Target
{
float shadow = MainLightRealtimeShadow(i.shadowCoord);
// sample the texture
// half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
half4 col = _MainCol;
col *= _MainLightColor * shadow;
// apply fog
col.rgb = MixFog(col,i.fogCoord);
col.a = _Alpha;
return col;
// return half4(shadow,shadow,shadow,1);
}
ENDHLSL
}
通過谷歌可以知道,想用多pass那么LightMode
需要設(shè)置成SRPDefaultUnlit
坡倔,或者你不寫URP會默認加上這個tag
漂佩,這樣出來半透明物體的就對了。
而如果我們把第一個pass里面的LightMode
設(shè)為UniversalForward
罪塔,很遺憾投蝉,URP只走第一個pass,后面的pass就不走了o(╯□╰)o
那么着绊,我們需要額外pass做功能時,是不是那些額外pass的LightMode
都設(shè)為SRPDefaultUnlit
就好了呢熟尉?我們這就來試一下归露,下圖是個最簡單的cube,正面背面分兩個pass渲染斤儿,又有最簡單的描邊pass(有硬邊的物體描邊會斷裂剧包,正好比較夸張,可以看效果)雇毫,最終結(jié)果這樣
代碼如下
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
v.vertex.xyz += v.normal * 0.2;
o.vertex = TransformObjectToHClip(v.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
return half4(1,0,0,1) * _MainLightColor;
}
ENDHLSL
}
Pass
{
Tags {"LightMode"="SRPDefaultUnlit"}
Blend SrcAlpha OneMinusSrcAlpha
Cull Front
ZWrite Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
#pragma enable_cbuffer
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float fogCoord : TEXCOORD1;
float4 shadowCoord : TEXCOORD2;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float _Alpha;
CBUFFER_END
v2f vert (appdata v)
{
v2f o;
VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex);
o.vertex = TransformObjectToHClip(v.vertex);
// float3 worldPos = TransformObjectToWorld(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.fogCoord = ComputeFogFactor(o.vertex.z);
o.shadowCoord = GetShadowCoord(vertexInput);
return o;
}
half4 frag (v2f i) : SV_Target
{
Light mainLight = GetMainLight(i.shadowCoord);
// sample the texture
half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
col *= _MainLightColor * mainLight.shadowAttenuation * mainLight.distanceAttenuation;
// apply fog
col.rgb = MixFog(col,i.fogCoord);
col.a = _Alpha;
return col;
}
ENDHLSL
}
Pass
{
Tags {"LightMode"="UniversalForward"}
Blend SrcAlpha OneMinusSrcAlpha
Cull Back
ZWrite Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _SHADOWS_SOFT
#pragma enable_cbuffer
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float fogCoord : TEXCOORD1;
float4 shadowCoord : TEXCOORD2;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float _Alpha;
CBUFFER_END
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex);
float3 worldPos = TransformObjectToWorld(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.fogCoord = ComputeFogFactor(o.vertex.z);
o.shadowCoord = TransformWorldToShadowCoord(worldPos);
return o;
}
half4 frag (v2f i) : SV_Target
{
float shadow = MainLightRealtimeShadow(i.shadowCoord);
// sample the texture
half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
col *= _MainLightColor * shadow;
// apply fog
col.rgb = MixFog(col,i.fogCoord);
col.a = _Alpha;
return col;
}
ENDHLSL
}
可以看到我第一個pass的LightMode
都沒寫玄捕,但也繪制了,所以說你不寫LightMode
和寫了Tags {"LightMode"="SRPDefaultUnlit"}
是一樣的效果棚放。那么枚粘,現(xiàn)在開個腦洞,如果所有pass都打上SRPDefaultUnlit
的tag
呢飘蚯?我試了下馍迄,URP只走了第一個pass,描了個邊就結(jié)束了局骤。
所以攀圈,要保證多pass物體正確繪制,需要確保有個pass打上UniversalForward
的tag
,其余pass有SRPDefaultUnlit
的tag
也行峦甩,沒有也行赘来。
接下來搞投影,理論上半透明的東西可以有投影凯傲,我記得built-in管線里面我都是靠Fallback
來投影的犬辰,而URP似乎。冰单。幌缝。我看自帶的Lit材質(zhì)里面是FallBack "Hidden/Universal Render Pipeline/FallbackError"
,而用了以后也不會投影诫欠,那么應(yīng)該是其他的地方做了投影了涵卵。從自帶的Lit材質(zhì)來看,URP中的投影需要有個pass來操作荒叼,叫ShadowCaster轿偎,代碼如下
Pass
{
Name "ShadowCaster"
Tags{"LightMode" = "ShadowCaster"}
ZWrite On
ZTest LEqual
ColorMask 0
Cull[_Cull]
HLSLPROGRAM
#pragma exclude_renderers gles gles3 glcore
#pragma target 4.5
// -------------------------------------
// Material Keywords
#pragma shader_feature_local_fragment _ALPHATEST_ON
#pragma shader_feature_local_fragment _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.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
ENDHLSL
}
我嘗試過直接把這個pass復(fù)制黏貼到我自己寫的shader中,影子倒是投了甩挫,SRP Batch compatible卻掛了贴硫。。。
沒轍英遭,自己寫個ShadowCaster Pass吧间护,代碼如下
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
float3 _LightDirection;
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float _Alpha;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
};
float4 GetShadowPositionHClip(Attributes input)
{
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
return positionCS;
}
Varyings ShadowPassVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
output.uv = TRANSFORM_TEX(input.texcoord, _MainTex);
output.positionCS = GetShadowPositionHClip(input);
return output;
}
half4 ShadowPassFragment(Varyings input) : SV_TARGET
{
half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
clip(_Alpha * 1.5 - col.r);
return 0;
}
這樣這個陰影pass還可以根據(jù)透明度來決定陰影需不需要顯示(我只是隨便寫了下,不能滿足復(fù)雜需求)挖诸,又可以SRP Batch compatible汁尺,多好O(∩_∩)O~
然后用到之前半透明材質(zhì)中,就可以投影了
Pass
{
Name "ShadowCaster"
Tags{"LightMode" = "ShadowCaster"}
ZWrite On
ZTest LEqual
ColorMask 0
Cull[_Cull]
HLSLPROGRAM
#pragma exclude_renderers gles gles3 glcore
#pragma target 4.5
// -------------------------------------
// Material Keywords
#pragma shader_feature_local_fragment _ALPHATEST_ON
#pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//--------------------------------------
// GPU Instancing
#pragma multi_compile_instancing
#pragma multi_compile _ DOTS_INSTANCING_ON
#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment
#include "ShadowCaster.hlsl"
ENDHLSL
}
這里要注意ShadowCaster這個pass在用的時候最好保證shader property是一樣的多律,我這邊有兩個shader都使用了這個ShadowCaster的pass痴突,然而frame debugger里面顯示沒有合批,因為我這兩個shader的property不一樣狼荞,所以最好同一類材質(zhì)的東西用同一個ShadowCaster辽装,不同類的要用相同的ShadowCaster就合不了批了。
然而O辔丁J盎!多pass的物體在URP中不能進行SRP Batch丰涉,即使你費了老大的勁把shader改成SRP Batch compatible也不行拓巧!所以強烈建議在URP中不要使用多pass的材質(zhì),多pass的需求應(yīng)該通過其他途徑解決一死,比如說Render Feature肛度。這個我們下次再聊。