Unity中Shader的模型网格阴影

文章目录


前言

Unity中Shader的模型网格阴影,一般用于 低配设置 情况下模拟影子的样子,节省性能。(适用于地面比较平缓的游戏)


一、网格阴影原理

1、在世界空间下,把角色模型在Y轴上压缩成一个面片,把修改成像影子的颜色

2、把压缩后的面片,移动到合适的位置,把模型和阴影面片错开

3、实现距离脚进的阴影偏移少,距离脚远的阴影偏移多


二、网格阴影的优缺点

优点:

1.实现简单

2.效果实时,清晰

缺点:

1.需要两个Pass来渲染

2.在地面起伏较大时,容易穿帮


三、模型网格阴影的实现

我们继续使用上一篇文章中的Shader来进行测试

我们在中配的Shader中,实现网格阴影

需要两个Pass,一个Pass实现基本的渲染,另一个Pass实现模型网格阴影

1、在 LOD 400 的 SubShader 的第一个 Pass 中,我们只保留最基础的模型渲染效果

Pass
        {
            //Tags{"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //#pragma multi_compile_fwdbase
            //剔除无用的变体
            //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON
            //自己定义,阴影需要使用的变体
            
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            
            sampler2D _MainTex;
            float _Clip;
            sampler2D _DissolveTex; 
            //这个四维向量,xyzw分别表示 Tilling 和 Offset 的 xy ,命名方式 在纹理名 后加 _ST
            float4 _DissolveTex_ST;


            //因为 在使用渐变纹理时,只使用了 渐变纹理的 u 坐标,所以把  sampler2D 换为 sampler
            sampler _RampTex;

            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
            };
            
            
            struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                
                float4 worldPos :TEXCOORD1;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                
                //为了减少传入的值 ,所以就不创建新变量来存储,而是把 uv 改为  四维向量 来用
                //使用 o.uv 的 xy 来存放 原人物贴图
                //使用 o.uv 的 zw 来存放 噪波贴图缩放 和 偏移 后的值
                o.uv.xy = v.uv.xy;
                //o.uv.zw = v.uv * _DissolveTex_ST.xy + _DissolveTex_ST.zw;
                o.uv.zw = TRANSFORM_TEX(v.uv,_DissolveTex);

                TRANSFER_SHADOW(o)
                //把顶点转化到世界空间下
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv.xy);
                
                //外部获取的 纹理 ,使用前都需要采样
                fixed4 dissolveTex = tex2D(_DissolveTex,i.uv.zw);
                
                //片段的取舍
                clip(dissolveTex.r -  _Clip);

                //进行归一化
                fixed4 dissolveValue = saturate((dissolveTex.r - _Clip) / (_Clip + 0.1 - _Clip));

                fixed4 rampTex = tex1D(_RampTex,dissolveValue.r);

                //col += rampTex;
                return col;
            }
            ENDCG
        }

2、然后继续加一个Pass,用来实现模型网格阴影

  1. 因为我们需要修改的东西,只跟顶点有关,所以在应用程序传入 和 顶点传入片元 时,只用传入顶点信息即可

struct appdata

{

float4 vertex : POSITION;

};

struct v2f

{

float4 pos : SV_POSITION;

};

  1. 把模型压缩成一个面(为了防止面片角度乱动,所以需要转化到世界空间下来压缩)

  2. 压缩的时候,修改世界空间下的 y 值即可

  3. 把顶点信息从 世界空间 下转化到裁剪空间,使用 UNITY_MATRIX_VP

v2f vert(appdata v)

{

v2f o;

//在世界空间下 压缩模型顶点,防止压缩后角度乱动

float4 worldPos = mul(unity_ObjectToWorld,v.vertex);

worldPos.y = 0.001;

//转化为裁剪空间内的坐标,转化时使用 UNITY_MATRIX_VP 从世界空间转化到 裁剪空间

o.pos = mul(UNITY_MATRIX_VP,worldPos);

return o;

}

  1. 因为角色脚的位置不是一成不变的,所以需要定义变量用来接受角色实时的 脚的位置

_Shadow("Shadow",Float) = 0.001 用该值来替代 上面的 0.001

替代后效果(可以修改影子生成的位置了):

  1. 给世界空间下的 xz 值加减值,实现阴影的偏移

worldPos.xz += float2(-1,2);

  1. 在偏移后,发现偏移值并没有离脚近偏移少,离脚远,偏移多

实现该效果,只需要让加减的偏移值乘以角色以脚为坐标原点的 y 值即可
但是,我们的模型原点是在模型中央的,那么要获取从脚为坐标原点的 y 值
只需要使用角色的 y 值 减去 之前影子所在的 y 值即可

worldPos.xz += float2(-1,2) * (worldPosY - _Shadow);

实现后效果:

  1. 实现以上效果后,我们的影子角度也是需要修改的,所以把之前 定义的阴影位置的变量改为四维向量,xz 控制影子偏移角度,y 控制影子位置,z 控制影子透明度

(使用四维向量的原因,节省性能。要让影子透明需要修改混合模式后才看得到效果)

属性栏中:

_Shadow("Offset(XZ) Height(Y) Alpha(W)",Vector) = (-1,0.001,1,0)

SubShader中:

Blend SrcAlpha OneMinusSrcAlpha

顶点着色器 和 片元着色器中:

v2f vert(appdata v)
            {
                v2f o;
                //在世界空间下 压缩模型顶点后,防止压缩后角度乱动
                float4 worldPos = mul(unity_ObjectToWorld,v.vertex);

                float worldPosY = worldPos.y;
                    
                worldPos.y = _Shadow.y;
                worldPos.xz += _Shadow.xz * (worldPosY - _Shadow.y);
                //转化为裁剪空间内的坐标,转化时使用 UNITY_MATRIX_VP 从世界空间转化到 裁剪空间
                o.pos = mul(UNITY_MATRIX_VP,worldPos);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = 0;

                col.a = _Shadow.w;
                
                return col;
                
            }

修改后效果:

但是,我们会发现 使影子变透明后,压缩后的模型会出现 模型另一面的效果,这个情况,和之前模板测试时一模一样,使用模板测试就可解决

//使用模板测试 让 透明后的模型别看见后面的效果(对比后,不等于的直接替换)

Stencil

{

//这个值,目前是随便取的

Ref 100

//不等于上面的值的话

Comp NotEqual

//替代

Pass Replace

}

最终代码:

//网格阴影原理
Shader "MyShader/P1_7_6"
{
    Properties
    {
        [Enum(Off,0,On,1)]_ZWrite("ZWrite",int) = 0
        [Enum(UnityEngine.Rendering.CompareFunction)]_ZTest("ZTest",int) = 0
        //使用这个标签,可以使外部暴露属性,有标题
        [Header(Base)]
        [NoScaleOffset]_MainTex ("Texture", 2D) = "white" {}
        _Clip("Clip",Range(0,1)) = 0
        //使用这个标签可以 在两行暴露属性之间加 间隙
        [Space(10)]
        [Header(Dissolve)]
        _DissolveTex("DissolveTex",2D) = "black"{}

        [NoScaleOffset]_RampTex("RampTex(RGB)",2D) = "black" {}
        
        [Header(Shadow)]
        _Shadow("Offset(XZ) Height(Y) Alpha(W)",Vector) = (-1,0.001,1,0)
        
    }
    SubShader
    {
        Tags{"Queue" = "Geometry"}
        LOD 600
        
        Blend Off
        Cull Back
        /*ZWrite [_ZWrite]
        
        ZTest [_ZTest]*/
        
        Offset -1,-1
        
        UsePass "MyShader/P1_6_4/XRay"
        
        Pass
        {
            //Tags{"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //#pragma multi_compile_fwdbase
            //剔除无用的变体
            //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON
            //自己定义,阴影需要使用的变体
            #pragma multi_compile DIRECTIONAL SHADOWS_SCREEN
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            
            sampler2D _MainTex;
            float _Clip;
            sampler2D _DissolveTex; 
            //这个四维向量,xyzw分别表示 Tilling 和 Offset 的 xy ,命名方式 在纹理名 后加 _ST
            float4 _DissolveTex_ST;


            //因为 在使用渐变纹理时,只使用了 渐变纹理的 u 坐标,所以把  sampler2D 换为 sampler
            sampler _RampTex;

            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
            };
            
            //1.在v2f中添加UNITY_SHADOW_COORDS(idx),unity会自动声明一个叫_ShadowCoord的float4变量,用作阴影的采样坐标.
            struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                UNITY_SHADOW_COORDS(1)
                float4 worldPos :TEXCOORD2;
            };
            //2.在顶点着色器中添加TRANSFER_SHADOW(o),用于将上面定义的_ShadowCoord纹理采样坐标变换到相应的屏幕空间纹理坐标,为采样阴影纹理使用.
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                
                //为了减少传入的值 ,所以就不创建新变量来存储,而是把 uv 改为  四维向量 来用
                //使用 o.uv 的 xy 来存放 原人物贴图
                //使用 o.uv 的 zw 来存放 噪波贴图缩放 和 偏移 后的值
                o.uv.xy = v.uv.xy;
                //o.uv.zw = v.uv * _DissolveTex_ST.xy + _DissolveTex_ST.zw;
                o.uv.zw = TRANSFORM_TEX(v.uv,_DissolveTex);

                TRANSFER_SHADOW(o)
                //把顶点转化到世界空间下
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                
                return o;
            }
            //3.在片断着色器中添加UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos),其中atten即存储了采样后的阴影.
            fixed4 frag (v2f i) : SV_Target
            {

                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
                
                fixed4 col = tex2D(_MainTex, i.uv.xy);

                //把阴影 和 纹理相乘
                col *= atten;
                
                //外部获取的 纹理 ,使用前都需要采样
                fixed4 dissolveTex = tex2D(_DissolveTex,i.uv.zw);
                
                //片段的取舍
                clip(dissolveTex.r -  _Clip);

                //进行归一化
                fixed4 dissolveValue = saturate((dissolveTex.r - _Clip) / (_Clip + 0.1 - _Clip));

                fixed4 rampTex = tex1D(_RampTex,dissolveValue.r);

                //col += rampTex;
                return col;
            }
            ENDCG
        }
        //阴影的投射
        Pass
        {
            //1、设置 "LightMode" = "ShadowCaster"
            Tags{"LightMode" = "ShadowCaster"}
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            //需要添加一个 Unity变体
            #pragma multi_compile_shadowcaster

            
            #include "UnityCG.cginc"

            //声明消融使用的变量
            float _Clip;
            sampler2D _DissolveTex;
            float4 _DissolveTex_ST;
            
            //2、appdata中声明float4 vertex:POSITION;和half3 normal:NORMAL;这是生成阴影所需要的语义.
            //注意:在appdata部分,我们几乎不要去修改名字 和 对应的类型。
            //因为,在Unity中封装好的很多方法都是使用这些标准的名字
            struct appdata
            {
                float4 vertex:POSITION;
                half3 normal:NORMAL;
                float4 uv:TEXCOORD;
            };
            //3、v2f中添加V2F_SHADOW_CASTER;用于声明需要传送到片断的数据.
            struct v2f
            {
                float4 uv : TEXCOORD;
                V2F_SHADOW_CASTER;
            };
            //4、在顶点着色器中添加TRANSFER_SHADOW_CASTER_NORMALOFFSET(o),主要是计算阴影的偏移以解决不正确的Shadow Acne和Peter Panning现象.
            v2f vert(appdata v)
            {
                v2f o;
                o.uv.zw = TRANSFORM_TEX(v.uv,_DissolveTex);
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
                return o;
            }
            //5、在片断着色器中添加SHADOW_CASTER_FRAGMENT(i)
            
            fixed4 frag(v2f i) : SV_Target
            {
                //外部获取的 纹理 ,使用前都需要采样
                fixed4 dissolveTex = tex2D(_DissolveTex,i.uv.zw);
                
                //片段的取舍
                clip(dissolveTex.r -  _Clip);
                
                SHADOW_CASTER_FRAGMENT(i);
            }
            ENDCG
        }
    }
    SubShader
    {
        Blend SrcAlpha OneMinusSrcAlpha
        LOD 400
        //使用模板测试 让 透明后的模型别看见后面的效果(对比后,不等于的直接替换)
        Stencil
        {
            //这个值,目前是随便取的
            Ref 100
            //不等于上面的值的话
            Comp NotEqual
            //替代
            Pass Replace
        }
        
        Pass
        {
            //Tags{"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //#pragma multi_compile_fwdbase
            //剔除无用的变体
            //#pragma skip_variants DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SHADOWMASK VERTEXLIGHT_ON
            //自己定义,阴影需要使用的变体
            
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            
            sampler2D _MainTex;
            float _Clip;
            sampler2D _DissolveTex; 
            //这个四维向量,xyzw分别表示 Tilling 和 Offset 的 xy ,命名方式 在纹理名 后加 _ST
            float4 _DissolveTex_ST;


            //因为 在使用渐变纹理时,只使用了 渐变纹理的 u 坐标,所以把  sampler2D 换为 sampler
            sampler _RampTex;

            struct appdata
            {
                float4 vertex : POSITION;
                float4 uv : TEXCOORD0;
            };
            
            
            struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                
                float4 worldPos :TEXCOORD1;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                
                //为了减少传入的值 ,所以就不创建新变量来存储,而是把 uv 改为  四维向量 来用
                //使用 o.uv 的 xy 来存放 原人物贴图
                //使用 o.uv 的 zw 来存放 噪波贴图缩放 和 偏移 后的值
                o.uv.xy = v.uv.xy;
                //o.uv.zw = v.uv * _DissolveTex_ST.xy + _DissolveTex_ST.zw;
                o.uv.zw = TRANSFORM_TEX(v.uv,_DissolveTex);

                TRANSFER_SHADOW(o)
                //把顶点转化到世界空间下
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv.xy);
                
                //外部获取的 纹理 ,使用前都需要采样
                fixed4 dissolveTex = tex2D(_DissolveTex,i.uv.zw);
                
                //片段的取舍
                clip(dissolveTex.r -  _Clip);

                //进行归一化
                fixed4 dissolveValue = saturate((dissolveTex.r - _Clip) / (_Clip + 0.1 - _Clip));

                fixed4 rampTex = tex1D(_RampTex,dissolveValue.r);

                //col += rampTex;
                return col;
            }
            ENDCG
        }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            
            float4 _Shadow;
            
            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            v2f vert(appdata v)
            {
                v2f o;
                //在世界空间下 压缩模型顶点后,防止压缩后角度乱动
                float4 worldPos = mul(unity_ObjectToWorld,v.vertex);

                float worldPosY = worldPos.y;
                    
                worldPos.y = _Shadow.y;
                worldPos.xz += _Shadow.xz * (worldPosY - _Shadow.y);
                //转化为裁剪空间内的坐标,转化时使用 UNITY_MATRIX_VP 从世界空间转化到 裁剪空间
                o.pos = mul(UNITY_MATRIX_VP,worldPos);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = 0;

                col.a = _Shadow.w;
                
                return col;
                
            }
            
            ENDCG
            
        }
    }
    // Fallback "Legacy Shaders/VertexLit"
}

最终效果:

相关推荐
躺下睡觉~5 小时前
Unity-Transform类-父子关系
java·unity·游戏引擎
躺下睡觉~6 小时前
Unity-Transform类-缩放和看向
unity·游戏引擎
君莫愁。8 小时前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎
咩咩觉主8 小时前
Unity实战案例全解析:PVZ 植物卡片状态分析
unity·c#·游戏引擎
蓝裕安12 小时前
伪工厂模式制造敌人
开发语言·unity·游戏引擎
谢泽浩16 小时前
Unity 给模型贴上照片
unity·游戏引擎
z2014z16 小时前
Unity Resource System 优化笔记
unity·游戏引擎
王维志16 小时前
Unity 高亮插件HighlightPlus介绍
unity·游戏引擎
zaizai100717 小时前
我的demo保卫萝卜中的技术要点
unity
菌菌巧乐兹18 小时前
Unity 百度AI实现无绿幕拍照抠像功能(详解版)
人工智能·百度·unity