话说当年学shader的目的, 就是为了想实现一个当时天真地以为很NB的算法TSM, 一年多来朝着这个目标努力, 到最后却发现, 结果并没有想像中的美好-_-
最开始实现的是Projective Shadow, 也就是投影纹理的方法. 先把需要投影的物体从光源视角以单一颜色画到一张RTT上, 然后用投影矩阵生成纹理坐标投影到地表上去跟纹理进行混合. 为什么用它呢? 主要是它对硬件没什么要求, 能支持RTT就OK了. 那么, 接下来, 有这么几个问题要解决:
l 锯齿: 没办法, 谁叫RTT那么小呢, 再大也比地形要小
l 阴影范围: RTT就那么大, 投影完了只是对应地上一小块区域, 区域外的没影子了
l 阴影遮挡错误: 这个是投影阴影的硬伤, 它没法判断谁在前谁在后. 比如人站在桥上, 那么不管桥墩, 桥面还是桥底下, 统统投上了影子. 你要是站在屋里看到屋顶上有个影子, 这不是见鬼了么...
l 自阴影就不用想了, 会把自己的正反面全变成影子的颜色
那么, 这些问题, 一个一个来. 找啊找啊找朋友, 哦, 不对, 是找例子. 找到了NV SDK里的PSM例子, 挺典型的, 大场景+平行光+四种算法, 很不错. 下面看看这几个问题怎么一个个地干掉:
l 锯齿: 其实PSM系的算法(包括LiPSM, TSM), 都是把View/Project矩阵拿来猥亵一番, 让阴影在RTT占的面积尽可能地大. 变换完后, 离你近的阴影在RTT上比离你远的要大. 所以, 这几种算法的核心是怎么生成那个变换矩阵, shader那边跟传统的SM没什么差别. 所以, 那矩阵生成代码直接挖出来就能用@_@
l 阴影范围: 这个是两个因素决定的: 一是投影的物体所占的范围, 二是接收阴影的物体所占的范围. 跟据双方的包围盒加吧加吧就出一个范围, 再根据这个范围来对投影矩阵进行优化, 不浪费一块地儿~ 事实证明, 这方法在不考虑接收阴影的物体时, 比什么SM算法都有效, 不过也会产生很诡异的问题. 比如, 你自己一个人时, 阴影精度高乎想像. 而人或NPC一多, 又变成原来那个样子了. 而且, 随着NPC的走动, 阴影的锯齿也在动, 抖啊抖得像是羊癫疯
后俩错误, 没有Depth Buffer是无药可救了, 正好在NV这个例子里注意到了它用的是自家的Hardware Shadow Mapping, 然后我就义无反顾地叛变了…
用HSM(但愿这么叫没有跟某SM算法冲突了), 有啥好处呢?
1. 写DepthBuffer时完全可以把ColorWrite关掉, 理论上比同时写ColorBuffer+DepthBuffer要快2~4倍. 而且实现完了发现它可以跟ProjectiveShadowMapping(我又发明了个名字@#%!@)共用同样的shader, 所以呢, 最后的实现结果会比它速度要快, 白来的性能不要白不要, 更何况遮挡错误也解决了.
2. 根据NV官方文档的说法, 用HSM时开启Linear过滤会免费给你进行2x2的PCF模糊, 能够减轻阴影边缘的柔和度. 不过我当时用错了, 后面提到PCF时再详细说
坏处也有, 这是NV自己的标准! 但我用的ATi3600咋也能跑NV的这个DEMO呢? ATi官方只提到了它自己的那个什么Fetch4技术, 同样是DepthTexture, 比NV的难用多了. 反正这卡跑着没问题, 当时就把ATi的那种实现直接无视了, 不支持就换成投影阴影好了. 后来在某年的显卡驱动新闻中知道, ATi在HD2400之后的显卡也提供了对NV的DepthStencilTexture的支持. 另外, N卡把DST当普通纹理时只能看到一片白, 而A卡确是红黑相间, 可以把DST画出来DEBUG阴影时用. 看看人家ATi, 比HSM的娘家都好.
下面说说自阴影的问题…用SM做自阴影简单是对心理素质的一种磨练, 承受能力差的还是放弃吧-_-在与光线几乎平行的面上的阴影交界处, 那是惨不忍睹啊….就算是地形这种平面, 要是做自阴影, 还有可能出现一大片的”斑马纹”~ 为什么会这样呢? 主要有两个原因:
1. DepthBuffer精度不够. 其实一般D24S8就够了, 但是我同时又结合了TSM, 问题就来了. TSM变换完后Z的范围都集中在一个很小的范围内, 官方是建议把变换前的Z写进buffer的. 但是呢, 对于HSM, DepthBuffer里写什么值不是我们能控制的(这个过程是跟固定管线混合的, 免去了shader的切换), 要想写上自定义的Z值, 需要自定义的VertexShader和浮点格式的RTT, 这个性能消耗就又上去了, 忽略. 还有就是ZNear和ZFar的控制, 这个结合上面说过的包围盒裁剪就可以做得很好. 还有就是需要调节两个bias值来减轻这种现象, 但是调多了也会产生走样, 甚用~
2. 再就是几何问题了. 对于与光线近乎平行的面, 可能一大串的地方(从抽象机角度看)只对应深度纹理上一个在阴影里的像素, 那么这一大串像素就是黑的了(在阴影内). 正好旁边RTT上的另一个像素在阴影外, 那么它对应的那一大串像素就成白了的(阴影外) . 于是乎阴影交界处就产生了参差不齐的花纹, 从另一个角度看, 还挺有艺术感的. 这种情况, 调bias值一般解决不了. 所以, 大多数游戏都不做自阴影的, 可怜地鸡肋…
对于1的问题, 调调bias, 弄弄Frustum, 调节一下CullMode估计就能解决的很好. 对于2, 我只知道一种方法: 模糊!
比如用SM来生成地形的LightMap, 有自阴影的话很难看, 但是做一下Blur之后, 就会发现, 原来效果这么完美. 阴影边界自然而然地有了过渡, 不在再生硬了, 走样问题也被模糊掉了, 一举两得.
那么实时阴影也是一个道理, 通常会采用PCF. 但是PCF的本意没有错, 错的是它又带来更严重的斑马纹~关于它的解决方法, 还没学习到. 暂且不说. 还有一种比较流行的SM : VSM, 是基于方差的, 看demo效果是不错, 就是效率让人受不了, 而且也没法利用DST这个好东西了. 所以还是先看看怎么来解决PCF的缺点来得实用.
今天先写到这里, 老婆召唤了~