XNA - 合并精灵以提高绘制性能?

4
我在某处读到过,当渲染大量3D对象时,可以将它们合并成一个巨大的网格,以便只进行一次绘制调用。这样就可以让“GPU发挥其魔力”,而“CPU可用于其他调用”而不是绘图。
因此,我的问题是,在考虑性能的情况下,是否可以在2D环境中执行此操作?
例如,假设我们有一个简单的瓷砖系统,而不是为视图中的每个瓷砖进行一次绘制调用,我们将所有瓷砖合并成一个大精灵,然后对其进行绘制调用。
任何关于此问题的见解-无论是提示、链接还是其他内容-都将不胜感激,因为我没有图形性能方面的先前经验。
编辑:抱歉我表达不清楚。我正在创建一个瓦片引擎供个人使用,并希望它尽可能多地实用。因此,我想进行优化,以防我不久的将来必须绘制大量瓦片。
我确实使用了tile sheet,但我的问题所指的是,如果将要从该sheet中绘制的所有瓦片合并到一个新的Texture2D中,是否会提高性能。例如:
我们需要在屏幕上绘制128x72个瓷砖。我们将所有瓷砖合并到一个大小为1280x720的新精灵中并进行绘制。这样,每帧只会调用一次draw()方法。我的问题是,这样做是否会提高性能,就像在3D时将3D对象合并成单个网格一样。
因为我收集到的信息是,调用draw()会影响性能,应尽可能少地调用它。有人可以证实或否认吗? :)
4个回答

11

我对XNA和3D没有任何经验,但是我可以给你一些有关2D游戏的建议。我在今年年初花了一些时间在XNA中创建一个tile引擎,并且也思考过同样的问题。我觉得简短的答案是:如果你关注性能,将你的tiles组合成一个更大的sprite是一个好主意。但是,如果你感兴趣的话,还有更长的答案。

通常情况下,当涉及到性能优化时,答案几乎总是“不要这么做”。如果你确定需要优化性能,接下来的答案几乎总是“暂时不要这么做”。最后,如果你尝试优化性能,最重要的事情是使用基准测试来收集更精确的性能度量值以便于在更改之前和之后进行比较。否则,你不知道是否成功了!

现在,与2D游戏相关的另一件事是,我发现我的tile引擎性能越好,切换纹理的次数越少。例如,假设我有一块草地tile和一块碎石tile。如果它们是内存中的两个单独的纹理,如果我先绘制一块草地tile,然后一块碎石tile,再绘制一块草地tile到屏幕上,GPU将会加载草地纹理,然后切换到加载碎石纹理,再切换回草地纹理来绘制另一块草地tile。这会很快降低性能!最简单的方法是使用sprite sheet,将你的草地和碎石tiles放入一个纹理中,并告诉SpriteBatch在同一纹理的不同区域绘制每个tile。

还有一件事要考虑,就是你要在屏幕上绘制多少个tile。我记不太清了,但我曾一次性绘制数千个tile(在缩小的视图中)。我注意到,当我使用更大的tile并且绘制数量较少时,也就是你在问题中提到的建议时,性能也得到了改善。尽管这种改进没有前面那段描述的那么明显,但我仍然建议你测量不同实现的性能变化。此外,如果你只绘制几十个或几百个tile,现在可能没有必要去优化它(参见第二段)。

为了证明我的说法,这里有Shawn Hargreaves关于纹理交换、sprite sheet等的一篇帖子链接。如果你搜索该主题,XNA论坛和Shawn Hargreaves的博客上可能会有更好的帖子。

http://forums.xna.com/forums/p/24254/131437.aspx#131437

更新:

既然你更新了你的问题,那我也来更新一下我的回答。我决定对一些示例进行基准测试,以便让你了解性能差异可能是什么样的。在我的Draw()函数中,我有以下内容:

        GraphicsDevice.Clear(Color.CornflowerBlue);

        Stopwatch sw = new Stopwatch();
        sw.Start();

        spriteBatch.Begin();

#if !DEBUG
        spriteBatch.Draw(tex, new Rectangle(0, 0, 
                         GraphicsDevice.Viewport.Width,
                         GraphicsDevice.Viewport.Height), 
                         Color.White);            
#else
        for (int i = 0; i < 128; i++)
            for (int j = 0; j < 72; j++)
            {
                Rectangle r = new Rectangle(i * 10, j * 10, 10, 10);
                spriteBatch.Draw(tex, r, r, Color.White);
            }
#endif
        spriteBatch.End();

        sw.Stop();

        if (draws > 60)
        {
            numTicks += sw.ElapsedTicks;
        }
        draws++;

        if (draws % 100 == 0)
            Console.WriteLine("avg ticks: " + numTicks / (draws - 60));

        base.Draw(gameTime);

在“#if !DEBUG”语句中删除感叹号,以在两种方法之间切换。我跳过了前60个绘制操作,因为它们包括一些初始设置(不太确定是什么),并且会使平均值产生偏差。我下载了一个1280x720的图像,对于顶部测试用例,我只绘制了一次。对于底部测试用例,我将一个源图像分成了大小为128x72的瓷砖,就像你在问题中提到的那样。以下是结果。

绘制一个图像:

avg ticks: 68566
avg ticks: 73668
avg ticks: 82659
avg ticks: 81654
avg ticks: 81104
avg ticks: 84664
avg ticks: 86626
avg ticks: 88211
avg ticks: 87677
avg ticks: 86694
avg ticks: 86713
avg ticks: 88116
avg ticks: 89380
avg ticks: 92158

绘制 128x72 像素的图块:

avg ticks: 7902592
avg ticks: 8052819
avg ticks: 8012696
avg ticks: 8008819
avg ticks: 7985545
avg ticks: 8028217
avg ticks: 8046837
avg ticks: 8291755
avg ticks: 8309384
avg ticks: 8336120
avg ticks: 8320260
avg ticks: 8322150
avg ticks: 8381845
avg ticks: 8364629

正如您所看到的,它们之间存在几个数量级的差异,因此这非常重要。测试这种类型的内容非常简单,我建议您为您特定的设置运行自己的基准测试,以考虑我可能忽略的一些因素。


有很多有用的信息,但我可能没有清楚地陈述我的问题。请更新我的原始问题。 - Robert Kaufmann
我添加了一些测试数据,以便您了解这两种不同方法的性能优势。 - Venesectrix
非常感谢你。我一定会自己进行一些基准测试,因为你的结果非常有趣。(直到现在我还不太清楚如何进行基准测试)。 - Robert Kaufmann

3

显卡不用频繁切换纹理性能就会更好。您可以在SpriteBatch.Begin()方法中按纹理进行排序,以便显卡尽可能少地切换纹理。

如果有意义的话,我会把我的瓷砖/精灵放入图集中。例如,一个字符可能在一个图集中,而一层瓷砖可能在同一个图集中。到目前为止,这种做法效果还不错。

我认为,在必须优化之前不要进行优化。如果你的游戏运行得足够快,为什么要费心进行优化呢?


嗯,我正在做一个瓷砖引擎(供个人使用),我希望它在任何时候都能尽可能地多才多艺,并且具有最佳的性能。至于表格,我已经在使用表格了,我的问题并不是关于那些表格的。然而,我之前提出的问题表述不够清楚,现在我已经更新了它。 - Robert Kaufmann

1

Rectangle r = new Rectangle(i * 10, j * 10, 10, 10);

创建许多新对象会调用垃圾回收,这可能会导致速度变慢!您不应该在大循环中分配新对象:


2
矩形是一个结构体,是值类型,因此不受GC的影响。一旦超出作用域,值类型就会从堆栈中取出。代码可以更加高效,但额外的混乱并不值得。此外,我认为你的评论应该作为实际评论发表在你所提到的答案下面,而不是作为答案。 - Robert Kaufmann

0

性能并非普世的必须品或上帝的命令;它只是达到目的的手段。如果你所拥有的已经足够好,那么提高其性能就没有任何实际意义。一味追求“始终保持最佳性能”是过早优化的定义,它会带来更多麻烦而不是预防。


我的原始问题与优化我编写的软件无关。它与是否有更好的绘制瓷砖的方法有关。调用Draw()非常昂贵!其次,我正在编写一个标题引擎,因此我需要考虑可扩展性。绘制20k个标题几乎比仅绘制一个标题更耗费性能。现在我知道有更好的方法,如果没有它,游戏今天看起来不会那么漂亮。因此,我从优化中获得了全部收益,抱歉。所以,是的,你的答案完全偏离了问题。 - Robert Kaufmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接