问题
在前面的教程中,你经常可以在Draw方法中看到以下代码:
device.RenderState.CullMode = CullMode.None;
如果你移除这行代码并将相机移动到三角形的后面,它们会消失!当你在没有理解backface culling时定义三角形顶点,会有50%可能三角形无法显示。
解决方案
你定义三角形顶点的方式告知XNA你想让哪一面朝向相机。
当绘制一个实心对象时,人们可以清楚地说出三角形的哪个面在物体的内部还是外部。要绘制必须的三角形,你可以要求XNA只绘制朝向相机的那个面,其他三角形在物体的内部被前面隐藏!
但是对计算机来说事情不是那么简单。对你定义的每个三角形,你需要说明三角形是在外面还是里面,这可以通过沿逆时针方向或顺时针方向定义三角形的顶点做到这点。
工作原理
当定义顶点时,你需要考虑相机的位置。要绘制三角形,顶点的顺序必须以相机看来按顺时针方向递增。
你可以想象一条从相机出发指向三角形中心的射线,如图5-9所示。如果顶点的顺序(从相机看过来!)是绕着这个中心点顺时针旋转的(左图),XNA就认为这个三角形是面向相机的,这样,三角形才会被绘制。
图5-9 位于三角形前方(左图)和后方(右图)的相机
如果你将相机放在同一个三角形的另一侧(如图5-9右图所示),顶点的顺序(同样是从相机看过来)是逆时针方向,XNA会认为这个三角形没有面向相机而不绘制它。
图5-10显示了相同的情况,但这次是从相机的角度观察,显然在右图中相机看到的是逆时针顺序。
图5-10 顺时针定义的三角形会被绘制(左图);而逆时针定义的会被剔除。
为什么需要背面剔除?
只有一个三角形的例子可以说明原理但无法说明使用背面剔除的好处。本教程的前面曾经提到,在绘制实心对象时背面剔除很有用,例如一个模型或地形。
让我们讨论一个立方体的例子。当你旋转一个立方体时,在任何时刻你只能看到三个面。这意味着绘制另外三个面对显卡的处理能力来说是一种浪费,没有朝向相机的三角形没必要绘制。
想象一下拿着一个立方体并只能看到一个面如图5-11的左边所示。然后看一下右图,前表面和后表面的各一个三角形加粗显示。
数字代表顶点的顺序。由12个三角形组成的六个面,需要定义36个顶点。图5-11中的数字表示哪个顶点定义了两个三角形。
注意:如果你使用索引,立方体只需要8个独立顶点,但仍需要36个索引才能绘制从8个顶点绘制12个三角形。那么图5-11中的数字代表哪个索引定义两个三角形。
图5-11 立方体:前表面和后表面的两个三角形
图5-12的左图显示了一根从相机出发的射线并与两个三角形相交,圆点表示射线和前面的三角形的交点。当你沿着前面的三角形的顶点增加的顺序(从0到1到2)前进时,会发现绕着圆点做顺时针旋转,这时XNA会绘制这个三角形。
现在看一下射线与后面的三角形的交点,当你沿着顶点增加的顺序(从6到7到8)前进时,会绕逆时针旋转,这时XNA会将这个三角形剔除,这样处理很好,因为这个三角形位于立方体的后方,被前表面隐藏了。
图5-12的右图显示了同样的立方体,但旋转了180度,这样前表面和后表面就互换了位置,相机仍位于页面的同一侧。现在如果顺着顶点6到7到8,将绕视线做顺时针旋转,这次,显卡会绘制这个三角形并剔除另一个!这正是我们想要的结果。
通过这种方式,显卡知道应该剔除哪些三角形,剔除那些不朝向相机的三角形可以极大地提高程序的性能,这也是默认激活的。
图5-12 从眼睛发出的射线与两个三角形的交点
技巧:教程5-7有一个圆柱体的例子,确保剔除是开启的,试着将相机移动到圆柱体上方,看一下结果(译者注:看不到圆柱体的上表面)!
如何关闭剔除
虽然当绘制由实心表面组成的对象时使用剔除可以带来极大地好处,但有时你也需要将提出关闭。例如,你想创建一个只由两个三角形组成的长方形墙,从外部看效果不错,但当你进入到建筑物内部,这面墙会被剔除,导致你可以看穿这堵墙!
要解决这个问题,你可以在墙的反面定义两个额外的三角形,这样无论从那一面观察这堵墙,总有两个三角形被绘制,另两个被剔除。更简单的方法是使用下列代码将剔除关闭:
device.RenderState.CullMode = CullMode.None;
别忘了在绘制墙后再将剔除打开。
技巧:当设计特别是调试程序时,你可以将剔除关闭,这样做可以排除物体没有被绘制的一个可能性。
代码
示例代码定义了一个沿顺时针和逆时针方向的立方体,当运行逆时针版本时,你将看到XNA只绘制了那些无需被绘制的三角形。
当运行顺时针版本时,试着将相机移动到立方体内部。