处理2D图像和纹理

问题

当使用SpriteBatch类不当,绘制大量图像时程序会变得很慢。

解决方案

教程3-1已经提到,每帧创建一个新的SpriteBatch类,或绘制每张图像时开启和关闭SpriteBatch类会极大地影响性能。但是还有更多微妙的东西需要考虑到。

工作原理
性能优化:Sprite Sorting Modes

SpriteBatch类的Begin方法让你可以设置SpriteSortMode。在讨论到不同模式前,你需要知道显卡喜欢的工作方式是什么,不喜欢的是什么。

在屏幕上绘制一张2D图像是通过绘制两个三角形并将它们填充为从一张纹理采样的颜色实现的。显卡喜欢一次性地绘制大量的三角形不要被打搅。但是当你需要改变纹理时,你就打搅了显卡。换句话说,从同一张纹理绘制100张图像比从100张纹理绘制100张图像快得多。所以,尽可能少地改变当前纹理可以改进性能。

有了这个概念,下面你可以学习可以作为SpriteBatch . Begin方法的一个参数指定的sprite排序模式了:

  • Deferred:这是默认的SpriteSortMode。虽然你几乎从不会用到它,因为它没有排序。 只要通过SpriteBatch . Draw方法将一个sprite添加到batch,这个sprite只是简单地添加到堆栈的顶部。当调用SpriteBatch . End方法时,堆栈中所有的sprite会根据进入堆栈的顺序进行绘制。
  • Texture:通常基于性能优化和易用性考虑,你会使用这个模式。当调用SpriteBatch . End方法时,在XNA实际要求显卡绘制图像前,堆栈中的所有图像据纹理进行排序。通过这种方式,显卡可以在一个轮次中使用同一张纹理绘制许多三角形,你可以减少干扰显卡改变纹理的次数。但是,当使用alpha透明时这个模式会遇到麻烦,你需要用到下面几个模式中的一个。
  • BackToFront:当使用alpha混合时,你想让距离最远的物体最先绘制。要知道为什么可见前一个教程。添加图像到SpriteBatch 类的SpriteBatch . Draw方法有一个重载方法可以接受layerDepth为参数。使用这个float参数,你可以指定图像在哪个层,1.0f表示最远的层(例如,包含草地),0.0f表示最近层(例如,在这个层绘制岩石)。你可以指定两者之间的任何值。当使用BackToFront模式时,只要调用SpriteBatch . End方法,在SpriteBatch中的图像就会进行排序让最远层最先被绘制。
  • FrontToBack:这个模式正好与前一个模式相反,所以最近层的图像最先被绘制。因为屏幕上所有被绘制的像素永远不会被覆盖(因为所有接下来的图像都在这个之后),这会导致最好的性能。但是,使用alpha混合无法工作(见前面的教程)并且只有在所有图像使用相同的纹理的情况下才会获得最好的性能!如果绘制最近层需要交换10次纹理,性能相比使用Texture模式会有更大的下降。
  • Immediate:相对于其他模式,在这个模式中XNA不会等待调用SpriteBatch . End方法绘制所有图像。只要你调用SpriteBatch. Draw方法将使用相同纹理的图像添加到SpriteBatch,SpriteBatch就会将它们推入堆栈中。当你使用另一个纹理绘制一个sprite时,当前堆栈中的sprite会立即被绘制。

使用前四个模式,只有在调用SpriteBatch . End方法时,sprite才会被排序,渲染状态被设置,三角形和纹理被传送到显卡。这让你可以使用多个SpriteBatch 类对象,以随机顺序添加新sprite,甚至是使用相同渲染状态的3D对象。但在使用Immediate模式时,渲染状态会在调用SpriteBatch. Begin方法时调用,sprite会在调用SpriteBatch . Draw方法之后被绘制。这意味着你可以在两个方法间改变渲染状态!通过这种方式,你可以访问更多的alpha混合模式或使用一个自定义的pixel shader 绘制sprite!但是使用Immediate模式绘制图像时,你无法在绘制其他东西,例如一个3D对象,因为这时你可能会改变渲染状态。当你想用SpriteBatch继续绘制sprite时,渲染状态还没有复位,因此你将使用3D物体的渲染状态和pixel shader 绘制sprite。

性能优化:在一个图像文件中排序多个图像

如前所述,当绘制大量sprite时你想尽可能少地改变当前纹理。SpriteSortingMode. Texture可以帮很多忙,但在一个完整的游戏中你往往需要几百张不同的图像,这会导致显卡在纹理间切换几百次。

一个有用的方法是将互相联系的图像存储在一张大图像中,如图3-4的左图所示。通过这种方式,你可以使用SpriteBatch . Draw 方法的sourceRectangle参数指定绘制大图像中的哪一部分,如教程3-3的代码所示。

如图3-4所示,每张子图像为40 × 40像素。教程3-3的代码定义了一些矩形表示在大图像中的位置。将这些矩形作为SpriteBatch . Draw方法的第三个参数导致在屏幕上只绘制这个子图像。

使用多个SpriteBatche

SpriteBatch是一个强大的对象,你可以将整个游戏的图像一次性地添加到一个单独的SpriteBatch中,进行排序并绘制。但是,有些情况中你可能还想使用多个SpriteBatch类。

例如创建一个空战游戏,飞机间互射导弹。当然你有多个种类的飞机和导弹。你可以将飞机和导弹的所有旋转情况存储在一张大纹理中,这可以减少所需纹理的数量。

你还想在飞机引擎的后部有烟雾,并会慢慢消散,在导弹轨迹上也有大量的烟雾。如果使用一个SpriteBatch绘制所有这些图像,会遇到一个问题:你想让所有图像根据纹理排序获得优化的性能。但是,飞机和导弹应该首先绘制,这样才能之后绘制烟雾sprite。而这需要 sprite根据深度排序!

解决方法是使用两个SpriteBatch类对象:planeBatch和smokeBatch。在Draw方法的开头,调用两个SpriteBatch类的Begin方法。然后,对每个飞机和导弹,将飞机和导弹图像添加到planeBatch中,飞机和导弹之后的烟雾图像添加到smokeBatch中。

planeBatch排序模式是Texture,smokeBatch使用BackToFront模式。当所有图像添加到batch之后,首先调用planeBatch的End方法,这个会让所有的飞机和导弹根据纹理排序,会以最优的性能绘制到屏幕。然后。调用smokeBatch的End方法,这会导致烟雾sprite很好地混合在图像上。

这样,你以优化的性能绘制了飞机,而且混合正确。

你可能感兴趣的:(处理)