模型的网格阴影形成的原理:

通过两个Pass的渲染形成,一个正常渲染模型,另一个用来渲染阴影

在需要渲染网格阴影的Pass中将先模型的坐标转换到世界空间下,在世界空间的坐标下对模型的y轴值进行压缩,使y轴值处于地面上,然后对模型的x、z轴值进行偏移

最后在将变形后的模型坐标转换到裁剪空间下输出到屏幕上就形成了阴影

模型网格阴影的优缺点:

基础渲染Pass:

 //基本的渲染Pass
 Pass
 {
     CGPROGRAM
     #pragma vertex vert
     #pragma fragment frag

     #include "UnityCG.cginc"

     struct appdata
     {
         float4 vertex : POSITION;
     };

     struct v2f
     {
         float4 pos : SV_POSITION;
     };

     v2f vert (appdata v)
     {
         v2f o;
         o.pos = UnityObjectToClipPos(v.vertex);
         return o;
     }

     fixed4 frag (v2f i) : SV_Target
     {

         //返回颜色值
         return fixed4(1,0.5,0.2,1);
     }
     ENDCG
 }

网格阴影Pass:

Shader "unity/Shadow_wangge"
{
    Properties
    {
        _Shadow("Shadow(Offset(X、Z),Height(Y),Alpha(W))",vector)=(0.5,0,1,1)
    }

    SubShader
    {

      //基础渲染Pass

      Pass{......}

      //网格阴影渲染Pass

      Pass{......}

      }

Pass
{
    //定义半透明的混合效果
    Blend SrcAlpha OneMinusSrcAlpha 

    //通过模版测试解决半透明模型阴影前后面均绘制的情况
    Stencil 
    {
        Ref 100
        Comp NotEqual
        Pass Replace
     }


    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;
        //通过可以_Shadow变量改变模型阴影的世界空间坐标的Y值,在将其变换到裁剪空间下
        worldPos.y=_Shadow.y;
        //改变模型阴影X、Z轴方向的偏移效果,并使其可以自动调节(也就是将fixed2(1,2)变为可调节的参数)
        //(worldPosY-worldPos.y)可以是模型的阴影从模型的正底部开始 worldPosY是模型的世界坐标 worldPos.y是模型阴影被压缩后的Y值(最开始值为0)
        //worldPos.xz+=fixed2(1,2)*(worldPosY-worldPos.y);
        worldPos.xz+=_Shadow.xz*(worldPosY-worldPos.y);
        //将世界空间下的坐标变换到裁剪空间
        o.pos=mul(UNITY_MATRIX_VP,worldPos);
        //o.pos=UnityObjectToClipPos(v.vertex);
        return o;
     }

    float4 frag(v2f i):SV_Target
    {
        //c=0代表阴影的颜色为黑色
        fixed4 c=0;
        //_Shadow.w可以控制阴影的半透明效果
        c.a=_Shadow.w;
        return c;

     }
    ENDCG
 }

1.在顶点着色器 v2f vert(appdata v){......}中将模型的本地空间坐标转换为世界空间坐标,方便后续的阴影偏移等效果的实现

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

2.然后将模型的世界空间下坐标的Y轴值赋值给一个自定义变量worldPosY,方便后续使用(因为之后要将所得模型Y轴的值进行压缩(所有Y轴上的值都会变为一个常量)这样原本模型的Y轴上的值就会发生变化)

float worldPosY = worldPos.y;

3.将模型的Y轴方向的值进行压缩,从而形成一个面片,用于阴影的效果表现

当将模型的Y轴方向的值都变为0时:(模型被压缩为一个面片,且面片Y轴的坐标值为0)

可以将一个可调节的参数属性_Shadow.y (_Shadow是一个四维向量) 赋值给模型的y值,这样将原本模型的Y轴值都变为变量_Shadow.y,从而就可以调节模型被压缩后形成的面片阴影的上下位置

Properties

{

    _Shadow(".....",vector)=(0.5,0,0.6,1);   //第二个参数表示压缩后模型的y值信息

}

Pass{

v2f vert(appdata v)
    {
        v2f o;

        .......

        worldPos.y=_Shadow.y;

}

4.将模型被压缩后而形成的阴影面片的X轴、Z轴方向的值进行偏移,以及将将阴影的初始值放置到模型的底部

worldPos.xz+=_Shadow.xz*(worldPosY-_Shadow.y);

worldPos.xz+=_shadow.xz将压缩后的面片模型的x、z轴的坐标位置(worldPos.xz)整体加上 _Shadow.xz,使得面片的整体坐标发生的移动

(worldPosY-_Shadow.y) :worldPosY是原始模型在世界空间坐标系下Y轴方向的值,_Shadow.y是被压缩后形成的面片在世界空间下的Y轴方向值。原始模型的最底部的y值(worldPosY)正好等于被压缩后的面片的y值(_Shadow.y),所以此时(worldPosY-_Shadow.y) =0

从而_Shadow.xz*(worldPosY-worldPos.y)=0,所以worldPos.xz+=_Shadow.xz*(worldPosY-worldPos.y) 就变成了worldPos.xz+=0(worldPos.xz=worldPos.xz+0),因此此时被压缩后模型x、z方向的坐标值worldPos.xz等于原始模型的xz方向的坐标值,所以由原始模型最底部形成的阴影面片就会到模型的底部

而随着原始模型Y轴方向的值(worldPosY)的增大,(worldPosY-_Shadow.y) 的值也随之增大,通过计算worldPos.xz+=_Shadow.xz*(worldPosY-_Shadow.y),阴影面片也就会发生偏移

5.最后将阴影模型面片转换到裁剪空间下

o.pos=mul(UNITY_MATRIX_VP,worldPos);

将网格阴影的效果变为半透明:

 Properties
 {

     //最后一个参数表示网格阴影的半透明效果值
     _Shadow("Shadow(Offset(X、Z),Height(Y),Alpha(W))",vector)=(0.5,0,1,1)
 }

SubShader

{

//定义半透明的混合效果
Blend SrcAlpha OneMinusSrcAlpha 

.......

}

 float4 frag(v2f i):SV_Target
 {
     //c=0代表阴影的整体颜色为黑色
     fixed4 c=0;    
     //_Shadow.w可以控制阴影的半透明效果 (RGBA)其中的A值代表半透明通道
     c.a=_Shadow.w;
     return c;
  }

利用模版测试解决因阴影模型半透明效果而产生的双面显示的效果:

  Stencil 
    {
        Ref 100
        Comp NotEqual    
//不相等时通过
        Pass Replace
     }

为什么要使用模版测试而不用深度测试呢?

模板测试发生在片元着色器之后,是一种用于决定片元是否可见的手段,可以将模板测试理解为特殊的深度测试,深度测试中的深度值是由物体本身的位置决定,但模板测试中的 "深度值" 是由我们自己定义的,所以我们在利用模板测试实现某些效果时,和深度测试的逻辑很类似。同样也存在一个和深度缓冲区一样的模板缓冲区
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/SliverAsh_/article/details/127427617

其实我也不太明白,还望有大佬看到的话能指点一二!!!!

添加模版测试后的最终效果:

代码:

Shader "unity/Shadow_wangge"
{
    Properties
    {
        _Shadow("Shadow(Offset(X、Z),Height(Y),Alpha(W))",vector)=(0.5,0,1,1)
    }
    SubShader
    {
        //基本的渲染Pass
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(1,0.5,0.2,1);
            }
            ENDCG
        }

        //模型网格阴影的渲染Pass
        Pass
        {
            //定义半透明的混合效果
            Blend SrcAlpha OneMinusSrcAlpha 

            //通过模版测试解决半透明模型阴影前后面均绘制的情况
            Stencil 
            {
                Ref 100
                Comp NotEqual   //不相等时通过
                Pass Replace    //重写模版缓冲区内的值
             }
            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;
                //通过可以_Shadow变量改变模型阴影的世界空间坐标的Y值,在将其变换到裁剪空间下
                worldPos.y=_Shadow.y;
                //改变模型阴影X、Z轴方向的偏移效果,并使其可以自动调节(也就是将fixed2(1,2)变为可调节的参数)
                //(worldPosY-worldPos.y)可以是模型的阴影从模型的正底部开始 worldPosY是模型的世界坐标 worldPos.y是模型阴影被压缩后的Y值(最开始值为0)
                //worldPos.xz+=fixed2(1,2)*(worldPosY-worldPos.y);
                worldPos.xz+=_Shadow.xz*(worldPosY-_Shadow.y);
                //将世界空间下的坐标变换到裁剪空间
                o.pos=mul(UNITY_MATRIX_VP,worldPos);
                //o.pos=UnityObjectToClipPos(v.vertex);
                return o;
             }

            float4 frag(v2f i):SV_Target
            {
                //c=0代表阴影的颜色为黑色
                fixed4 c=0;
                //_Shadow.w可以控制阴影的半透明效果
                c.a=_Shadow.w;
                return c;
             }
            ENDCG
         }
    }
}

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐