产生阴影的原理:光沿直线传播
即,从光源出发,看不到的地方都处于阴影中
Unity处理阴影的两种途径
传统的阴影映射
- 调用LightMode为ShadowCaster的Pass,获取光源的阴影映射纹理ShadowMap(包含可以被光源照亮的点的z值)
- 在正常渲染的Pass中,将顶点转换到光源空间中,获取该顶点在光源空间中的xy坐标,以及深度值z
- 使用顶点在光源空间中的xy坐标,对ShadowMap进行采样获得深度值Z
- 若z > Z,则该点处于阴影中
屏幕空间的阴影映射(Screenspace Shadow Map)
原本为延迟渲染中的方法,需要显卡支持MRT,否则使用传统的阴影映射处理
- 调用LightMode为ShadowCaster的Pass,获取光源的阴影映射纹理ShadowMap(可以被光源照亮的点的z值),和摄像机的深度纹理DepthMap(可以显示在屏幕上的点的z值)
- 使用ShadowMap和DepthMap计算获得屏幕空间的阴影图(显示在屏幕上的每一个像素是否可以被光源照亮的信息)
阴影的投射与接收
阴影的投射
若想让一个物体投射阴影给其他物体,需要将该物体添加到光源的阴影映射纹理ShadowMap的计算中(即shader中应包含LightMode为ShadowCaster的Pass,如果没有就会去FallBack中查找),否则该物体无法投射阴影
在Unity中,需要在Inspector中MeshRender组件的Lighting-CastShadows属性进行设置
注意:一些特殊情况下可能需要将CastShadows设置为“Two Sided”,因为默认情况下在计算光源阴影映射纹理时会剔除掉网格的背面,导致无法生成阴影
阴影的接收
若想让一个物体接收阴影,需要在着色时对光源的阴影映射纹理ShadowMap进行采样,并对光照结果进行相乘处理
在Unity中,需要勾选Inspector中MeshRender组件的Lighting-ReceiveShadows选项
ShadowCaster Pass
功能:将深度信息写入渲染目标中
渲染目标:光源的阴影映射纹理,或摄像机的深度纹理
所在内置着色器:VertexLit
使用:一般情况下不需要自己写,而是直接FallBack到含有该Pass的内置着色器
具体内容(提取于Unity内置着色器:builtin_shaders-2019.4.21f1.zip\DefaultResourcesExtra\Normal-VertexLit.Shader),大概了解一下:
// Pass to render object as a shadow caster
Pass {
Name "ShadowCaster"
Tags {
"LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_shadowcaster
// allow instanced shadow pass for most of the shaders
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct v2f {
V2F_SHADOW_CASTER;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert( appdata_base v )
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
float4 frag( v2f i ) : SV_Target
{
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
其中appdata_base为内置结构体,包含顶点位置、顶点法线、第一组纹理坐标
阴影接收相关的内置宏
阴影计算“三剑客”
SHADOW_COORDS
作用:声明一个用于对阴影纹理采样的坐标
struct v2f {
// *为了宏正确执行,与SV_POSITION绑定的变量名必须为pos
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
// TEXCOORD0、TEXCOORD1已被使用,下一个可用的纹理坐标为TEXCOORD2
SHADOW_COORDS(2)
};
TRANSFER_SHADOW
作用:计算阴影纹理坐标
struct a2v {
// * 为了宏正确执行,顶点坐标必须命名为vertex
float4 vertex : POSITION;
float3 normal : N