初識Unity的URP并用multiple pass制作半透明物體

自從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

設(shè)成UniversalForward那么只走第一個pass征堪,只寫入了深度瘩缆,沒有輸出顏色,所以什么也沒有佃蚜。不過這里我有shadowcaster pass庸娱,所以陰影還在

那么着绊,我們需要額外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都打上SRPDefaultUnlittag呢飘蚯?我試了下馍迄,URP只走了第一個pass,描了個邊就結(jié)束了局骤。

所以攀圈,要保證多pass物體正確繪制,需要確保有個pass打上UniversalForwardtag,其余pass有SRPDefaultUnlittag也行峦甩,沒有也行赘来。

接下來搞投影,理論上半透明的東西可以有投影凯傲,我記得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肛度。這個我們下次再聊。

項目地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末投慈,一起剝皮案震驚了整個濱河市承耿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伪煤,老刑警劉巖瘩绒,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異带族,居然都是意外死亡,警方通過查閱死者的電腦和手機蟀给,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門蝙砌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人跋理,你說我怎么就攤上這事择克。” “怎么了前普?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵肚邢,是天一觀的道長。 經(jīng)常有香客問我,道長骡湖,這世上最難降的妖魔是什么贱纠? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮响蕴,結(jié)果婚禮上谆焊,老公的妹妹穿的比我還像新娘。我一直安慰自己浦夷,他們只是感情好辖试,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著劈狐,像睡著了一般罐孝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肥缔,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天莲兢,我揣著相機與錄音,去河邊找鬼辫继。 笑死怒见,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姑宽。 我是一名探鬼主播遣耍,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炮车!你這毒婦竟也來了舵变?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤瘦穆,失蹤者是張志新(化名)和其女友劉穎纪隙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扛或,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡绵咱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了熙兔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悲伶。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖住涉,靈堂內(nèi)的尸體忽然破棺而出麸锉,到底是詐尸還是另有隱情,我是刑警寧澤舆声,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布花沉,位于F島的核電站柳爽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碱屁。R本人自食惡果不足惜磷脯,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望忽媒。 院中可真熱鬧争拐,春花似錦、人聲如沸晦雨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闹瞧。三九已至绑雄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奥邮,已是汗流浹背万牺。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留洽腺,地道東北人脚粟。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像蘸朋,于是被迫代替她去往敵國和親核无。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353