二十七 Shader山下平面阴影

以下摘录自《Unity 3D 开发实战详解》 , 第10章 。
转注:这是一个非常简单且廉价的实时假阴影 , 比较常用到 , 据我所知 , 王者荣耀中就使用了类似的手法 。缺点就是比较假 , 只能投射到平面上 , 不能应对地面的凹凸 , 最明显的问题就是会插到墙里去 。
转注:需要传给接受阴影的地面(平面)的和 。对应的变量分别为和 。
Shader "Tut/Shadow/PlanarShadow_3" {//转注:这里对原作里的平行光计算部分和阴影淡出部分做了整合 , 略去了点光源计算部分 。Properties{_Intensity("atten",range(1,16))=1}SubShader {Pass {Tags {"LightMode" = "ForwardBase"}Material {Diffuce(1,1,1,1)}Lighting On}Pass {Tags {"LightMode" = "ForwardBase"}Cull FrontBlend DstColor SrcColorOffset -1,-1 //使阴影在平面之上CGPROGRAM#pragma vertex vert#pragma fragment frag#inlcude "UnityCG.cginc"float4x4 _World2Ground;float4x4 _Ground2World;float _Intensity;struct v2f{float4 pos:SV_POSITION;float atten:TEXCOORD0;};v2f vert(float4 vertex:POSITION){v2f o;float3 litDir;litDir = WorldSpaceLightDir(vertex);litDir = mul(float3x3(_World2Ground), litDir);//把光源方向转换到接受平面空间litDir = normalize(litDir);float4 vt;vt = mul(_Object2World, vertex);vt = mul(_World2Ground, vt);//将物体顶点转换到接受平面空间vt.xz=vt.xz-(vt.y/litDir.y)*litDir.xz;//用三角形相似计算沿光源方法投射后的xz//上面这行代码可拆解为如下的两行代码 , 这样可能在进行三角形相似计算时更好理解//vt.x=vt.x-(vt.y/litDir.y)*litDir.x;//vt.z=vt.z-(vt.y/litDir.y)*litDir.z;vt.y=0;//使阴影保持在接受平面上vt=mul(_Ground2World,vt);//返回到世界坐标空间vt=mul(_World2Object,vt);//将结果重新表达为Object Space坐标o.pos = mul(UNITY_MATRIX_MVP, vt);//经典的MVP变换 , 输出到Clip Spaceo.atten=distance(vertex,vt)/_Intensity;//根据前后定点的距离计算衰减return o;}float4 frag(void) : COLOR{return smoothstep(0,1,i.atten/2);}ENDCG}}}
在此中 , 我们首先使用固定管线对物体做了一个简单的照明 。在计算阴影的中 , 首先使用一个可以叠加阴影的混合模式 , 然后使用z偏移保证出来的阴影在接受平面之上 。和分别是我们自定义的两个进出阴影接受平面矩阵(转注:这里原作解释的不好 , 按字面意思理解即可) 。在具体计算中 , 首先将光源方向和投影物体的顶点都转换到接受平面的空间(接受平面的物体空间) , 在它们都处于同一空间后 , 通过简单的三角形近似算法 , 来计算投影物体顶点沿光线投影后在接受平面上的新位置 。因为这是一个Build In的(Built-in)Unity Plane , 所以只计算其xz分量即可 , 并将y分量设为0 , 这样投影出来的阴影就出现在接受平面上了 。投影计算完成后 , 返回世界空间 , 然后到投影物体本身的 Space , 之后的事情就如通常那样 , 一个经典的MVP变换即可 。
这个方法还有一个显而易见的问题 , 那就是物体本身是立体的 , 不在一个平面 , 因此 , 这个计算前后的点的距离是包括物体本身厚度的 , 这个厚度就会表现在阴影上 。
转注:使用可以解决这个问题 , 但是原作中并没有涉及 , 所以暂时略过 。
算了 , 以下为原创:

二十七  Shader山下平面阴影

文章插图

二十七  Shader山下平面阴影

文章插图
如上图所示 , Cube和貌似没有什么问题 , 但是右边那个绳结形状的物体的阴影就出现了问题 。解决方法 , 在第二个pass里之前 , 加入以下代码: