原理
以灯光的视角对场景进行渲染,得到一张阴影深度图,然后正常渲染场景的时候在灯光空间下判断当前片段的深度是否大于阴影深度图中对应的地方,从而判断当前片段是否在阴影中。
渲染阴影深度图
- 灯光视角的投影矩阵:
- 平行光:用正交投影矩阵。
- 点光源:用六个透视投影矩阵。
- 灯光视角范围:
- 平行光:灯光的投射体正好包含摄像机所能看到的物体。
- 点光源:整个场景。
- 渲染阴影深度图
Pass{
Fog{ Mode Off }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "unitycG.cginc"
struct v2f{
float4 pos : SV_POSITION;
float2 depth :TEXCOORD0;
};
v2f vert(appdata_base v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
UNITY_TRANSFER_DEPTH(o.depth);//输出当前点的Z深度,其实就是输出o.xy = o.zw。
}
float4 frag(v2f i):COLOR{
float d = i.depth.x / i.depth.y;//透视除法
d = Linear01Depth(d);//变换到01,加强输出的对比度
return d;
}
ENDCG
}
接受阴影
- 接受灯光的世界矩阵、投影矩阵、阴影深度图。
- 取出阴影深度。
- 比较当前片段的深度和阴影图中对应深度的大小。
- Z精度:深度精度不足会产生很多问题,可以用EncodeFloatGRGA函数将深度数据存储在文理的4个通道中用来提高精度。
- Z值偏移:同时可以通过Z值偏移进一步的解决Peter Pan问题。
- 注意:如果Camera.depthTextureMode = DepthTextureMode.depth那么就可以直接从已经自动生成的深度图中采样当前片段的深度,否者自己计算深度。
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _myShadow;
float4x4 _myShadowProj;//把灯光空间矩阵,灯光的投影矩阵 * 灯光作为摄像机的视角矩阵(就是灯光的World2Local矩阵)。
struct vertOut{
float4 pos:SV_POSITION;
float4 texc:TEXCOORD0;
float4 v:TEXCOORD1;
}
vertOut vert(appdata_base b){
float4x4 proj;
proj = mul(_myShadowProj, _Object2World);
vertOut o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.texc = mul(proj, v.vertex);//将物体顶点变换的灯光空间下。
o.v = mul(UNITY_MATRIX_MVP, v.vertex);
return 0;
}
float4 frag(vertOut i):COLOR{
float4 d1 = tex2Dproj(_myShadow, i.texc).r;//从阴影图中取出对应片段的深度值。
float d2 = i.v.z / i.v.w;//计算当前片段的深度值。
if(d1 < d2){
return 0;
}else{
return 1;
}
}
ENDCG
}