感谢onemoo http://bbs.gameres.com/showthread.asp?postid=939466
“为什么我把镜子的值设置为0的话,镜子前的茶壶就不会挡住镜子里的茶壶?...”
——你不是设置的镜子值,而是设置的镜子像素在模版缓存中相关位置的值。
“深度缓存清楚为1.0后,墙、镜子、原茶壶不是可以认为是在同一深度1.0了吗?...”
——深度缓存和模板缓存在逻辑上是两个东西,没什么直接关系。
深度缓存是根据深度值决定是否将像素写入后缓存,也就是前面的东西会遮挡后面的东西。
模版缓存其实是在后缓存中标记了一些位置,根据这些标记决定是否渲染像素到后缓存!!这是模版的实质!!
我给你描述一下整个流程:
首先在RenderScene()里,你渲染了镜子前的茶壶、地面、墙壁、镜子。对吧?这里没有问题
后面的void RenderMirror()是关键。
void RenderMirror() //看这里我的注释!
{
//
// Draw Mirror quad to stencil buffer ONLY. In this way
// only the stencil bits that correspond to the mirror will
// be on. Therefore, the reflected teapot can only be rendered
// where the stencil bits are turned on, and thus on the mirror
// only. //其实这里解释得很清楚:)
//
Device->SetRenderState(D3DRS_STENCILENABLE, true); //启用模版测试
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); //将测试设置成永远通过
Device->SetRenderState(D3DRS_STENCILREF, 0x1); //设置参考值为1
Device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff); //掩码
Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff); //写入掩码
Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP); //如果深度测试失败,不改变模版中的值
Device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); //没有用,因为我们的模版测试将永远成功
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE); //模板测试通过了,就将模版缓冲中的值替换成参考值(也就是1)
// disable writes to the depth and back buffers
Device->SetRenderState(D3DRS_ZWRITEENABLE, false); //不写入Z,就是这次操作中不改变Z值
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); //
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); //
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); // 这里实际上是不让像素写入后缓存!!
//必要的设置完成!
// draw the mirror to the stencil buffer
Device->SetStreamSource(0, VB, 0, sizeof(Vertex));
Device->SetFVF(Vertex::FVF);
Device->SetMaterial(&MirrorMtrl);
Device->SetTexture(0, MirrorTex);
D3DXMATRIX I;
D3DXMatrixIdentity(&I);
Device->SetTransform(D3DTS_WORLD, &I);
Device->DrawPrimitive(D3DPT_TRIANGLELIST, 18, 2);
//又渲染了一遍镜子,为什么呢?
并不是真的想渲染镜子的像素,因为根据前面的设置,镜子的像素并不会写入后缓存。
渲染中要是发现Z测试失败,那么不会改变模版值。注意:实际上那些被前面茶壶遮挡的那部分镜子就是“Z测试失败”,所以那些部分的模版值还是0!
因为设置了模版测试总是通过,所以镜子的其他未被遮挡的部分都会通过模版测试,都会被设置成参考值,也就是1!!
明白了吗,这里实际上将镜子的所有可视位置的模版值都标记为参考值1。这就是模版最重要的功能:标记!
// re-enable depth writes
Device->SetRenderState( D3DRS_ZWRITEENABLE, true ); //重启用Z写入,就是说可以改变Z值了。
// only draw reflected teapot to the pixels where the mirror
// was drawn to.
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); //模版中只有等于参考值的地方可以通过测试
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
// position reflection
D3DXMATRIX W, T, R;
D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
D3DXMatrixReflect(&R, &plane);
D3DXMatrixTranslation(&T,
TeapotPosition.x,
TeapotPosition.y,
TeapotPosition.z);
W = T * R; //以上设置了镜子后面的茶壶的相关矩阵
// clear depth buffer and blend the reflected teapot with the mirror
//Device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0); //清除Z缓存。不清除的话,后面的茶壶会被镜子挡住而渲染不出来的。
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO); //这是将镜子和茶壶的像素混合一下,使得茶壶看起来像是从镜子中看到的,而不是直接摆在镜子前的。
// Finally, draw the reflected teapot
Device->SetTransform(D3DTS_WORLD, &W);
Device->SetMaterial(&TeapotMtrl);
Device->SetTexture(0, 0);
Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
Teapot->DrawSubset(0);
//以上我们首先重设置了镜子后边的那个茶壶(因为茶壶是反着的,所以还要改变D3DRS_CULLMODE)
然后就渲染这个茶壶。因为只有模版测试通过,茶壶的像素才会写到后缓存上,也就是我们之前在模版上标记的那些镜子的可视位置上都会画上这个茶壶。
一切就完成了,下面的代码不过是将render state恢复正常。
// Restore render states.
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
Device->SetRenderState( D3DRS_STENCILENABLE, false);
Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
}
明白了么!?
下面说说你的问题。
如果你将参考值设置为0,那么你标记了什么呢?
我们在之前正确找到了镜子的可视部分,可你却标记为0,也就是什么也没标记(整个模版缓存都是0)。又因为模版中和参考值相同的地方都会渲染上,所以不仅镜子上会渲染出茶壶,前面的那个茶壶处也会渲染出来的。整个后缓存都可以渲染,连墙上也一样!
在这里设置为0就使得模板缓存失去意义了。
罗嗦了这么多,不知你明白了么?
仔细想想,深入理解模版缓存的功能。
再强调一遍:模版的实质就是标记!