初识Unity的URP并用multiple pass制作半透明物体

自从2019Unity发布URP以来,我就一直想尝试下。最近公司项目迁移到了URP管线下,终于可以开始折腾URP了。

接触一个未知的东西,当然是从它的文档开始啦!首先映入眼帘的是URP只支持单个pass,如下图

再仔细看看,这个图表的意思是对于实时光光照来说,built-in管线能支持多pass而URP不支持。那么,合理的推测就是说ForwardAdd这个LightMode没有了,额外的动态光源只会在一个pass中被计算。那么,也就是说,我在URP中可以用多pass渲染物体?

带着这个疑问,我想来实际写个东西确认下。想来想去,最简单的多pass的物体就是半透明物体了,为了渲染复杂结构的半透明物体并且保持遮挡正确,我们需要一个pass先写入深度,再在另一个pass进行渲染;或者为了看到半透明物体的内部结构,我们需要两个pass,一个画背面,一个画正面,然后这个物体还能进行描边,这样就有三个pass了。那么,这样一个三pass的物体在URP中能正确绘制么?

先来看写入深度的双pass半透明物体的情况,代码如下,基本上抄下冯乐乐前辈书里的内容,再把它翻译成hlsl就行了。

注意cg语言可以用,只是不能参与SRP Batch,有时还会编译错误造成粉色材质,所以最好用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需要设置成SRPDefaultUnlit,或者你不写URP会默认加上这个tag,这样出来半透明物体的就对了。

而如果我们把第一个pass里面的LightMode设为UniversalForward,很遗憾,URP只走第一个pass,后面的pass就不走了o(╯□╰)o

设成UniversalForward那么只走第一个pass,只写入了深度,没有输出颜色,所以什么也没有。不过这里我有shadowcaster pass,所以阴影还在

那么,我们需要额外pass做功能时,是不是那些额外pass的LightMode都设为SRPDefaultUnlit就好了呢?我们这就来试一下,下图是个最简单的cube,正面背面分两个pass渲染,又有最简单的描边pass(有硬边的物体描边会断裂,正好比较夸张,可以看效果),最终结果这样

代码如下

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"}是一样的效果。那么,现在开个脑洞,如果所有pass都打上SRPDefaultUnlittag呢?我试了下,URP只走了第一个pass,描了个边就结束了。

所以,要保证多pass物体正确绘制,需要确保有个pass打上UniversalForwardtag,其余pass有SRPDefaultUnlittag也行,没有也行。

接下来搞投影,理论上半透明的东西可以有投影,我记得built-in管线里面我都是靠Fallback来投影的,而URP似乎。。。我看自带的Lit材质里面是FallBack "Hidden/Universal Render Pipeline/FallbackError",而用了以后也不会投影,那么应该是其他的地方做了投影了。从自带的Lit材质来看,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复制黏贴到我自己写的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还可以根据透明度来决定阴影需不需要显示(我只是随便写了下,不能满足复杂需求),又可以SRP Batch compatible,多好O(∩_∩)O~

然后用到之前半透明材质中,就可以投影了

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不一样,所以最好同一类材质的东西用同一个ShadowCaster,不同类的要用相同的ShadowCaster就合不了批了。

然而!!!多pass的物体在URP中不能进行SRP Batch,即使你费了老大的劲把shader改成SRP Batch compatible也不行!所以强烈建议在URP中不要使用多pass的材质,多pass的需求应该通过其他途径解决,比如说Render Feature。这个我们下次再聊。

项目地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355