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

  • 相关阅读:
    IT教育课程考评系统开发-07
    2020091201-1
    ip
    输入框枚举
    语言枚举
    《岁月神偷》弹唱和弦吉他谱_六线谱
    string 转化成 string数组
    获取类的字段值
    获取类的字段
    最全的省份递归
  • 原文地址:https://www.cnblogs.com/jaffhan/p/7645475.html
Copyright © 2011-2022 走看看