[工作积累] shadow map问题汇总

1.基本问题和相关

Common Techniques to Improve Shadow Depth Maps:

https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324(v=vs.85).aspx

Cascaded Shadow Maps

https://msdn.microsoft.com/en-us/library/windows/desktop/ee416307(v=vs.85).aspx

Soft shadow

PCSS: http://developer.download.nvidia.com/shaderlibrary/docs/shadow_PCSS.pdf

PCSS shader sample: http://developer.download.nvidia.com/whitepapers/2008/PCSS_Integration.pdf

Translucent shadow

http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/S2008-Filion-McNaughton-StarCraftII.pdf

http://www.crytek.com/download/Playing%20with%20Real-Time%20Shadows.pdf

Shadows & Transparency

Translucency map generation:

  • Depth testing using depth buffer from a regular opaque shadow map to avoid back projection/leaking
  • Transparency alpha is accumulated only for objects that are not in “opaque” shadows
  • Alpha blended shadow generation pass to accumulate translucency alpha (sorted back to front)
  • In case of cascaded shadow maps, generate translucency map for each cascade
  • Shadow terms from shadow map and translucency map are both combined during deferred shadow passes with max() operation S

2.实现细节和问题

Hardware shadow map:

D16/ D32 / D24S8 作为rendertarget.

Hardware PCF:

  • 纹理采样开启双线性过滤
  • d3d9用tex2Dproj, d3d11用SampleCmpLevelZero

最小视锥(minimal frustum)

在shadow map贴图大小固定的情况下, 视锥越小, shadow map上的内容越少(有效内容不变), 所以利用率和分辨率越高.

naive 实现:

1.计算场景包围盒

2.根据场景包围盒计算最小视锥

3.渲染shadow map

更好的实现:

1.计算场景包围盒

2.用场景相机裁剪这个包围盒

3.用裁剪后的凸包体,计算最小视锥

4.渲染shadow map

由于场景包围盒被相机裁剪(求交), 所以包围盒变小, 那么生成的视锥也变得更小.

因为视锥是不规则六面体, 视锥和AABB求交得到的是一个凸包.求交方法可以看Ogre, Ogre有convex实现.

我用的另外一个方法: 用aabb 的12条边和视锥的6个面求交, 视锥的12个边和aabb的6个面求交, 得到的交点如果同时在aabb和视锥内就是凸包体的顶点.

如果要计算凸包在Light space的包围盒, 那么应该先将每个顶点变换到light space, 再求包围盒.

如果先求包围盒, 在把包围盒变换到light space, 因为AABB的轴对齐特性, 变换以后通常会变大.

Depth bias based on frustum depth range

因为使用的是Hardware shadowmap, 所以需要指定depth bias和slope scaled bias.

由于frustum根据根据convex的顶点计算来. 如果frustum的深度范围(znear, zfar)不固定的话, 那么相同的bias值, 对应的误差会有浮动.

所以可以根据视锥的深度范围来计算bias, 这样的误差值是固定的. 方法:

渲染shadow map depth的时候只指定slope scaled bias, 不指定depth bias.

渲染物体的时候, shadow matrix = M * light_projection * light_view , 也就是再乘以一个参数矩阵 M (DepthBias):

        float offset = isD3D9 ? 0.5f / (float)ShadowMapSize : 0f;   //half texel offset
        float bias = -0.0015f / (DepthRange) * adjustScale;
        DepthBias[0] = Vector4(1, 0.0f, 0.0f, offset));
        DepthBias[1] = Vector4(0.0f, 1, 0.0f, offset));
        DepthBias[2] = Vector4(0.0f, 0.0f, 1.0f, bias));
        DepthBias[3] = Vector4(0.0f, 0.0f, 0.0f, 1.0f));

同时由于计算出的uv要采样shadowmap, 对于dx9来说, 同时可以预先应用half pixel offset.

PCSS:

PCSS的原理比较简单, 也有很多变种. 目前用的是标准的实现. 遇到的问题:

PCSS的半影采样范围是根据相似三角形计算出来的

float PenumbraSize(float zReceiver, float zBlocker) //Parallel plane estimation
{
     return (zReceiver - zBlocker) / zBlocker;
}  

如果zblocker的深度太小(接近0), 那么半影采样范围就变得非常大, 难以接受.

这种情况在做Self shadow的时候会遇到, 因为视锥是很小的. 解决办法:

1.用户指定半影的大小范围(shader constant, lightWidthMin, lightWidthMax),  然后根据深度(距离)做线性插值.

或者 2.放大depth range, 这样最小的depth也不会接近0

如果产生阴影的物体和接受阴影的物体靠得太近, zReceiver - zBlocker 太小, 导致半影范围接近于0, 导致锯齿:

解决办法可以用上面的线性插值, 因为线性插值最小值是lightWidthMin, 保证有最小的半影范围.

或者: 在半影范围上加一个常量值, 比如1.0/shadowMapSize
个人使用lightwidth_min和max线性插值, 这样也方便美术调控参数.

Translucent shadow(not alpha test):

上面已经贴出的Crytek和StarCraftII的方法了, 方式比较类似.

我这里的简单实现:

R8 + Depth16, alpha和depth同时绘制, 一个color buffer保存alpha, 一个depth buffer保存深度.

opaque: Depth test - less, disable color write.

transparency: Depth test - less, disable Z write, output alpha, enable color blending(addative)

这种方式比较简单, 一次绘制没有render target切换, 先画不透明物体再画半透明物体. shader中对于alpha的阴影判断也比较hacky: 如果alpha值(R8.color.r)不为0, 则认为有阴影, 不需要比较深度. 因为能写alpha值的时候, 说明深度测试less通过了. 对于PCSS需要深度的, 可以模拟一个深度值.

问题: 不支持自阴影(doesn‘t support self shadow). 产生阴影的半透明物体本身, 如果要计算阴影, 根据alpha!=0这个判断, 也是有阴影的...

改进方式:

基于上面的方式, 给transparent objects再加上一个depth pass, 绘制阴影时采样两张深度图. 或者将前面的R8改为RGBA, A保存透明度, RGB打包深度, 单独混合alpha通道, 这样不用切换render target.
两个depth pass的话多一张D16的贴图, 显存占用要比RGBA小.

问题: 自阴影错误

|        |          |

a1      a2       O

如上, O是不透明物体, a1和a2是透明物体.

当出现多层透明物体的时候, O的阴影是对的, 因为a1和a2的alpha 会混合.

基于上面的改进, 因为有了深度信息, 再加上bias, 所以a1不会有阴影, 也是对的.

但是a2的阴影和O的阴影是一样的, 都是基于同一个alpha计算出来的.由于a2是半透明物体, 阴影表现没有那么明显, 这里的问题可以忽略.

或者: 用深度来做线性插值进行:

shadow(uv.xyz) = lerp(1-alpha_a1a2, 1, saturate( (depth_O  - uv.z) / (deoth_O -  depth_a1) ) );

其中uv.z是shader中当前物体的深度. alpha_a1a2是alpha混合的结果. depth_O是opaque shadow map采样出的深度. depth_a1是transparent shadow map采样出来的深度.

这其实还是不对的, 因为a2的阴影透明度是1-alpha_a1, 跟距离无关, 但是可以解决透明物体和非透明物体靠的太近时的z fighting和shadow acne.

上面是工作中主要遇到的问题. 另外简单记录一下其他东西.

CSM

如果所有阴影都产生在一张shadowmap上,那么近处的分辨率也会比较低.CSM主要是通过多个cascade来提高近处的阴影分辨率.

之前工作中也做过PSSM, 一般会把多个shadowmap合到一张上, 比如4张1024x1024的shadowmap, 可以用2048x2048的贴图, 通过viewport来绘制四个区域. 上面Crytek也提到了.

还有CSM边界分割处也需要blend处理, 不然会有缝隙.

PSM

perspect最大的特点是近大远小, 所以使用pserspective 投影, 来提高近处阴影的分辨率.

但一般方向光都是平行光, 需要用orthographic (parallel)投影, 但由于一般场景相机都是perspective, 视锥不是box, 但在投影以后(post perspective space)是一个box, D3D是z[0,1]的扁盒子, OGL是一个cube. 在这个空间下, 因为方向会有切变, 所以原本世界空间的方向光到了post perspective space就变成了点光源, 可以用perspective projection了.

原理大致是这样, 实现的话会比较tricky.

另外一个Light space perspective shadow map (LiSPSM), 也是一种PSM, 还是利用perspective 投影来提高近处投影的精度. 主要的改变是不在用场景相机的post perspective space. 因为perspective投影下大部分方向都会有形变, 但是垂直于视方向(平行于xy平面)的方向不会有改变. LiSPSM就是利用这一点, 使用一个垂直于光照方向的透视投影, 来渲染深度. 如果直接用这个视锥渲染的话, y值就是沿着光照方向的深度值. 所以先把光空间变换到垂直于光照方向, 在变换回来, 得到的z就是深度了. 这个实现上要比PSM简单得多. 我也尝试了一下, 但是和一般的shadow map差别不大, 可能是实现上有点问题, 或者是只渲染了一个角色的自阴影的问题.

时间: 2024-10-25 19:04:19

[工作积累] shadow map问题汇总的相关文章

Shadow Map阴影贴图技术之探 【转】

这两天勉勉强强把一个shadowmap的demo做出来了.参考资料多,苦头可不少.Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势.本篇是第一辑.——ZwqXin.comShadow Map的原理很简单,但是实现起来到处是雷.当然这只是我的体会.恩,不过就是“从光源处看场景,那些看不见的区域全部都该是阴影”.很容易看出,与针对 特定模型的Shadow Volume不同,Shadow Map是针对场景的.这就是说,对一个光源应用一次

(转)Shadow Map & Shadow Volume

转自:http://blog.csdn.net/hippig/article/details/7858574 shadow volume 这个术语几乎是随着 DOOM3 的发布而成为FPS 玩家和图形学爱好者谈论的对象的.虽然这个游戏还没有上市,但是凭借 John Carmack 的传奇经历以及 DOOM3发布的一些让人惊讶的预览图片,我们仍然有理由认为它将会是 2004 年最热门的 FPS 游戏之一. id software向来都不吝惜为了达到最好的图像效果而使用最先进的渲染技术,这曾经使得玩

Shadow Map 原理和改进 【转】

http://blog.csdn.net/ronintao/article/details/51649664 参考 1.Common Techniques to Improve Shadow Depth Maps 2.Tutorial 16 : Shadow mapping 3.Shadow Mapping 4.Shadow Mapping Algorithms 5.Shadow Map阴影贴图技术之探 6.Cascaded Shadow Maps 写在前面 之前已经很久没有再更新博客,上一篇已

[工作积累] Google/Amazon平台的各种坑

所谓坑, 就是文档中没有标明的特别需要处理的细节, 工作中会被无故的卡住各种令人恼火的问题. 包括系统级的bug和没有文档化的限制. 继Android的各种坑后, 现在做Amazon平台, 遇到的坑很多, 这里记录一下备忘: 先汇总下Android Native下的各种问题, 当然有些限制有明确文档说明,不算坑,但是限制太多还是很不爽: android平台下的某些限制: android下的各种坑 (我的C/C++/汇编/计算机原理博客) OBB的各种bug: OBB的解决方案 arm gcc t

[ZZ] Shadow Map

Shadow Map 如何能够高效的产生更接近真实的阴影一直是视频游戏的一个很有挑战的工作,本文介绍目前所为人熟知的两种阴影技术之一的ShadowMap(阴影图)技术.     ShadowMap技术的概念应该说是最早应用在视频游戏中的阴影实现技术,有着非常高效和快速的特点,在实现阴影的同时只需要相对很小的计算负担.     ShadowMap绘制阴影主要是通过一张额外的阴影贴图来实现的,在早期的3D游戏中人物等动态运动的物体通常不绘制阴影,而场景内遮蔽关系相对确定的静态物体的阴影通常是在建立模

Shadow Map阴影贴图技术之探

这两天勉勉强强把一个shadowmap的demo做出来了.参考资料多,苦头可不少.Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势.本篇是第一辑.--ZwqXin.comShadow Map的原理很简单,但是实现起来到处是雷.当然这只是我的体会.恩,不过就是"从光源处看场景,那些看不见的区域全部都该是阴影".很容易看出,与针对特定模型的Shadow Volume不同,Shadow Map是针对场景的.这就是说,对一个光源

工作积累之NDK编译STL (zhuan)

方法: 1.在jni目录下新建Application.mk; 加入 APP_STL :=  stlport_static  右边的值还可以换成下面几个: system - 使用默认最小的C++运行库,这样生成的应用体积小,内存占用小,但部分功能将无法支持 stlport_static - 使用STLport作为静态库,这项是Android开发网极力推荐的 stlport_shared - STLport 作为动态库,这个可能产生兼容性和部分低版本的Android固件,目前不推荐使用. gnust

阴影映射(Shadow Map)的研究(二)

阴影映射(Shadow Map)的研究(二) 上一篇文章介绍了我对Z缓存的较为详细的研究.这里之所以对Ze求导函数,是因为的我们需要寻找它的变化曲线,从而找到极值点,这样就能够确定Ze相对于zw的疏密分布情况.幸运的是,我们找到的导函数是双曲函数,并且我们关心的的右侧是单调递增的. 蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/44946763.欢迎同行前来探讨. 引出上一篇文章的结论,当 时,导函数取得最大值.但是在Zw∈

Shadow Map 实现极其细节

这里不介绍算法原理,只说说在实现过程中遇到的问题,以及背后的原因.开发环境:opengl 2.0  glsl 1.0. 第一个问题:产生深度纹理. 在opengl中每一次离屏渲染需要向opengl提供一个renderframe,一个renderframe包含一个texture和一个renderbuffer.texture是一个存储特定数据的内存区,可以存储颜色,深度以及模版.renderbuffer目前不太清楚. 具体代码如下: glGenFramebuffers(1, &frameBuff)