【问题标题】:How do I draw 1000+ particles (w/ unique rotation, scale, and alpha) in iPhone OpenGL ES particle system without slowing down the game?如何在 iPhone OpenGL ES 粒子系统中绘制 1000 多个粒子(带有独特的旋转、缩放和 alpha)而不减慢游戏速度?
【发布时间】:2011-09-15 17:36:29
【问题描述】:

我正在使用 OpenGL ES 1.1 为 iPhone 开发游戏。在这个游戏中,我有角色在被射击时会发出的血颗粒,因此屏幕上随时可以有1000多个血颗粒。问题是当我要渲染超过 500 个粒子时,游戏的帧率会大幅下降。

目前,每个粒子都使用 glDrawArrays(..) 进行渲染,我知道这是导致速度变慢的原因。所有粒子共享同一个纹理图集。

那么,减少绘制许多粒子的速度的最佳选择是什么?以下是我找到的选项:

  1. 将所有血液颗粒组合在一起并使用单个 glDrawArrays(..) 调用来渲染它们 --如果我使用这种方法,是否有办法让每个颗粒都有自己的自己的旋转和阿尔法?或者当使用这种方法时,它们是否都必须具有相同的旋转?如果我无法渲染具有唯一旋转的粒子,则无法使用此选项。
  2. 在 OpenGL ES 2.0 中使用点精灵。我还没有使用 OpenGL ES 2.0,因为我需要在我设定的截止日期之前在 App Store 上发布我的游戏。要使用 OpenGL ES 需要进行初步研究,遗憾的是我没有时间进行。我将在以后的版本中升级到 OpenGL ES 2.0,但首先我只想使用 1.1。

这是每个粒子自己渲染的。这是我最初的粒子渲染方法,它导致游戏在渲染 500 多个粒子后帧速率显着下降。

// original method: each particle renders itself.
// slow when many particles must be rendered

[[AtlasLibrary sharedAtlasLibrary] ensureContainingTextureAtlasIsBoundInOpenGLES:self.containingAtlasKey];

glPushMatrix();

// translate
glTranslatef(translation.x, translation.y, translation.z);

// rotate
glRotatef(rotation.x, 1, 0, 0);
glRotatef(rotation.y, 0, 1, 0);
glRotatef(rotation.z, 0, 0, 1);

// scale
glScalef(scale.x, scale.y, scale.z);

// alpha
glColor4f(1.0, 1.0, 1.0, alpha);

// load vertices
glVertexPointer(2, GL_FLOAT, 0, texturedQuad.vertices);
glEnableClientState(GL_VERTEX_ARRAY);

// load uv coordinates for texture
glTexCoordPointer(2, GL_FLOAT, 0, texturedQuad.textureCoords);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

// render
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glPopMatrix();

然后我使用了方法 1,但是使用这种方法(我知道),粒子不能具有唯一的旋转、缩放或 alpha。

    // this is method 1: group all particles and call glDrawArrays(..) once

    // declare vertex and uv-coordinate arrays
    int numParticles = 2000;
    CGFloat *vertices = (CGFloat *) malloc(2 * 6 * numParticles * sizeof(CGFloat));
    CGFloat *uvCoordinates = (CGFloat *) malloc (2 * 6 * numParticles * sizeof(CGFloat));

    ...build vertex arrays based on particle vertices and uv-coordinates.
    ...this part works fine.


    // get ready to render the particles
    glPushMatrix();
    glLoadIdentity();

    // if the particles' texture atlas is not already bound in OpenGL ES, then bind it
    [[AtlasLibrary sharedAtlasLibrary] ensureContainingTextureAtlasIsBoundInOpenGLES:((Particle *)[particles objectAtIndex:0]).containingAtlasKey];

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);

    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glTexCoordPointer(2, GL_FLOAT, 0, uvCoordinates);

    // render
    glDrawArrays(GL_TRIANGLES, 0, vertexIndex);

    glPopMatrix();

我会重申我的问题:
如何渲染 1000 多个粒子而不会大幅降低帧速率,并且每个粒子仍然可以具有唯一的旋转、alpha 和缩放?

任何建设性的建议都会非常有帮助,我们将不胜感激!

谢谢!

【问题讨论】:

  • FWIW,你也可以在 ES 1.1 上使用点精灵。该功能不限于 2.0。

标签: objective-c ios opengl-es particle-system


【解决方案1】:

使用大约 1-10 个纹理,每个纹理由透明背景上的 200 个红血点组成,然后每个纹理绘制大约 3-10 次。然后你有你的数千个点。您以球形图案等方式绘制所有图像 - 分层爆炸。

在玩游戏时,您不能总是与现实进行一对一的对应。仔细看看一些在旧 Xbox 或 iPad 等上运行的游戏——你需要做一些捷径——完成后它们通常看起来很棒。

【讨论】:

  • 我明白这一点。但是,我遇到的问题与我调用 glDrawArrays 的次数有关。我在游戏的一次渲染中调用该方法的次数越多,帧速率就越慢。因此,我需要一种更快地绘制每个粒子的方法。它们已经在地图集中。听起来您所描述的是将粒子聚集在一起以分组行动。然而,问题在于粒子不会相互组合。他们大多以独立的性质行事。所以我需要粒子保持彼此之间的独立性。
  • 很好的输入,不过!其他人有什么建议吗?
  • 您的意思是说,镜头中发出的血液颗粒非常重要,必须单独计算和渲染?您需要查看其他游戏中的爆炸等情况,并回答“我可以用多少个多边形来绘制该场景?”。
  • 如果你有一个有一百个红点的纹理,然后在它从撞击中移出时扩展它,它看起来像“独立点”。如果您的引擎已经有 1000 个这样的粒子,那么看起来您也需要重做引擎。例如,请参阅youtube.com/watch?v=DY4hDZ1HjIs
  • +1 表示汤姆的答案。我认为您可能对 iOS 设备上的 OpenGL ES 1 要求过高,您可能不得不接受一些变通方法。
【解决方案2】:

每个 OpenGL ES API 调用都会产生大量开销,因此您会在此处看到数百次通过该绘图循环时速度变慢也就不足为奇了。将您带到这里的不仅仅是 glDrawArrays(),还有各个 glTranslatef()、glRotatef()、glScalef() 和 glColorf() 调用。由于延迟渲染在这些 GPU 上的工作方式,glDrawArrays() 可能看起来是热点,但其他调用也会对您造成伤害。

您应该将这些粒子顶点组合在一个数组中(最好是 VBO,以便您可以更有效地利用流式更新数据到 GPU)。您绝对可以在组合的顶点数组中复制单个旋转、缩放等的效果,但是您将需要计算顶点在旋转、缩放等时的位置。这将放置每一帧都会给 CPU 带来一些负担,但可以通过使用 Accelerate 框架对此进行一些矢量处理来抵消一些负担。

也可以在数组中为每个顶点提供颜色和 alpha,因此您可以为每个粒子控制它。

但是,我认为您是对的,OpenGL ES 2.0 可以通过让您编写自定义着色器程序来为此提供更好的解决方案。您可以在 VBO 中为所有点发送静态顶点,然后只需要更新矩阵来操纵每个粒子和每个粒子顶点的 alpha 值。我做了类似的事情来生成程序冒名顶替者作为球体的替身。我描述了这个过程here,你可以下载源代码到应用程序here

【讨论】:

  • 这是非常好的反馈,布拉德。感谢您抽出宝贵时间提供如此出色的答案。我尝试注释掉 glDrawArrays() 代码,并没有放慢速度。我想知道这是否是因为我对 glDrawArrays() 在调用数百次时花费了这么多时间是正确的,或者只是因为 OpenGL ES 知道没有调用 glDrawArrays(),所以它优化并避免了 glScalef() 的开销、glRotatef() 和 glTranslatef() 方法。无论如何,对于第一个版本,我可能会使用现有的,甚至尝试分组策略。
  • 布拉德,您能否提供一个示例来说明您提到的概念?
  • @TrueLifeCoder - 你指的是哪些概念?我链接到上面的 Molecules 应用程序(源代码可用)使用 VBO 和预先计算的 OpenGL ES 1.1 端中的原子位置,以及用于置换在原子中心传入的顶点和光栅化的自定义顶点和片段着色器内容。虽然比您在此处查找的内容更复杂,但基本概念是相同的。
  • 其实我明白你的意思,现在我已经有时间考虑了。非常感谢!现在,我正在尝试 VBO 方法,但我很难画出任何东西。你能帮帮我吗?我的问题在这里描述link
  • 您说,“您绝对可以在组合顶点数组中复制单个旋转、缩放等的效果,但是您需要执行顶点应该在哪里的计算因为它们被旋转、缩放等。”你能举个例子说明我如何做到这一点吗?考虑到我目前的框架,这对我来说似乎是最简单的选择。谢谢!
猜你喜欢
  • 1970-01-01
  • 2012-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多