当实现一个镜面,一个物体假如在一面镜子前那么它就会被反射。然而,我们不想测试空间一个物体是否在一面镜子前,要做它是非常复杂的。因此,为了简化事 情,我们总是反射物体并且无限制地渲染它。但是这样就有一个象本章开头的图8.1一样的问题。即,物体反射被渲染到了没有镜子的表面。我们能够用模板缓存 来解决这个问题,因为模板缓存允许我们阻止渲染在后缓存中的特定区域。因此,我们使用模板缓存来阻止渲染被反射的不在镜子里的茶壶。下面的步骤简要的说明 了怎样实现:
1、 正常渲染所有的场景——地板,墙,镜子和茶壶——不包含反射的茶壶。注意这一步没有修改模板缓存。
2、 清除模板缓存为0。图8.3显示了后台缓存和模板缓存。

3、 渲染只有镜子部分的图元到模板缓存中。设置模板测试总是成功,并且假如测试成功就指定模板缓存入口为1。我们仅仅渲染镜子,在模板缓存中的所有像素都将为 0,除了镜子部分为1以外。图8.4显示了更新以后的模板缓存。也就是说,我们在模板缓存中对镜子像素做了标记。

4、 现在我们渲染被反射的茶壶到后台缓存和模板缓存中。但是假如模板测试通过,我们就只渲染后台缓存。假如在模板缓存中的值为1,那么我们设置模板测试通过。 这样,茶壶就仅仅被渲染到模板缓存为1的地方了。因为只有镜子对应的模板缓存值为1,所以反射的茶壶就只能被渲染到镜子里。
8.2.3代码和解释
这个例子的相关代码在RenderMirror函数中,它首先渲染镜子图元到模板缓存,然后渲染那些能被渲染到镜子里的反射茶壶。我们现在一行一行的分析 RenderMirror函数的代码,并解释为什么要这么做。
假如你想使用8.2.2部分的步骤实现代码,注意我们从第3步开始,因为对模板缓存来说1和2步已经没有什么事做了。同样我们通过这个解释来讨论通过镜子 渲染的信息。
注意我们将分成几个部分来讨论它。
8.2.3.1第一部分
我们通过允许模板缓存和设置渲染状态来开始:
|
void RenderMirror() { Device->SetRenderState(D3DRS_STENCILENABLE, true); Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); Device->SetRenderState(D3DRS_STENCILREF, 0x1); 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); |
这是非常容易理解的。我们设置模板比较运算为D3DCMP_ALWAYS,这就是说让所有模板测试都通过。
假如深度测试失败了,我们指定D3DSTENCILOP_KEEP,它表明不更新模板缓存入口。即,我们保存当前值。这样做的原因是假如深度测试失败了, 那么就意味着像素被“遮挡”了。我们不想渲染被“遮挡”的反射像素。
同样假如模板测试失败了,我们也指定D3DSTENCILOP_KEEP。但是在这里这样做不是必须的,因为我们指定的是D3DCMP_ALWAYS,当 然这样的测试也就永远不会失败。然而,我们只改变比较运算的一位,那么设置模板失败渲染状态是必须的。我们现在就这样做。
假如深度测试和模板测试都通过了,我们就指定D3DSTENCILOP_REPLACE,更新模板缓存入口,设置模板参考值为0x1。
8.2.3.2第二部分
这下一步阻止渲染镜子代码,除了模板缓存。我们通过设置D3DRS_ZWRITEENABLE并指定为false来阻止写深度缓存。我们能够防止更新后台 缓存,混合和设置源混合要素为D3DBLEND_ZERO目的混合要素为D3DBLEND_ONE。将这些混合要素代入混合等式,我们得到后台缓存是不会 改变的:

|
// disable writes to the depth and back buffers Device->SetRenderState(D3DRS_ZWRITEENABLE, false); 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); // re-enable depth writes Device->SetRenderState(D3DRS_ZWRITEENABLE, true); |
8.2.3.3第三部分
在模板缓存中,符合镜子可视像素的为0x1,因此对已经渲染的镜子区域做记号。我们现在准备渲染被反射的茶壶。回忆一下,我们仅仅想渲染镜子范围内的反射 像素。我们现在可以很容易的做到了,因为在模板缓存中这些像素已经被做了记号。
我们设置下面的渲染状态:
|
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); |
用一个新的比较运算设置,我们进行下面的模板测试:
|
(ref & mask == (value & mask) (0x1 & 0xffffffff) == (value & 0xffffffff) (0x1)== (value & 0xffffffff) |
这说明了只有当value=0x1时模板测试才成功。因为在模板缓存中只有镜子相应位置的值才是0x1,若我们渲染这些地方那么测试将会成功。因此,被反 射的茶壶只会在镜子里绘制而不会在镜子以外的表面上绘制。
注意我们已经将渲染状态由D3DRS_STENCILPASS变为了D3DSTENCILOP_KEEP,简单的说就是假如测试通过那么就保存模板缓存的 值。因此,在下一步的渲染中,我们不改变模板缓存的值。我们仅仅使用模板缓存来对镜子相应位置的像素做标记。
8.2.3.4第四部分
RenderMirror函数的下一部分就是计算在场景中反射位置的矩阵:
|
// 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; |
注意我们首先确定没有反射的茶壶位置,然后就通过xy平面来反射。这种变换规则是通过矩阵相乘来指定的。
8.2.3.5第五部分
我们已经为渲染反射茶壶做好了准备。然而,假如我们现在就渲染它,它是不会被显示的。为什么呢?因为被反射的茶壶的深度比镜子的深度大,因此镜子的图元将 把被反射茶壶的图元弄模糊。为了避免这种情况,我们清除深度缓存:
|
Device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0); |
并不是所有问题都解决了。假如我们简单的清除深度缓存,被反射的茶壶会被绘制到镜子的前面,物体看起来就不对了。我们想做的是清除深度缓存并且要混合被反 射的茶壶和镜子。这样,被反射的茶壶看起来就象在镜子里了。我们能够通过下面的混合等式来混合被反射的茶壶和镜子:

因为原像素(sourcePixel)来自被反射的茶壶,目的像素(DestPixel)来自镜子,我们能够通过这个等式明白它们是怎么被混合到一起的。 我们有如下的代码:
|
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR); Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO); |
最后,我们准备绘制被反射的茶壶:
|
Device->SetTransform(D3DTS_WORLD, &W); Device->SetMaterial(&TeapotMtrl); Device->SetTexture(0, 0); Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW); Teapot->DrawSubset(0); |
回顾一下8.2.3.4部分的W,它能够正确的将被反射的茶壶变换到场景中恰当的位置。同样,我们也要改变背面拣选模式。必须这样做的原因是当一个物体被 反射以后,它的正面和背面将会被交换。因此为了改变这种情况,我们必须改变背面拣选模式。
|
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); Device->SetRenderState( D3DRS_STENCILENABLE, false); Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); } // end RenderMirror() |
本文详细阐述了如何利用模板缓存和渲染状态设置实现镜面反射效果的渲染技术,包括步骤、代码解析及关键概念解释。
3186

被折叠的 条评论
为什么被折叠?



