绘制多个移动物体

发布于 2024-12-29 09:12:15 字数 573 浏览 1 评论 0原文

我目前正在开发一款 iOS 游戏,长话短说,我需要绘制大量移动立方体 - 每帧最多大约 200 个。强调移动,因为是的,我确实在这个主题上搜索了几个小时,但尚未找到合适的解决方案来快速、高效地绘制多个对象,其中它们的位置更新每一帧。

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

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

我的代码由“立方体”的静态顶点数组和包含每个立方体的位置和颜色的数组组成。我的游戏逻辑循环更新数组中每个立方体的位置。目前,我正在循环访问立方体数组,为每个立方体调用 glTranslatef 和 glDrawArrays。据我所知,这是非常低效的,但是我完全不知道如何优化它。有什么想法吗?

(也许我不应该瞄准旧的、已停产的 iOS 设备,但考虑到我的代码效率极其低下,我认为无论我是否找到解决这个问题的方法,它都将有助于我未来的努力)

I'm currently working on an iOS game where, long story short, I need to draw a lot of moving cubes - approximate maximum of 200 per frame. Emphasis on moving because yes, I have indeed Googled away for hours on this topic and have yet to find a suitable solution for fast, efficient drawing of multiple objects where their position updates every frame.

Through my endless amounts of research on this subject most seem to mention VBOs, however I'm not sure this would suit my case where the position of every object changes every frame.

I'm using OpenGL 1 at the moment - I have working code and on generation 3/4+ devices (the ones which support OpenGL 2, ha) it runs at a reasonable framerate - however when testing on my (old, yes) 2nd-gen iPod touch, it is very sluggish and essentially unplayable.

My code comprises of a static array of vertices for a 'cube' and an array containing the position and colour of every cube. My game logic loop updates the position of every cube in the array. At the moment I'm looping through the cube array, calling glTranslatef and glDrawArrays for every cube. From what I've read this is very inefficient, however I'm completely confused as to how you would optimise it. Any ideas?

(maybe I shouldn't be aiming for old, discontinued iOS devices but given my belief that my code is incredibly inefficient, I figure it'll help my future endeavours regardless if I find a way to address this)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷…雨湿花 2025-01-05 09:12:15

对于这样简单的对象,我会制作一个大的 VBO,比如 200 个对象 * NrVerticesPerCube,将所有数据交错放置:顶点、法线、UV、顶点、法线、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 个顶点、3 个法线和2UV),并将第一个关键帧复制到缓冲区,您可以对初始对象位置执行相同的操作,或者只保留该位置并计算后者...

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

对于只进行 gltranslates 的操作来说,这会更简单,只需将 x、y、z 的值添加到每个立方体的每个顶点即可。

您可以像这样更新它:

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

将 vbo 缓冲区和 mapit 绑定到缓冲区变量。
计算你想要的临时变量的东西。

            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]
  };

所以这是交错的顶点,你先有顶点,然后是法线,然后是 UV,如果你不使用纹理,请用 UV 代替颜色。

最简单的方法是拥有一个包含所有对象的数组(如果所有对象的大小相同,则很容易)并在绘制后更新位置(而不是在 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 即可。

所有这些都是凭记忆写的,所以可能有龙:-)

希望有所帮助。

For such simple objects I would make one big VBO say 200 Objects * NrVerticesPerCube, put all the data interleaved Vertex,Normal,UV,Vertex,Normal,UV, etc.

I do something similar in a keyframe animation of a beaver in my game, I start with something like this:

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);

This creates my VBO buffer with the correct size (in this case 8 * sizeof(GLfloat) wich is 3 Verts, 3 Normals and 2UV), and copies the first keyframe to the buffer, you could do the same with you initial object positions, or just leave that and compute latter...

Then in each frame I do interpolation between 2 keyframes for each vertex of my beaver, and just make one draw call, this is very fast for the 4029 vertices my beaver has, and works at 60FPS on my iPhone 3G.

For you doing only gltranslates it would be even simpler, just add the values of x,y,z to each vertice of each cube.

You would update it like this:

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

Bind the vbo buffer and mapit to a buffer var.
Calculate the stuff you want on a temp var.

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

Copy it and update buffer to next object, repeat until all objects updated...
You could also do all the updates in a seperate array and copy the whole array, but then you would be copying extra info that usually doesn't change (normals and UV). Or you could not use interleaved data and copy that...

        glUnmapBufferOES(GL_ARRAY_BUFFER);

Unmap the VBO buffer

    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);

Setup your draw call, and draw it all...

If you need to rotate objects and not just gltranslate them, you will need to add some matrix multiplications along the way...

EDIT **

ok, making a gltranste by hand is actually very easy (rotation, etc is a bit trickier).

I'm using a an interleaved plane drawed using TRIANGLE_STRIP instead of triangles, but the principle is the same.

  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]
  };

So this is interleaved vertex, you got vertex then Normals then UV, if you're not using textures substitute UV for color.

The easiest way is to have an array with all the objects inside (made easy if all your objects are the same size) and make the position updates after draw (instead of in the middle of the opengl frame), better still make a seperate thread, create 2 VBOs update one of them while drawing from the other, something like this:

  • Thread 1 OpenGL DrawFrom VBO0
  • Thread 2 Game Updates, update positions on internal array and copy to VBO1, set Var saying VBO1 yes ready (so thread 1 only changes from drawing to VBO1 when all the updates are done).
  • Thread 1 OpenGL DrawFrom VBO1
  • Thread 2 Game update, same thing but update VBO0
  • continue with same logic

this is called double buffering and you use it to garanty stability, without this sometimes your game logic will be updating the VBO while the graphics card needs it and the graphics card will have to wait, resulting in lower FPS.

Anyway, back on topic

to make the equivalent to gltranslatef(10,20,30) just do:

 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);

Of course the update values doesn't have to be the same for all the objects, infact using a base array like this you can update all the info as you go along and just have the routine to copy it to VBO when needed.

All this was written from memory on the fly, so there maybe dragons :-)

Hope that helps.

还如梦归 2025-01-05 09:12:15

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

我不确定为什么你想将多维数据集分割成单独的数组,除非它使你的数据结构更加优雅/面向对象,但这是我首先要考虑改进的地方。

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

You could optimise quite a bit by sticking all the coords for all your cubes in a single array, and drawing it with a single glDrawArrays call.

I'm not sure why you'd want to split up the cubes into separate arrays, except maybe because it makes your data structure more elegant/object oriented, but that's the first place I'd look at making an improvement.

Dump the cube coordinates in one big array, and give each cube object an index into that array so that you can still keep your update logic fairly compartmentalised (as in, cube n owns the coordinates in the range x to y and is responsible for updating them, but when you actually draw the coordinates you run glDrawArrays directly on the centralised coord array instead of looping through the cube objects and rendering them individually).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文