Shadow Mapping

来源:互联网 发布:绝密淘宝小类目 编辑:程序博客网 时间:2024/05/17 07:53
 1、什么是Shadow Mapping?

      Shadow Mapping是由Lance Williams于1978年在一篇"Casting curved shadows on curved surfaces"的文章中提出的,这篇文章是Shadow Map技术之根源。其实原理很简单,如果光源和目标点之间的连线没有任何物体阻挡的话,则目标点没有在阴影中;如果有物体遮挡,则目标点处在阴影中。而Shadow Map,就是一张记录了每个象素处用于比较遮挡关系信息的Textur。

      产生这个ShadowTexture的方法很简单,以SpotLight为例,把3D Camera放到光源的位置,把DepthTest打开,渲染场景,在PixShader中把每个象素的深度信息或者光源和此象素距离信息写到 RenderTarget上,由于DepthTest是打开的,保证了最终写到RenderTarget上的均是物体上未处在阴影中的点的深度值,实质完全可以等效为最终的DepthBuffer。

    得到这个Show Map之后,如何最终生成阴影呢?在PixShader对每个pixel进行处理时,算出当前象素与灯当的距离Dc,与存在 Shdow Map中的引像素的值Dz进行比较,如果Dc > Dz,则在阴影中,反之则被灯光照亮。

2、Shadow Map之HLSL的实现

    在Direct SDk中有Shadow Map的Sample,下面的Shader和Sample里面空全一样,只是加了一些注释便于理解。

    (1)生成Shadow Map的VS和PS
  1. //-----------------------------------------------------------------------------
  2. // Vertex Shader: VertShadow
  3. void VertShadow( float4 Pos : POSITION,
  4.                 float3 Normal : NORMAL,
  5.                 out float4 oPos : POSITION,
  6.                 out float2 Depth : TEXCOORD0 )
  7. {
  8.     //从模型坐标系变换到观察坐标系
  9.     oPos = mul( Pos, g_mWorldView );
  10.   //进行投影变换
  11.   oPos = mul( oPos, g_mProj );
  12.   //把投影坐标系的ZW值赋给Depth,作为PixelShader中的输出,这里的Z还是齐次坐标,这里不直接输出Z/W,我的理解是让Z和W都在 Rasterizer中进行线性插
  13.   //值,这样可以增加最终生成的Shadow Map的精度。
  14.     Depth.xy = oPos.zw;
  15. }
  16. //-----------------------------------------------------------------------------
  17. // Pixel Shader: PixShadow
  18. void PixShadow( float2 Depth : TEXCOORD0,
  19.                 out float4 Color : COLOR )
  20. {
  21.     // 把 z / w的值作为Color值输出,写到RenderTarget上,此时的RT formate是D3DFMT_R32F
  22.   //把Z/W目的是把齐次坐标Z变换到三维空间的非齐次坐标,范围则是[-1,1]
  23.     Color = Depth.x / Depth.y;
  24. }
(2)用Shadow Map生成Shadow
  1. //-----------------------------------------------------------------------------
  2. // Vertex Shader: VertScene
  3. // Desc: Process vertex for scene
  4. //-----------------------------------------------------------------------------
  5. void VertScene( float4 iPos : POSITION,
  6.                 float3 iNormal : NORMAL,
  7.                 float2 iTex : TEXCOORD0,
  8.                 out float4 oPos : POSITION,
  9.                 out float2 Tex : TEXCOORD0,
  10.                 out float4 vPos : TEXCOORD1,
  11.                 out float3 vNormal : TEXCOORD2,
  12.                 out float4 vPosLight : TEXCOORD3 )
  13. {
  14.     vPos = mul( iPos, g_mWorldView );
  15.     oPos = mul( vPos, g_mProj );
  16.     vNormal = mul( iNormal, (float3x3)g_mWorldView );
  17.     Tex = iTex;
  18.     //把当前顶点位置变换到以光源为Camera的投影空间,
  19.     vPosLight = mul( vPos, g_mViewToLightProj );
  20. }
  21. //-----------------------------------------------------------------------------
  22. // Pixel Shader: PixScene
  23. // Desc: Process pixel (do per-pixel lighting) for enabled scene
  24. //-----------------------------------------------------------------------------
  25. float4 PixScene( float2 Tex : TEXCOORD0,
  26.                 float4 vPos : TEXCOORD1,
  27.                 float3 vNormal : TEXCOORD2,
  28.                 float4 vPosLight : TEXCOORD3 ) : COLOR
  29. {
  30.     float4 Diffuse;
  31.     // 计算光源到当前象素方向向量并单位化
  32.     float3 vLight = normalize( float3( vPos - g_vLightPos ) );
  33.     //  dot( vLight, g_vLightDir )为光源到当前象素方向向量和光的方向向量之间的夹角余旋值,由于是spotlight,因此必须要在spotlight可照射的范围内。因为角
  34.     //度越小余旋值越大,因此这里是大于
  35.     if( dot( vLight, g_vLightDir ) > g_fCosTheta )
  36.     {
  37.         // Pixel is in lit area. Find out if it's
  38.         // in shadow using 2x2 percentage closest filtering
  39.         //从投影空间坐标转化为纹理空间坐标,也就是找到投影空间中的点和纹理空间中的点的对应关系
  40.       //除以w,xy坐标便处在(-1,1)的范围内,乘0.5加0.5,则变换到了(0,1)的范围,因texture space的u,v坐标是(0,1)的
  41.         float2 ShadowTexC = 0.5 * vPosLight.xy / vPosLight.w + float2( 0.5, 0.5 );
  42.       //在投影坐标系中,Y轴是向上的,而在纹理空间中Y轴向下,因此要作以下处理
  43.         ShadowTexC.y = 1.0f - ShadowTexC.y;
  44.         // 在texel space中对应的象素坐标
  45.         float2 texelpos = SMAP_SIZE * ShadowTexC;
  46.      
  47.         // 取得小数部分       
  48.         float2 lerps = frac( texelpos );
  49.         //这里使用的是2x2 percentage closest filtering,因此是采的邻近的四个点,判断它们是否在阴影中,
  50.         float sourcevals[4];
  51.         sourcevals[0] = (tex2D( g_samShadow, ShadowTexC ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
  52.         sourcevals[1] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 0) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
  53.         sourcevals[2] = (tex2D( g_samShadow, ShadowTexC + float2(0, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
  54.         sourcevals[3] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
  55.      
  56.         // 用lerps
  57.         float LightAmount = lerp( lerp( sourcevals[0], sourcevals[1], lerps.x ),
  58.                                   lerp( sourcevals[2], sourcevals[3], lerps.x ),
  59.                                   lerps.y );
  60.         // 计算光照,如果完全在阴影中,则LightAmount为0,这里只计算了Diffuse color,没有高光
  61.         Diffuse = ( saturate( dot( -vLight, normalize( vNormal ) ) ) * LightAmount * ( 1 - g_vLightAmbient ) + g_vLightAmbient )
  62.                   * g_vMaterial;
  63.     } else
  64.     {
  65.         Diffuse = g_vLightAmbient * g_vMaterial;
  66.     }
  67.     return tex2D( g_samScene, Tex ) * Diffuse
  68. }

3、Shdow Map 的优缺点

    优点:简单,不需要知道场景中Object的Geometry,不需要Stencil Buffer,每个灯光只需多渲染一个Pass。

    缺点:当Shadow Map分辨率不够高时,或灯光与物体隔得很近时,在边缘处会产生Aliasing,锯齿,因此,很多改进shadow Map的算法都围绕着如何消除锯齿作文章。

4、Shadow Map的改进

    关于Shadow Map的改进,又出了很多的paper和技术,比如:Percentage Shadow map,  使用bloom filter对Shadow Map进行模糊处理.以及siggraph 2002 中Marc Stamminger和 George Drettakis提出的Perspective Shadow Map.以及Adaptive Shadow Map等等。

 

    阴影算法,在3D渲染中是很重要的一部分。阴影算法大致可以分为以下三类:基于ray tracing,基于shadow volume,基于shadow map(Z buffer).

    Ray tracing可以很自然地实现shadow,不需要特殊处理,但是ray tracing一般都用于离线渲染。Shadow Volume在实时渲染中也有应用,但是Shadow Volume依赖于geometry,而且Volume的生成是比较麻烦的事情。因此在实时渲染中,还是简单的Shadow map运用得最多,基于Shadow Map的论文也是层出不穷。

Shadow Map分为两个pass:

(1) 以灯光的位置作为视点,渲染整个场景,把深度值(Z值)写到一张texture(shadow map)中。

(2)以相机所在的位置作为视点,渲染整个场景,在PS中把每个象素P(x,y,z)转换到灯光所在的视空间中对应P`(x`,y`,z`),用(x`,y`)作为uv去采样shadow map中此点的z值Zmap,在与z`比较,如果z` > Zmap,此像素便在阴影中,如果z` < Zmap此像素便不在阴影中。

Shadow Map优点是简单,易于实现。但是Shadow map有alias(走样、锯齿)的问题。为了解决这个问题,很多人提出了很多改进的办法。

1、Percentage-Closer Filtering:

Percentage-Closer Filtering出自论文“Rendering Antialiased Shadows with Depth Maps”,NV的Sample 里面也有,该文采用”Percentage-closer filtering”的滤波方法来解决Shaodow map的走样问题。

其思想很简单,拿文章里的一个图为例,假如某像素转换到灯光视图空间中的Z值为49.8,把这个值与在Shadow Map中3X3的区域的Z值比较,如果49.8小于在Shadow map中对应的Z值,则记为0,表示不在阴影中,反之则记为1。这样得到了右边所示的3X3区域大小的9个值,在对这9个值取平均,得到0.55,以这个值作为在pixel shader中的阴影权值。此方法能够在一定程度上解决alias的问题,而且有软阴影效果。

2、Perspective Shadow Maps

  Perspective Shadow Maps来自论文:“Perspective Shadow Maps,SIGGRAPH 2002 by Stamminger and Drettakis”。

该论文把Shadow map的alias问题分为两类:Perspective alias和Project alias. Project alias是因为当灯光照射方向与物体表面夹角比较小时,使得多个pixel对应Shadow map中一个texel,产生alias问题,可以增大shadow map来解决此问题。Perspective alias产生的原因是因为透视透影会产生近大远小的效果,这使得近处的物体有可能多个pixel对应着Shadow map中一个texel, 产生alias问题。Perspective Shadow map就是用于解决这一问题。

Perspective Shadow Maps其实思想还是比较容易理解的。在生成shadow map时,首先将物体以及灯光变换到perspective space中,在perspective space中,整个空间是一个长方体,没有了近大远小的问题,在这个空间中,再以常规方法,以灯光作为视点,生成shadow map.

Perspective Shadow Maps有很多局限性,对光源的位置和类型都有要求,很多情况需要特殊处理,源文中列了一些需要特殊处理的情况。正因为这些限制,使得实现起来比较复杂。但是此论文开了解决Perspective alias的先河,有不少后续文章都是借鉴了此文思想。

Light Space Perspective Shadow Maps

该文出自论文:“Light Space Perspective Shadow Maps”,这篇论文是以Perspective Shadow Maps为基础的,是对其的改进。

“Light Space Perspective Shadow Maps”与Perspective Shadow maps的区别是,它在产生shadow map之前,不是先以Camera的View Frustrum作透视投影,而是在和灯光方向垂直的方向构建View Frustrum,以此View Frustrum把灯光和场景转换到Perspective space中,再计算Shadow map.这样的好处在于,平行光源转换后依然是平行光,点光源被转换成了平行光源,克服了Perspective Shadow Maps中的一些问题。

Parallel-Split Shadow Maps for Large-scale Virtual Environments

    该方法出自论文:” Parallel-Split Shadow Maps for Large-scale Virtual Environments”,

附件: PSSM.jpg

  如上图示,Parallel-Split Shadow Maps把View Frustrum按照Z的范围分成三个部份,再分别为这三个部分各自生成Shaodw Map。假如光源不是平行光,可先用Light space Shadow Map的方法转换到Light Space,此时光源便是平行光了。Parallex-Split Shadow Maps是关键在于如何对View Frustrum作合理的切分。

Variance Shadow Maps.

Variance Shadow Maps,来自William Donnelly和Andrew Lauritzen的“Variance Shadow Maps”。该方法利用概率论中的期望值、方差和切比雪夫不等式,实在是巧妙。

在前面的Percentage closer filter方法中,我们不能用纹理过滤方法(如高期滤波等)对Shadow Map进行预处理,因为预处理之后结果就会变得不正确,不能反映像素是否在阴影之中,因此只能采样一定范围取差值求平均。而Variance Shadow Maps就没有这个限制。它的方法其实很简单,分为如下几步:

(1)              像生成一般shadow map一样渲染,但是除了把每个像素的深度值d,以及深度的平主 d2记录下来。

(2)              利用一种虑波方法对Shadow map进行处理。

(3)              从相机处渲染场景,在PS中把每个象素P(x,y,z)转换到灯光所在的视空间中对应P`(x`,y`,z`),用(x`,y`)作为uv去采样shadow map中此点的z值和z2记为D和F,采样时可以采用硬件支持的Blinear或Trilinear和AF去采,此时:

期望值E(z) = D,E(z2)=F

那么方差就等于: variance = E(z2) - E(z)2 = F – D2

再根据切比雪夫不等式,计算出阴影参数即可,具体的公式可查阅论文或Nvida对应的Sample。(

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 物流代收货款一直拿不到钱怎么办 丰巢快递柜收不到验证码怎么办 拼多多三级惩罚下架3天怎么办 在万达买的衣服穿一次烂了怎么办 内蒙古对于没有地的农民改怎么办 微信号码重新注册后回零钱怎么办 安卓系统文件苹果手机打不开怎么办 课题必须发表论文吗?查重怎么办 学信网学籍绑定输错5次怎么办 大学学校图书馆借的书丢了怎么办 借阅机里的图书不显示书名怎么办 苹果6s锁屏密码忘了怎么办 父亲去世后妈将父亲存款带走怎么办 狗和别的狗打架腿瘸了怎么办 神经病砍人警察不积极处理怎么办 才买了车里面的气味很大怎么办 家里装修两年了很大的木味怎么办 装修一年的房子夏天味很大怎么办 死了怕下地狱活着又受煎熬怎么办 狗狗的疫苗证丢了怎么办 剃了毛的狗不睡觉怎么办 家里的小狗送人了孩子一直哭怎么办 半个月的小狗一天没拉屎怎么办 把狗狗的毛剃了怎么办 用了维a酸乳膏过敏怎么办 药水点痣留下的红印怎么办 小孩牙齿被虫子吃了个洞怎么办 一岁宝宝贫血值是84怎么办 荒岛求生手机版被困在石室里怎么办 工伤认定期间被厂里辞退工资怎么办 怀孕50天看恐怖片肚子阴痛怎么办 欧卡二进游戏就卡画面了怎么办 魅族手机的微信图标找不到了怎么办 金立手机:微信图标找不到怎么办? 鞋厂装跟机老是卡钉怎么办 苹果手机摔了一下开不了机怎么办 百度网盘解析的种子保存不了怎么办 下载了种子百度网盘解析不了怎么办 正畸复诊后吃饭一直会磨嘴怎么办 遇到儿子说话对母亲不满母亲怎么办 冰箱旧了怎么办教你创新冰箱翻新法