【Unity】Planar Shadows平面阴影的实现
Plannar Shadows,即平面阴影,是一个适用于平坦地形的假阴影技术。要求阴影的Receiver为平面,Occluder不与其他物体穿插。
实现效果
1.定向光源Planar Shadows
2.点光源Planar Shadows
实现步骤
实现Planar Shadows,这里主要考虑两种光源,定向光和点光源两种十分常用的光源类型。实现Planar Shadows的核心就是推导两种光源的投影矩阵,为了简化矩阵,将xz平面(y=0)作为Receiver平面。
顶点着色器核心代码如下:
1 v2f vert(a2v v) 2 { 3 v2f o; 4 o.uv = v.uv; 5 half3 wordPos=mul(unity_ObjectToWorld,v.vertex).xyz; 6 //投影:中心投影(透视投影,点光源),平行投影 7 //平行投影:斜投影(定向光),正投影 8 9 half3 l=_WorldSpaceLightPos0.xyz; 10 //透视投影矩阵:将点光源投影到xz平面 11 half4x4 Projection=half4x4( 12 l.y,-l.x,0,0, 13 0,0,0,0, 14 0,-l.z,l.y,0, 15 0,-1,0,l.y 16 ); 17 //斜投影矩阵 18 // half4x4 Projection=half4x4( 19 // l.y,-l.x,0,0, 20 // 0,0,0,0, 21 // 0,-l.z,l.y,0, 22 // 0,0,0,l.y 23 // ); 24 half4 shadowPos=mul(Projection,half4(wordPos,1)); 25 shadowPos.x/=shadowPos.w; 26 shadowPos.z/=shadowPos.w; 27 shadowPos.y=_HeightBias; 28 o.pos=UnityWorldToClipPos(shadowPos); 29 return o; 30 }
1.额外的阴影Pass
需要在Shader中,常规渲染Pass外增加新的用于Planar Shadows的额外的渲染Pass。
2.推导定向光和点光源的投影矩阵
定向光的投影类型为斜投影,点光源的投影类型为中心投影,需要提前推导出投影矩阵。为了条理清晰,在顶点着色器中进行了矩阵的计算。实际上,可以预计算矩阵然后脚本传递给Shader。
3.矩阵变换
首先将顶点变换到世界空间,然后进行投影变换。由于使用齐次坐标系,需要除w分量来得到正确的结果。
_WorldSpaceLightPos0——是Unity内置着色器变量,在光源为定向光的情况下,xyz分量表示方向,w分量为0,在光源为点光源的情况下,xyz分量表示位置,w分量为1。
_HeightBias——用来解决深度冲突造成的显示错误。
效果优化
以上的步骤虽然完成了阴影的形式上的正确,但仍然有优化的空间。接下来介绍使用模板缓冲,实现更优的效果。
优化效果如下:
上图实现了阴影边缘裁剪和半透明的效果。
1.边缘裁剪
为了实现边缘裁剪的效果,这里用到了模板缓冲,用于Mask掉超出Receiver边缘的阴影像素。
首先,在Receiver的Shader中,添加模板缓冲的代码:
1 Stencil { 2 Ref 1 3 Comp always 4 Pass replace 5 }
这段代码的用处是,将模板值1写入到模板缓冲中,即Receiver覆盖像素位置的对应模板缓冲区的模板值均为1。
然后在我们Occluder的Shader中,将Queue值调整为Receiver的Queue+1,同时添加一下的代码:
1 Stencil { 2 Ref 1 3 Comp equal 4 Pass keep 5 }
这段代码的用处是,绘制阴影像素的模板值为1,与深度缓存中已有的模板值比较,如果相同的话,那就保持缓冲区值不变,模板测试通过,像素将被绘制。关闭深度缓冲写入的意义是使得渲染按照先渲染Receiver然后Occluder的顺序,让Receiver的模板值先写入模板缓冲中。
2.半透明
如果我们直接对阴影进行半透明着色,会发现在简单物体上效果还可以。但是在复杂的模型上,有很大的问题,如下图所示:
因此,我们为了解决这个问题,需要用模板缓冲,来保证每个像素被绘制一次。
做法很简单,除了对一些透明的相关设置外,需要对Occluder的模板缓冲代码进行修改。
1 Stencil { 2 Ref 1 3 Comp equal 4 Pass incrWrap 5 Fail keep 6 }
这段代码的和上面的不同是,如果模板测试通过,将模板缓冲中的模板值+1,像素被绘制。这样的话,如果一个重叠像素要被绘制,因为模板值+1=2,不通过模版测试(条件为Equal),并不会被绘制。
小结
虽然实现了阴影的正确投射,以及边缘裁剪和半透明问题,现在的效果于Unity中Hard Shadow的效果已经十分接近。对于对于如何实现软阴影(本影+半影),希望有较好的解决方案。