平面镜像的实现
在自然界中有许多物体的表面都非常光滑,可以像镜子一样反射周围的物体。本节介绍了如何在3D应用程序中模拟镜像效果。为简单起见,我们降低了任务难度,只在平面上实现镜像效果。例如,一辆光滑的汽车可以反射周围的物体;但是,车身是一个平滑曲面,而非平面。我们不选择这样的物体。我们将在光滑的大理石地板或挂在墙上的镜子中渲染物体的映像——换句话说,我们只实现平面上的镜像效果。
要在程序中实现镜像效果,必须解决两个问题。首先,我们必须知道如何在一个任意平面上反射物体,正确地绘制该物体的映像。其次,我们只能在镜子里面显示映像;也就是,我们必须以某种方式将一个表面“标记”为镜子,然后在渲染时只在镜子里面绘制物体映像。回顾图10.1,它最先引入了这一概念。
第一个问题可以很容易地通过解析几何来解决,具体请参见附录C。第二个问题可以通过模板缓冲区来解决。
1. 平面镜像实现原理及步骤
注意:当绘制映像时,我们还需要在镜子平面上反射光源。否则,映像中的光照会显得很不真实。
上图说明了要绘制一个物体的映像,我们必须在镜子平面上对它进行反射。不过,这会出现下图所示的问题。
即,物体映像(在本例中是头骨)会被渲染到镜子之外的区域(例如,墙面)。映像只应该显示在镜子里面。我们可以使用模板缓冲区来解决一问题,因为模板缓冲区可以阻止像素渲染到后台缓冲区的某些区域上。所以,我们可以使用模板缓冲区来控制头骨的映像,避免映像渲染到镜子之外的区域。下面给出了具体的实现步骤:
1.将地板、墙壁和头骨(不包括镜子)渲染到后台缓冲区。注意,这一步不修改模板缓冲区。
2.将模板缓冲区清为0。下图展示了此时的后台缓冲区和模板缓冲区。
(将场景渲染到后台缓冲区,并将模板缓冲区清为0(由浅灰色表示)。模板缓冲区上的黑色轮廓线用于说明后台缓冲区像素与模板缓冲区像素之间的对应关系——它们并不代表绘制在模板缓冲区上的任何数据。)
3.把镜子只渲染到模板缓冲区。我们可以通过设置以下混合状态禁止颜色写入到后台缓冲区中:
D3D11_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask = 0;
通过以下设置禁止写入到深度缓冲区中:
D3D11_DEPTH_STENCIL_DESC::DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
在将镜子绘制到模板缓冲时,我们将模板测试函数设为D3D11_COMPARISON_ALWAYS(始终成功),并指定当模板测试成功时将模板缓冲区元素替换(D3D11_STENCIL_OP_REPLACE)为1(StencilRef)。将StencilDepthFailOp设为D3D11_STENCIL_OP_KEEP,当深度测试失败时不更新模板缓冲区(如果头骨挡住了镜子的某一部分,那么就会发生这种情况)。由于我们只将镜子绘制到模板缓冲区,所以在模板缓冲区中只有镜子的可视区域对应的像素为1,而其他像素均为0。下图展示了更新后的模板缓冲区。实际上,我们是给镜子在模板缓冲区中的可视区域做了标记。
(把镜子渲染到模板缓冲区,这个操作的实际上是在模板缓冲区中标记了镜子的可视区域。实心黑色区域的模板元素值为1。注意,被盒子挡住的区域不会设为1,因为这一部分根本无法通过深度测试(盒子挡住了镜子前面的这一部分)。)
4.现在我们将头骨映像渲染到后台缓冲区和模板缓冲区。但是要记住,只有通过了模板测试的像素片段才能渲染到后台缓冲区中。这次,我们要将模板测试函数设为D3D11_COMPARISON_EQUAL,使模板元素为1时测试成功。通过一方式,头骨映像只会渲染到模板元素为1的区域中。由于在模板缓冲区中只有镜子的可视区域的模板元素为 1,所以头骨映像只会被渲染到镜子里面。
5.最后,我们将镜子绘制到后台缓冲区。但是,为了为了能显示镜子之后的头骨镜像,我们需要使用透明混合绘制镜子,如果不这样做,那么镜子就会挡住位于它后面的头骨镜像。要实现这个效果,我们只需定义一个镜子用的材质实例,将漫反射分量的alpha通道设置为0.5,这样镜子表示镜子是半透明的,然后就可以像9.5.4节那样绘制半透明的镜子了:
mMirrorMat.Ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
mMirrorMat.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.5f);
mMirrorMat.Specular = XMFLOAT4(0.4f, 0.4f, 0.4f, 16.0f);
上述设置给出以下混合方程:
C = 0.5•Csrc + 0.5•Cdst
假设我们已将头骨镜像的像素发送到后台缓冲区中,则50%的颜色来自于镜子(源),50%的颜色来自于头骨ÿ