绘制多个移动物体

时间:2012-01-28 12:38:15

标签: iphone ios opengl-es

我正在开发一款iOS游戏,长话短说,我需要绘制很多移动的立方体 - 每帧大约最多200个。强调移动,因为是的,我确实在这个主题上搜索了几个小时,并且还没有找到一个合适的解决方案来快速,有效地绘制多个对象,每个帧的位置都会更新。

通过我对这个主题的无数研究,大多数人似乎都提到了VBO,但是我不确定这是否适合我的情况,即每个对象的位置每一帧都会发生变化。

我目前正在使用OpenGL 1 - 我有工作代码和第3代/ 4 +设备(支持OpenGL 2,ha)的设备以合理的帧速率运行 - 但是在我的测试时(旧的,是的)第二代iPod touch,它非常缓慢,基本上无法播放。

我的代码包含“立方体”的静态顶点数组和包含每个立方体的位置和颜色的数组。我的游戏逻辑循环更新了数组中每个多维数据集的位置。目前我正在循环遍历立方体数组,为每个立方体调用glTranslatef和glDrawArrays。从我所看到的,这是非常低效的,但是我完全混淆了如何优化它。有什么想法吗?

(也许我不应该针对旧的,已停产的iOS设备,但鉴于我相信我的代码非常低效,我认为无论我是否找到解决此问题的方法,它都将有助于我未来的努力)

2 个答案:

答案 0 :(得分:0)

您可以通过将所有立方体的所有坐标粘贴在单个阵列中并使用单个glDrawArrays调用绘制它来进行优化。

我不确定你为什么要将多维数据集拆分成单独的数组,除非因为它使你的数据结构更加优雅/面向对象,但这是我第一个看到改进的地方。

将多维数据集坐标转储到一个大数组中,并为每个多维数据集对象提供一个索引到该数组中,这样您仍然可以使更新逻辑保持相当划分(例如,多维数据集n拥有x到y范围内的坐标,并且负责更新它们,但是当您实际绘制坐标时,您可以直接在集中式坐标数组上运行glDrawArrays,而不是循环遍历立方体对象并单独渲染它们。

答案 1 :(得分:0)

对于这样的简单对象,我会制作一个大的VBO说200个对象* NrVerticesPerCube,将所有数据交错为Vertex,Normal,UV,Vertex,Normal,UV等。

我在游戏中的海狸关键帧动画中做了类似的事情,我从这样的事情开始:

glGenBuffers(1, &vboObjects[vboGroupBeaver]);
glBindBuffer(GL_ARRAY_BUFFER, vboObjects[vboGroupBeaver]);
glBufferData(GL_ARRAY_BUFFER, beaverVerts*8*sizeof(GLfloat), 0, GL_STATIC_DRAW);
vbo_buffer = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES);
NSString *path;
path = [[NSBundle mainBundle] pathForResource:@"beaver01" ofType:@"bin"];
NSFileHandle *model = [NSFileHandle fileHandleForReadingAtPath:path];
float vertice[8];
int counter = 0;
while (read([model fileDescriptor], &vertice, 8*sizeof(float))) {
    memcpy(vbo_buffer, vertice, 8*sizeof(GLfloat));        // 0
    vbo_buffer += 8*sizeof(GLfloat);
    counter++;
}
glUnmapBufferOES(GL_ARRAY_BUFFER); 
glBindBuffer(GL_ARRAY_BUFFER, 0);

这会创建具有正确大小的VBO缓冲区(在这种情况下8 * sizeof(GLfloat),它是3 Verts,3 Normals和2UV),并将第一个关键帧复制到缓冲区,您可以对初始化做同样的事情对象位置,或者只是离开并计算后者......

然后在每个帧中,我为我的海狸的每个顶点在2个关键帧之间进行插值,并且只进行一次绘制调用,这对于我的海狸拥有的4029个顶点非常快,并且在我的iPhone 3G上以60FPS工作。

对于你只做gltranslate它会更简单,只需将x,y,z的值添加到每个立方体的每个顶点。

你会像这样更新它:

    glBindBuffer(GL_ARRAY_BUFFER, vboObjects[vboGroupBeaver]);
    GLvoid* vbo_buffer = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES);

将vbo缓冲区和mapit绑定到缓冲区var。 在temp var上计算你想要的东西。

            memcpy(vbo_buffer, currentVert, 6*sizeof(GLfloat));        // 0
            vbo_buffer += 8*sizeof(GLfloat);

将其复制并更新缓冲区到下一个对象,重复直到所有对象都更新... 您还可以在单​​独的数组中执行所有更新并复制整个数组,但随后您将复制通常不会更改的额外信息(法线和UV)。或者您无法使用交错数据并复制...

        glUnmapBufferOES(GL_ARRAY_BUFFER);

取消映射VBO缓冲区

    glVertexPointer(3, GL_FLOAT, 8*sizeof(GLfloat), (GLvoid*)((char*)NULL));
    glNormalPointer(GL_FLOAT, 8*sizeof(GLfloat), (GLvoid*)((char*)NULL+3*sizeof(GLfloat)));
    glTexCoordPointer(2, GL_FLOAT,8*sizeof(GLfloat), (GLvoid*)((char*)NULL+6*sizeof(GLfloat))); 
    glDrawArrays(GL_TRIANGLES, 0, beaverVerts);

设置您的绘制调用,并将其全部绘制出来......

如果您需要旋转对象而不仅仅是gltranslate它们,您将需要在此过程中添加一些矩阵乘法...

编辑**

好吧,手工制作gltranste实际上很容易(旋转等等有点棘手)。

我正在使用一个使用TRIANGLE_STRIP而不是三角形绘制的交错平面,但原理是相同的。

  float beltInter[] = {
0.0, 0.0, 0.0,             // vertices[0]
0.0, 0.0, 1.0,             // Normals [0]
6.0, 1.0,                  // UV [0]
0.0, 480, 0.0,             // vertices[1]
0.0, 0.0, 1.0,             // Normals [1]
0.0, 1.0,                  // UV      [1]
320.0, 0.0, 0.0,           // vertices[2]
0.0, 0.0, 1.0,             // Normals [2]
6.0, 0.0,                  // UV      [2]
320.0, 480, 0.0,       // vertices[3]
0.0, 0.0, 1.0,             // Normals [3]
0.0, 0.0                   // UV      [3]
  };

所以这是交错的顶点,你得到顶点然后是法线然后是紫外线,如果你没有使用纹理替换紫外线的颜色。

最简单的方法是让一个包含所有对象的数组(如果你的所有对象都是相同的大小就很容易)并在绘制后进行位置更新(而不是在opengl框架的中间),更好的是一个单独的线程,创建2个VBO更新其中一个,同时从另一个绘图,如下所示:

  • 线程1 OpenGL DrawFrom VBO0
  • 线程2游戏更新,更新内部阵列上的位置并复制到VBO1,设置Var说VBO1是准备就绪(因此线程1仅在完成所有更新后从绘图更改为VBO1。)
  • 线程1 OpenGL DrawFrom VBO1
  • 线程2游戏更新,同样的事情,但更新VBO0
  • 继续使用相同的逻辑

这称为双缓冲,你使用它来保证稳定性,没有这个,有时你的游戏逻辑将更新VBO,而显卡需要它,显卡将不得不等待,导致较低的FPS。

无论如何,回到主题

使gltranslatef(10,20,30)等效于:

 int   maxvertices = 4;
 float x = 10;
 float y = 20;
 float z = 30;
 int   counter = 0;
 int   stride  = 8;                // stride is 8 = 3 x vertice + 3 x normal + 2 x UV change to 3 x color or 4 x color depending on your needs
 glBindBuffer(GL_ARRAY_BUFFER, vboObjects[myObjects]);
 GLvoid* vbo_buffer = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES);
 while (counter < (maxVertices*8)) {
      beltInter[counter]   += x;   // just sum the corresponding values to each
      beltInter[counter+1] += y;  
      beltInter[counter+2] += z;
      memcpy(vbo_buffer, currentVert, 3*sizeof(GLfloat));  // again only copy what you need, in this case only copying the vertices, if your're updating all the data, you can just do a single memcpy at the end instead of these partial ones
      vbo_buffer += stride*sizeof(GLfloat);     // forward the buffer
      counter += stride;         // only update the vertex, but you could update everything
 }
 glUnmapBufferOES(GL_ARRAY_BUFFER);

 glVertexPointer(3, GL_FLOAT, stride*sizeof(GLfloat), (GLvoid*)((char*)NULL));
 glNormalPointer(GL_FLOAT, stride*sizeof(GLfloat), (GLvoid*)((char*)NULL+3*sizeof(GLfloat)));
 glTexCoordPointer(2, GL_FLOAT,stride*sizeof(GLfloat), (GLvoid*)((char*)NULL+6*sizeof(GLfloat)));   
 glDrawArrays(GL_TRIANGLE_STRIP, 0, maxVertices);

当然,对于所有对象,更新值不必相同,实际上使用这样的基本数组,您可以随时更新所有信息,并且只需要在需要时将其复制到VBO

这一切都是从内存中写的,所以可能是龙: - )

希望有所帮助。