【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的效果已经十分接近。对于对于如何实现软阴影(本影+半影),希望有较好的解决方案。

posted @ 2017-10-10 14:33  JaffHan  阅读(3077)  评论(0编辑  收藏  举报