1、Unity使用的是Shadow Map(阴影贴图),将视点设置在光源处后向四周发射光线生成相对于光源的深度贴图,渲染物体时对相对于光源的深度进行比较判断出是否处于阴影之中。如果只是为了得到阴影贴图正常地渲染一遍场景有点浪费,所以Unity提供了LightMode为ShadowCaster的模式来简化Pass的工作量,只写入shadowmap而不渲染到帧缓冲。在Unity渲染管道中,会在当前渲染物体的shader中寻找这个Pass,如果没有那么在FallBack中继续寻找,没找到那么物体将不会向其它物体投射阴影。使用上,把顶点变换到光源坐标空间后使用xy对shadowmap进行采样得到阴影深度信息。

 

2、Unity 5之后使用了新的阴影映射技术--屏幕空间阴影映射(Screenspace Shadow Map),但需要硬件支持MRT(Multiple Render Target)。它生成shadowmap与摄像机的深度纹理,然后使用它们生成屏幕空间的阴影图,如果摄像机深度图中记录的表面深度大于转换到阴影映射纹理的深度值,此表面可见但出于阴影之中,这种方式能够使阴影贴图宝行屏幕空间所有阴影区域(???)。

 

3、Unity的光源有一个ShadowType的选项,No Shadows、Hard Shadows、Soft Shadows,Hard与Soft的区别在于软阴影把光源看做是一个区域进行处理使阴影边界能够更加平滑

unity URP半透阴影 unity 阴影shader_Soft

4、物体是否接受阴影映射也是有对应的选项,因为面剔除是默认开启的,所以shadowmap只是生成物体正面信息,如果需要背面的需要设置Cast Shadows为Two Sided

unity URP半透阴影 unity 阴影shader_#pragma_02

5、在开启了上面的选项后,需要shaderlab代码的配合才能够真正实现阴影映射,Unity提供了实现阴影映射的许多帮助内用,包含在AutoLight.cginc中

 1)首先定义vertex shader的输出结构体中添加阴影纹理的坐标

unity URP半透阴影 unity 阴影shader_Soft_03

 SHADOW_COORDS这个宏就是一个声明的语句

unity URP半透阴影 unity 阴影shader_unity URP半透阴影_04

 2)在vertex shader返回前调用TRANSFER_SHADOW宏,这宏只有一个参数是第一步中定义的输出结构体的实例

unity URP半透阴影 unity 阴影shader_Soft_05

 这个宏的作用是计算阴影纹理的映射坐标

unity URP半透阴影 unity 阴影shader_unity URP半透阴影_06

 3)在fragment shader中使用SHADOW_ATTENUATION计算出阴影映射值,这个宏的参数是fragment shader的输入结构体即vertex shader的输出结构体

unity URP半透阴影 unity 阴影shader_贴图_07

这个宏的计算方式会根据光源的类型自动进行选择

unity URP半透阴影 unity 阴影shader_unity URP半透阴影_08

 4)将第三中得到的阴影映射值域光照计算得到的漫反射、高光进行相乘即可

 

6、光线衰减也是对场景表现力的一种体现,所以Unity提供了UNITY_LIGHT_ATTENUATION来同时计算光线衰减与阴影映射得到一个综合的影响值,实现上只需要在上面的过程中替换SHADOW_ATTENUATION这个宏即可

unity URP半透阴影 unity 阴影shader_#pragma_09

 

7、透明物体的阴影纹理的生成是不能完全采用上面的方法,因为Alpha Test会丢弃片元但是VertexLit生成阴影纹理的Pass并不支持这一操作,所产生的的阴影会像不透明物体一样。能够代替的是"Transparent/Cutout/VertexLit",需要注意的his因为渲染透明物体时需要关闭深度缓冲的写入,所以内置Unity Shader都没有包含阴影投射的Pass,进而半透明物体也不会参与到深度图与阴影纹理的运算,既不产生也不接受阴影映射。但一定要为其在阴影映射上做出贡献也是可以的,粗糙方法就是按照不透明物体来,更进一步可以自己编写shader对其进行支持。

 

8、UnityCG.cginc文件中提供了大量的帮助来减轻自定义ShadowCaster Pass的工作如V2F_SHADOW_CASTER、TRANSFER_SHADOW_CASTER_NORMALOFFSET、SHADOW_CASTER_FRAGMENT......这些帮助内容能够完成那些公式化的步骤,我们将着重需要关心需求的定制内容,如顶点动画的阴影映射Pass将着重于顶点变化其它步骤能够使用帮助内容完成。

// Pass to render object as a shadow caster
Pass {
    Tags { "LightMode" = "ShadowCaster" }
            
    CGPROGRAM
            
    #pragma vertex vert
    #pragma fragment frag
    #pragma multi_compile_shadowcaster
    #include "UnityCG.cginc"
            
    float _Magnitude;
    float _Frequency;
    float _InvWaveLength;
    float _Speed;
            
    struct v2f { 
        V2F_SHADOW_CASTER;
    };
            
    v2f vert(appdata_base v) {
        v2f o;
                
        float4 offset;
        offset.yzw = float3(0.0, 0.0, 0.0);
        offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
        v.vertex = v.vertex + offset;

        TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
        return o;
    }
            
    fixed4 frag(v2f i) : SV_Target {
        SHADOW_CASTER_FRAGMENT(i)
    }
    ENDCG
}