使用OpenGL(现代方式)绘制大量图块


35

我正在与一群人一起开发基于图块/精灵的小型PC游戏,但我们遇到了性能问题。我上一次使用OpenGL是在2004年左右,所以我一直在教自己如何使用核心配置文件,但发现自己有些困惑。

我需要每帧在屏幕上绘制250-750个48x48瓦片附近,以及大约50个精灵。磁贴仅在加载新关卡时更改,并且精灵一直在更改。一些图块由四个24x24片组成,大多数(但不是全部)子画面与图块大小相同。许多图块和精灵使用alpha混合。

现在,我正在即时模式下进行所有操作,这是一个坏主意。一样,当我们的一个团队成员尝试运行它时,他的帧率非常差(〜20-30 fps),而且当有更多的图块时,情况会更糟,尤其是当这些图块中的很多都是被切成碎片。所有这些使我认为问题在于进行绘制调用的次数。

我已经想到了一些可能的解决方案,但是我想让一些知道他们在说什么的人来运行它们,所以我不会在愚蠢的事情上浪费时间:

标题:

  1. 加载关卡后,将所有图块一次绘制到连接到大喇叭声纹理的帧缓冲区中,然后每帧绘制一个带有该纹理的大矩形。
  2. 加载关卡时,将所有图块放入静态顶点缓冲区中,并以这种方式绘制它们。我不知道是否有一种方法可以通过一次调用glDrawElements来绘制具有不同纹理的对象,或者这是否是我想要做的事情。也许只是将所有瓦片都变成巨大的巨型纹理,然后在VBO中使用有趣的纹理坐标?

精神:

  1. 通过单独调用glDrawElements绘制每个精灵。这似乎涉及许多纹理切换,但我被告知是不好的。纹理数组在这里可能有用吗?
  2. 以某种方式使用动态VBO。与上面的数字2相同的纹理问题。
  3. 点精灵?这可能很愚蠢。

这些想法中有什么明智的吗?我可以看的地方有很好的实现吗?


如果图块既不移动也不变化,并且它们在整个级别上的外观相同,则应使用第一个想法-帧缓冲区。这将是最有效的。
zacharmarz 2012年

尝试使用纹理图集,这样就不必切换纹理,而其他所有东西都保持不变。现在他们的帧率如何?
user253751 '16

Answers:


25

渲染图块的最快方法是将顶点数据打包到带有索引的静态VBO中(如glDrawElements所示)。完全不必将其写入另一张图像,只需要更多的内存即可。纹理切换非常昂贵,因此您可能需要将所有图块打包到一个所谓的“ 纹理图集”中,并为VBO中的每个三角形提供正确的纹理坐标。基于此,根据您的硬件,渲染1000个甚至100000个图块应该不是问题。

Tile渲染和Sprite渲染之间的唯一区别可能是Sprite是动态的。因此,为了获得最佳但又容易实现的性能,您可以仅将精灵顶点的坐标放入流中,每帧绘制VBO并使用glDrawElements绘制。还将所有纹理打包在“纹理图集”中。如果您的子画面很少移动,您也可以尝试制作一个动态VBO并在子画面移动时对其进行更新,但这在这里完全是多余的,因为您只想渲染一些子画面。

您可以看一下我用OpenGL用C ++制作的一个小型原型:Particulate

我猜大概渲染了10000个点精灵,在普通机器(Quad Core @ 2.66GHz)上的平均fps为400。它具有CPU上限,这意味着图形卡可以渲染更多图像。请注意,我在这里不使用“纹理地图集”,因为我只有一个纹理用于​​粒子。使用GL_POINTS渲染粒子,然后着色器计算实际的四边形大小,但是我认为还有一个四边形渲染器。

哦,是的,除非您有一个正方形并使用着色器进行纹理映射,否则GL_POINTS非常愚蠢。;)


精灵会更改其位置和所使用的纹理,并且大多数精灵会在每一帧执行此操作。而且,精灵以及经常被创建和销毁的精灵。VBO可以处理流这些东西吗?
Nic 2012年

2
流绘制基本上意味着:“将这些数据发送到图形卡,并在绘制后将其丢弃”。因此,您必须在每个帧中再次发送数据,这意味着您渲染多少个精灵,它们具有什么位置,什么纹理坐标或哪种颜色都没有关系。但是,一次发送所有数据并让GPU处理它比立即模式快很多。
Marco Marco

这一切都说得通。为此使用索引缓冲区是否值得?唯一要重复的顶点是每个矩形的两个角,对吗?(我的理解是,索引是glDrawElements和glDrawArrays之间的区别。对吗?)
Nic

1
如果没有索引,则无法使用GL_TRIANGLES,这通常是不好的,因为这种绘制方法是保证最佳性能的一种方法。另外,在OpenGL 3.0中不推荐使用GL_QUADS实现(来源:stackoverflow.com/questions/6644099/…)。三角形是任何图形卡的固有网格。因此,您“多”使用2 * 6字节来保存2个顶点着色器执行和vertex_size * 2字节。因此,您通常可以说总是更好。
Marco Marco

2
与Particulate的链接已失效...您能提供一个新的链接吗?
SWdV

4

即使有如此多的绘制调用,您也不应该看到这种性能下降-即时模式可能会很慢,但并没有那么慢(作为参考,即使是亲爱的Quake也可以在不降低帧率的情况下管理每帧数千个即时模式调用如此糟糕)。

我怀疑这里还有更有趣的事情。您需要做的第一件事是花一些时间对程序进行性能分析,否则基于可能导致零性能提升的假设,您将面临重新配置的巨大风险。因此,即使是像GLIntercept这样的最基本的东西,也要运行它,然后看看时间在哪里。根据这些结果,您可以通过一些方法解决该问题。有关主要瓶颈所在的真实信息。


我做了一些分析,尽管很尴尬,因为性能问题不会在与开发相同的机器上发生。我对此问题有些怀疑,因为问题肯定会随着磁贴数量的增加而增加,并且磁贴实际上什么也没做,除非被绘制。
Nic 2012年

那状态变化呢?您是否按州对不透明图块进行分组?
Maximus Minimus 2012年

那是可能的。在我这方面确实值得更多关注。
Nic 2012年

2

好的,既然我的最后一个答案有点失控,那么新的方法也许会更有用。


关于2D性能

首先提供一些一般性建议:2D不需要当前的硬件,即使大部分未经优化的代码也可以使用。但是,这并不意味着您应该使用“中间模式”,至少要确保不要在不需要时更改状态(例如,当已经绑定相同的纹理时,不要使用glBindTexture绑定新纹理,如果对CPU进行检查是否为吨比glBindTexture调用要快得多),并且不要使用glVertex这样完全错误和愚蠢的东西(即使glDrawArrays也会更快,并且使用起来也不难,虽然不是很“现代”)。使用这两个非常简单的规则,帧时间应至少降至10ms(100 fps)。现在要提高速度,下一个逻辑步骤是批处理,例如将多个绘制调用捆绑成一个,为此,您应该考虑实现纹理图集,因此您可以最大程度地减少纹理绑定的数量,从而增加一次调用可以绘制的矩形的数量。如果您现在还没达到2ms(500fps)的水平,那您就做错了:)


平铺地图

实施图块地图的绘制代码是在灵活性和速度之间找到平衡。您可以使用静态VBO,但不能与动画图块一起使用,也可以仅在每个帧中生成顶点数据并应用我上面解释的规则,这非常灵活,但到目前为止还不那么快。

在我之前的回答中,我介绍了一个不同的模型,其中片段着色器负责整个纹理处理,但是有人指出,它需要依赖的纹理查找,因此可能不如其他方法那么快。(这个想法基本上是您只上传图块索引,并在片段着色器中计算纹理坐标,这意味着您可以只用一个矩形绘制整个地图)


精灵

除了“关于2D性能”部分中讨论的内容外,Sprite还具有很大的灵活性,因此很难进行优化。而且,除非您希望同时在屏幕上显示一万个精灵,否则可能不值得付出任何努力。


1
即使您有一万个精灵,现代硬件也应以适当的速度运行它:)
Marco Marco

@ API-Beast等待什么?如何在片段着色器中计算纹理UV?您是否应该将UV发送到片段着色器?
HgMerk

0

如果一切失败...

设置触发器绘图方法。一次只能更新其他所有精灵。虽然,即使使用VisualBasic6和简单的位盲方法,您也可以每帧主动绘制数千个精灵。也许您应该研究那些方法,因为仅绘制子画面的直接方法似乎失败了。(听起来更像是您在使用“渲染方法”,但尝试像“游戏方法”一样使用它。渲染的目的是清晰度,而不是速度。)

您可能会不断地不断重画整个屏幕。不仅仅是仅重绘更改的区域。那是很多开销。这个概念很简单,但不容易理解。

为原始静态背景使用缓冲区。除非屏幕上没有精灵,否则它永远不会渲染。这经常用于“还原”绘制精灵的位置,以在下一次调用中取消绘制精灵。您还需要一个缓冲区来“绘制”,而不是屏幕。在此处绘制,然后全部绘制,然后一次将其翻转到屏幕上。这应该是您所有精灵的一次屏幕呼叫。(与在屏幕上一次绘制每个精灵或一次尝试一次绘制所有精灵相反,这会使alpha混合失败。)写入内存速度很快,并且不需要屏幕时间即可“绘制” ”。每个绘图调用将在尝试再次绘图之前等待返回信号。(不是垂直同步,而是实际的硬件滴答,它比RAM的等待时间慢很多。)

我想这是您只能在一台计算机上看到此问题的部分原因。或者,它会退回到所有卡都不支持的ALPHA-BLEND的软件渲染。在尝试使用该功能之前,您是否检查过该功能是否受硬件支持?如果没有备用广告,您是否有备用广告(非alpha混合模式)?显然,您没有限制(混合的事物数量)的代码,因为我认为这会降低您的游戏内容。(不像这些仅仅是粒子效果,它们都是alpha混合的,因此,为什么程序员要限制它们,因为即使在硬件支持下,它们也会对大多数系统造成很大的负担。)

最后,我建议将您要进行alpha混合的内容限制为仅需要它的内容。如果一切都需要,您别无选择,只能要求您的用户具有更好的硬件要求,或者降低游戏的性能以获得所需的性能。


-1

像在其他2D游戏中一样,为对象创建一个精灵表,为地形创建一个图块集,而无需切换纹理。

渲染图块可能会很麻烦,因为每个三角形对都需要自己的纹理坐标。但是,有一个解决方案,称为实例渲染

只要您可以以某种方式对数据进行排序,例如,您可以获得草丛及其位置的列表,就可以通过一次绘制调用来渲染每个草丛,您所要做的就是提供一个数组每个图块的模型与世界矩阵的关系。即使是最简单的场景图,以这种方式对数据进行排序也不是问题。


-1:实例化比Beast先生的纯着色器解决方案差。渲染中等复杂度(约100个三角形)的对象时,实例化对性能的影响最大。每个需要纹理坐标的三角形块都不是问题。您只需创建带有一堆松散的四边形的网格,这些四边形恰好形成一个tilemap。
Nicol Bolas 2012年

1
@NicolBolas好吧,我将为了学习而留下答案
dreta 2012年

1
为了清楚起见,尼科尔·波拉斯(Nicol Bolas),您如何处理所有这些建议?Marco的抽奖内容?我可以在某处看到此实现吗?
Nic 2012年

@Nic:流式传输到缓冲区对象不是特别复杂的代码。但是,实际上,如果您只谈论50个恶意,那没什么。很有可能是由地形图引起的性能问题,因此为此切换到静态缓冲区可能就足够了。
Nicol Bolas 2012年

实际上,如果实例化能够按我们认为的那样工作,那将是最好的解决方案-但既然如此,将所有实例烘焙到单个静态vbo中是可行的方法。
加里·康帕
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.