Z Pass 实现 Shadow Volume

Posted on 2010-07-05 00:34 Herbert 阅读(2338) 评论(2)  编辑 收藏 引用 所属分类: DirectX
      折腾了好几天,终于弄明白Shadow Volume 的实现方法。当我看到那个黑黑的影子的时候,心里激动不已,毕竟这对我这个菜鸟来说是一个突破。附上一个简陋的Demo截图。
  

       现在比较普遍的实现方法有三种:Z PassZ Fail DirectX Samples 里面介绍的那种(其实Z Fail只是Z Pass的改进版)。这里只介绍Z Pass的实现方法。如下图所示,有一个三角形abc和它投到地面的影子三角形def。从摄像机分别看ABC三处,明显只有B是处在三角形的阴影中。

    

 

       那么如何通过程序来判断一个点是否在阴影中呢?在多面体abcdef中,从视点看A点时,视线没有经过多面体;从视点看B点时,视线经过多面体正对视点的一个面abde;从视点看C点,视线首先经过多面体正对视点的面abde,然后经过背对视点的面bcef,然后才到达C点。由此可得,当视线只经过阴影多面体正对面而没经过多面体背对面时,所看到的点就在阴影中。当场景中有多个阴影体时,我们可以认为当视点经过阴影多面体正对面次数大于经过背对面次数时,看到的点就在阴影中。

       在程序中实现的时候,需要做以下几步操作:

1) 为有阴影的模型生成它的阴影多面体。

遍历模型的每一个面,如果该面是正对光源的,就把它的三个边添加到一个列表中。

如果发现某一个边已经在列表中,则不再添加该边,并且把列表中的该边剔除。当遍历完整个模型的所有三角形后,列表中剩下的边就是阴影的边缘。

2) 关闭光照,渲染一次场景,生成一个具有场景深度信息的表面。

3) 对阴影多面体的正对面进行深度测试,如果测试通过,则模版缓存加1

4) 对阴影多面体的背对面进行深度测试,如果测试通过,则模版缓存减1,这时我们获得了一个模版缓存 stencil_1

5) 打开光照,根据所得的模版缓存stencil_1,再次渲染场景。如果stencil_1的值跟这一次渲染场景的模版值相等,则渲染场景;否则,保留表面原色(阴影)。

这样,阴影就渲染出来了。第二步到第五步都涉及到深度测试和模版测试的问题,如果对这两种测试不太熟悉的话,实现起来是一件很头痛的事情。下面附上一段代码,用以说明从第二步到第五步的深度测试和模版测试。

 

 

g_pDev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB( 0, 66, 75, 121), 1.0f, 0);

     g_pDev->BeginScene();

     //关闭光源并渲染场景

     g_pDev->SetRenderState(D3DRS_LIGHTING, TRUE);

     g_pDev->LightEnable(0, FALSE);  

     RenderScene();

     //把模版缓存设为0

     g_pDev->Clear(0, NULL, D3DCLEAR_STENCIL, D3DCOLOR_ARGB( 0, 66, 75, 121), 1.0f, 0);

     //备份渲染设置

     DWORD CullMode, AlphaBlendEnable, SrcBlend, DestBlend, ZFunc, StencilRef, StencilMask, StencilWriteMask, StencilFunc, StencilZFail, StencilFail, StencilPass;

     g_pDev->GetRenderState(D3DRS_CULLMODE, &CullMode);

     g_pDev->GetRenderState(D3DRS_ALPHABLENDENABLE, &AlphaBlendEnable);

     g_pDev->GetRenderState(D3DRS_SRCBLEND, &SrcBlend);

     g_pDev->GetRenderState(D3DRS_DESTBLEND, &DestBlend);

     g_pDev->GetRenderState(D3DRS_ZFUNC, &ZFunc);

     g_pDev->GetRenderState(D3DRS_STENCILREF, &StencilRef);

     g_pDev->GetRenderState(D3DRS_STENCILMASK, &StencilMask);

     g_pDev->GetRenderState(D3DRS_STENCILWRITEMASK, &StencilWriteMask);

     g_pDev->GetRenderState(D3DRS_STENCILFUNC, &StencilFunc);

     g_pDev->GetRenderState(D3DRS_STENCILZFAIL, &StencilZFail);

     g_pDev->GetRenderState(D3DRS_STENCILFAIL, &StencilFail);

     g_pDev->GetRenderState(D3DRS_STENCILPASS, &StencilPass);

 

     //设置第一轮z pass 渲染

     g_pDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); //渲染逆时针三角形(即正对摄像机的三角形)

     g_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);

     g_pDev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); //使阴影网格全透明

     g_pDev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

     g_pDev->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); //禁止阴影网格改变场景深度

     g_pDev->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESS); //与D3DRS_STENCILPASS 一起使用(实现z pass 时,stencil + 1 或stencil - 1)

     g_pDev->SetRenderState(D3DRS_STENCILENABLE, TRUE); //开启stencil test

     g_pDev->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); //stencil test总是成功

     g_pDev->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR); //stencil test 成功并且z test 成功时,stencil + 1

     RenderShadow();

     //设置第二轮z pass 渲染

     g_pDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW); //渲染顺时针三角形(即背对摄像机的三角形)

     g_pDev->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_DECR); //stencil test 成功并且z test 成功时,stencil - 1

     RenderShadow();

     //此时的stencil 缓存值,与原来相比,真正阴影部分的stencil + 1

     //还原渲染设置

     g_pDev->SetRenderState(D3DRS_CULLMODE, CullMode);

     g_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE, AlphaBlendEnable);

     g_pDev->SetRenderState(D3DRS_SRCBLEND, SrcBlend);

     g_pDev->SetRenderState(D3DRS_DESTBLEND, DestBlend);

     g_pDev->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);

     g_pDev->SetRenderState(D3DRS_ZFUNC, ZFunc);

 

     //设置stencil test 方法,使得再次渲染场景时,只渲染stencil value 没被改变的部分

     g_pDev->SetRenderState(D3DRS_STENCILENABLE, TRUE);

     g_pDev->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); //与D3DRS_STENCILREF、D3DRS_STENCILMASK 和D3DRS_STENCILWRITEMASK一起使用

     g_pDev->SetRenderState(D3DRS_STENCILREF, 0x0); // 当符合(ref & mask) == (stencil & mask) 条件时(即stencil 没被改变),渲染场景

     g_pDev->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);

     g_pDev->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);

 

     //打开光源并渲染场景

     g_pDev->SetLight(0, &g_Light);

     g_pDev->LightEnable(0, TRUE);   

     RenderScene();

     //还原渲染设置

     g_pDev->SetRenderState(D3DRS_CULLMODE, CullMode);

     g_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE, AlphaBlendEnable);

     g_pDev->SetRenderState(D3DRS_SRCBLEND, SrcBlend);

     g_pDev->SetRenderState(D3DRS_DESTBLEND, DestBlend);

     g_pDev->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);

     g_pDev->SetRenderState(D3DRS_ZFUNC, ZFunc);

     g_pDev->SetRenderState(D3DRS_STENCILENABLE, FALSE);

     g_pDev->SetRenderState(D3DRS_STENCILREF, StencilRef);

     g_pDev->SetRenderState(D3DRS_STENCILMASK, StencilMask);

     g_pDev->SetRenderState(D3DRS_STENCILWRITEMASK, StencilWriteMask);

     g_pDev->SetRenderState(D3DRS_STENCILFUNC, StencilFunc);

     g_pDev->SetRenderState(D3DRS_STENCILZFAIL, StencilZFail);

     g_pDev->SetRenderState(D3DRS_STENCILFAIL, StencilFail);

     g_pDev->SetRenderState(D3DRS_STENCILPASS, StencilPass);

g_pDev->EndScene();

     

然而,Z Pass 并不是一个完美的解决方案。比如,当摄像机处在一个阴影里面的时候,我们就无法用Z Pass来实现了。Z Fail的出现就是为了弥补Z Pass的不足。

Feedback

# re: Z Pass 实现 Shadow Volume  回复  更多评论   

2010-07-05 12:48 by chaogu
好像还有一种方法,好像叫做模板缓冲?

# re: Z Pass 实现 Shadow Volume[未登录]  回复  更多评论   

2010-07-16 14:11 by zz
g_pDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); //渲染逆时针三角形(即正对摄像机的三角形)
LZ确定是这样的?我记得这句是把逆时针三角形cull掉,渲染的是正面顺时针的三角形。directx默认顺时针的三角形是正面。

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理