如果我们能时时刻刻都在学习,从观察、聆听、注视和行动中学习,那么你会发现,学习是不断进展,永无过去 | 学习技术不是发财之道 | 学习技术,因为崇尚科学与真理
现在比较普遍的实现方法有三种:Z Pass、Z Fail 和DirectX Samples 里面介绍的那种(其实Z Fail只是Z Pass的改进版)。这里只介绍Z Pass的实现方法。如下图所示,有一个三角形abc和它投到地面的影子三角形def。从摄像机分别看A、B、C三处,明显只有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
//此时的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);
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的不足。