UnityShader实时阴影

1.概述

        在Unity中创建一个默认物体会带有一个默认的材质球和Shader,实现了一个PBR材质,里面已经实现了阴影,反射等功能。而我们不用自带的材质来实现阴影,但是实现原理还是Unity中的Shadow Map 技术。

        在前向渣染路径中,如果场景中最重要的平行光开启了阴影, Unity 就会为该光源计算它的阴影映射纹理( shadow map)。 这张阴影映射纹理本质上也是一张深度图, 它记录了从该光源的位置出发、 能看到的场景中距离它最近的表面的深度信息。

        Unity 选择使用一个额外的 Pass 来专门更新光源的阴影映射纹理, 这个 Pass 就是 LightMode 标签被设置为 ShadowCaster 的 Pass。

2.准备

        创建一个shader文件,并且命名为Shadow,写上最基础的代码。代码如下:

Shader "MyShader/Shadow" {
    Properties{
      _MainTex("Main Texture", 2D) = "white" {}
    }
        SubShader{
        Pass {
          Tags { "LightMode" = "ForwardBase" } 

          CGPROGRAM
          #pragma vertex vert
          #pragma fragment frag
          #pragma multi_compile_fwdbase

          #include "UnityCG.cginc"

          struct appdata
          {
              float4 vertex : POSITION;
              float2 uv : TEXCOORD0;
          };

          struct v2f {
            float4 pos : SV_POSITION;
            float2 uv : TEXCOORD0;
          };

          sampler2D _MainTex;
          float4 _MainTex_ST;

          v2f vert(appdata v)
          {
            v2f o;

            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = TRANSFORM_TEX(v.uv, _MainTex);
            return o;
          }

          float4 frag(v2f i) : COLOR
          {
              float4 clr = tex2D(_MainTex, i.uv);
              return clr;
          }
             ENDCG
       }
               
    }
}

然后创建一个材质球名为cube,并且关联上Shadow的shader文件。然后在Unity场景中创建一个Plane用来接受阴影,并且Receive Shadows要勾选。然后创建一个cube,Cast Shadows要开启,将cube材质球替换cube默认的材质球,如图所示:

此时这个cube上没有任何效果,没有阴影。

3.添加ShadowCaster

        前面中我们说过,Unity会选择一个额外的Pass来更新阴影映射纹理,而这个 Pass 就是 LightMode 标签被设置为 ShadowCaster 的 Pass。那么我们就添加如下代码:

                Pass
                {
                    Tags {"LightMode" = "ShadowCaster"}

                    CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag
                    #pragma multi_compile_shadowcaster
                    #include "UnityCG.cginc"

                    struct v2f {
                        V2F_SHADOW_CASTER;
                    };

                    v2f vert(appdata_base v)
                    {
                        v2f o;
                        TRANSFER_SHADOW_CASTER(o)
                        return o;
                    }

                    float4 frag(v2f i) : SV_Target
                    {
                        SHADOW_CASTER_FRAGMENT(i)
                    }
                    ENDCG
                }

保存之后,我们回到Unity场景中看到了cube产生了阴影。如下图所示:

但是,我们发现这个阴影只有在Plane上产生,在cube本身上并没有接受阴影。

4.阴影三剑客

        为了使自身能够接受阴影,使用阴影三剑客来实现,使用内置宏来计算阴影,需要注意的是三个内置宏都是在AutoLight.cginc文件中。在Base Pass上计算阴影。

  • SHADOW_COORDS:声明了一个名字_ShadowCoord的阴影纹理坐标变量。
  • TRANSFER_SHADOW:会根据平台选择阴影生成技术后,会把顶点从模型空间转到光源空间后存到_ShadowCoord中。
  • SHADOW_ATTENUATION:把上一个宏存到_ShadowCoord的内容进行采样,并返回阴影衰减。

     (1)包含新的内置文件:#include "AutoLight.cginc"。

     (2)在顶点着色器的输出结构体 v2f 中添加内置宏 SHADOW_COORDS。声明一个对阴影纹理坐标的采样。

     (3)定点着色器返回之前添加另一个内置宏 TRANSFER_SHADOW :     

       (4)片元着色器中依然使用内置宏来处理,采用内置宏 SHADOW_ATTENUTATION

                fixed shadow = SHADOW_ATTENUATION(i);

     (5)最后在返回值的时候,把变量 shadow与颜色相乘。

注意:要注意的是,这些宏会使用上下文变量进行相关计算,所以需要使自定义的变量名与这些宏中使用的变量名一致,如a2f中顶点坐标变量必须交vertex,vert输出必须为v2f且命名为v,顶点位置变量需要叫pos。 

        在添加了上述代码后,代码如下:

Shader "MyShader/Shadow" {
    Properties{
      _MainTex("Main Texture", 2D) = "white" {}
    }
        SubShader{
        Pass {
          Tags { "LightMode" = "ForwardBase" } 

          CGPROGRAM
          #pragma vertex vert
          #pragma fragment frag
          #pragma multi_compile_fwdbase

          #include "UnityCG.cginc"
          #include "AutoLight.cginc"

          struct appdata
          {
              float4 vertex : POSITION;
              float2 uv : TEXCOORD0;
          };

          struct v2f {
            float4 pos : SV_POSITION;
            float2 uv : TEXCOORD0;
            //对阴影纹理采样的坐标生成并存在_ShadowCoord变量中,参数是下一个可以寄存器的索引
            SHADOW_COORDS(2)
          };

          sampler2D _MainTex;
          float4 _MainTex_ST;

          v2f vert(appdata v)
          {
            v2f o;

            o.pos = UnityObjectToClipPos(v.vertex);
            o.uv = TRANSFORM_TEX(v.uv, _MainTex);
            //自动判断平台,如果不支持阴影映射则使用传统的映射,把值存到_ShadowCoord中
            TRANSFER_SHADOW(o)
            return o;
          }

          float4 frag(v2f i) : COLOR
          {
              float4 clr = tex2D(_MainTex, i.uv);
              //添加环境光
              float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb;
               //阴影衰减
              fixed shadow = SHADOW_ATTENUATION(i);
              return float4(shadow * clr.xyz+ ambientLighting, 1);
            }
             ENDCG
          }
                Pass
                {
                    Tags {"LightMode" = "ShadowCaster"}

                    CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag
                    #pragma multi_compile_shadowcaster
                    #include "UnityCG.cginc"

                    struct v2f {
                        V2F_SHADOW_CASTER;
                    };

                    v2f vert(appdata_base v)
                    {
                        v2f o;
                        TRANSFER_SHADOW_CASTER(o)
                        return o;
                    }

                    float4 frag(v2f i) : SV_Target
                    {
                        SHADOW_CASTER_FRAGMENT(i)
                    }
                    ENDCG
                }
    }
}

        为了更好的看效果,我复制了一个cube,将复制的cube放到cube斜上方,以便能够遮挡光线,在cube上生成阴影。当然cube自身在光源背面也会生成阴影。效果如下:

        可以看到在cube上自身能够接受阴影了。

5.总结

  •  一个物体向其他物体投射阴影

        1.在 Shader 中添加标签为 ShadowCaster 的 Pass 。

        2.打开Cast Shadows属性。

  • 一个物体接收来自其他物体的阴影:

        1.在 Shader 中对阴影映射纹理( 包括屏幕空间的阴影图) 进行采样, 把采样结果和最后的光照结果相乘来产生阴影效果。 

        2.打开Receive Shadows属性

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YanisWu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值