OpenGl > v3,2d游戏高效渲染

OpenGl > v3, 2d game efficient rendering

我对 VBO 和现代 openGL 感到困惑。在这个post的最后有一个直接的问题,然后那里有一堆。如果您对此有任何了解,我将不胜感激。如果你回答,请把我当作一个完全白痴,没有任何知识。

所以,我的历史是这样的:

我有一个游戏,它是一个自上而下的 2d 游戏。我使用即时模式来渲染 2d 精灵。我的纹理图集的实际纹理坐标是静态的,并在单独的 class 中预定义。四边形坐标在每个实体中定义,并随着游戏的进行而更新。当我渲染时,我简单地绑定一个特定的纹理,称为 glBegin(triangles),然后调用每个可见对象的渲染方法。这反过来将四边形坐标和纹理坐标发送到我的渲染器 class,它进行了 openGl 调用。然后我刷新了纹理,它只调用了 glEnd()。

我为所有不同的地图集做了这个,并按顺序这样做,这样我就得到了合适的深度。

但时代确实变了。我想转而使用 VBO 和着色器。我过去曾尝试过几次,但都以失败告终。我在 google 上找不到一些东西可以让我完全了解它,以及如何使用它来加快我的游戏速度。

我知道基础知识。无需在每次渲染调用时通过总线将所有信息发送到 gpu,我可以简单地存储初始化阶段所需的所有信息,然后使用着色器计算最终结果。但是...

我对纹理坐标有了想法。这些将是静态的,因为它们永远不会改变。将它们存储在 GPU 上是有意义的。但是我怎么知道每个QUAD/TRIANGLE对应的是哪个坐标呢?我在想游戏中的每个可渲染对象都可以有某种索引,而不是四个浮点数,它作为属性传递给顶点着色器。顶点着色器使用索引在 VBO 中查找四个纹理坐标。这是一个可行的解决方案吗?你会如何实现这样的东西?

但是至于四边形我迷路了。这些将不断移动。它们将是可见的,然后消失,等等。这意味着我的四边形 VBO 将在每次渲染调用时发生变化,而且我看到的更新 VBO 的代码非常难看。我见过这样的东西:

我觉得很贵。而且我不明白如何删除某个条目(如果实体移出屏幕等),也不明白如何操作某个条目(实体移动)。如果每次渲染调用都必须以这种方式更新 VBO,性能提升是多少?对我来说更像是一种损失...

此外,我怎样才能跟踪结果图像的 "depth"。我正在做 2d,但是 "depth" 我的意思是渲染顺序,例如确保 object2 呈现在 object1 之上。也许每个深度都有不同的 VBO?或者我应该为此使用 z 坐标并启用深度信息。后者不会带来性能上的冲击吗?

还有二维因素。我非常尊重 3d,但我想使用 2d 并利用它在理论上应该产生更好性能的事实。但是,从我收集到的信息来看,情况似乎并非如此。在 opengl 3+ 中似乎为了让我渲染 2d 的东西,我需要先将它转换为 3d,因为这是硬件中的进程。对我来说似乎很奇怪,因为屏幕上的最终结果是 2d。有没有办法规避这一点,并为 GPU 节省 2d -> 3d -> 2d 的工作?

换句话说,我怎样才能有效地改变这个:

class main{

void main(){
while(true){
Renderer.bind();
//call render in all gameObjects
Renderer.flush();
}
}
}

class GameObject{

private float X1, X2, Y1, Y2;
private TexureCoordinate tex;

render(float dt){
//update X1, X2...
Renderer.render(tex.getX1(), tex.getX2()... X1, X2 ...);
}

}

class Renderer{

//called once
void bind(Texture texture){
    texture.bind();
    glBegin(GL_TRIANGLES)

}

//called "nr of visable objects" times
void render(texX1, texX2, texY1, texY2, quadX1, quadX2, quadY1, quadY2){

glTexCoo2d(texX1, texY1)
....
etc.
....
}

void flush(){
glEnd();
}
}

使用现代 openGl 的东西?

第一个也是最重要的关键见解是,顶点不仅仅是位置。顶点是用于在调用 glVertex 之前在即时模式绘图调用中预设的整个属性元组。如果您只更改其中一个属性,您最终会得到一个非常不同的顶点。

让我们暂时从 VBO 退后一步,让整个 glBuffer[Sub]Data 不受影响,看看普通的旧客户端顶点数组(大约与即时模式一样长)。

假设您有两个位置数组,它们具有完全相同的布局,但具有不同的值:

GLfloat quad_pos_a[2][4] = {
  {1,2}, {2,2}, {2,3}, {1,3}
};

GLfloat quad_pos_b[2][4] = {
  {5,5}, {10,5}, {10,20}, {5,20}
};

除了它们的值之外,它们的布局是相同的:四个连续的 2 元素属性。这简单地允许使用公共纹理坐标数组,匹配这两个四边形的布局:

GLfloat quad_texc[2][4] = {
  {0,0},{1,0},{1,1},{0,1}
};

我想你应该很清楚,如何使用即时模式调用来绘制 quad_pos_aquad_pos_b 共享 quad_texc。如果不是很明显,现在是解决问题的时候了。这个回答很耐心,会等你完成…


中场休息


…因为将几何数据放入数组是一件显而易见的事情,OpenGL 很快引入了一个名为 顶点数组 的概念:你可以告诉 OpenGL 从哪里获取来自的顶点数据,然后告诉它要绘制多少个顶点,或者在给定索引列表的情况下从数组中挑选哪些顶点。

使用 VA 如下所示:

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glVertexPointer(
  2 /* = number of elements per attribute */,
  GL_FLOAT /* type of attribute elements */,
  0 /* = the byte distance between attributes OR zero if tightly packed */,
  quad_pos_a );
glTexCoordPointer(
  2 /* = number of elements per attribute */,
  GL_FLOAT /* type of attribute elements */,
  0 /* = the byte distance between attributes OR zero if tightly packed */,
  quad_texc );

glDrawArrays(
  GL_QUADS /* what to draw */,
  0 /* which index to start with */,
  4 /* how many vertices to process*/ );

或者如果您只想绘制第 0、1 和 3 个顶点的三角形:

GLushort indices[] = {0,1,3};
glDrawElements(
  GL_TRIANGLES /* what */,
  3 /* how many */,
  GL_UNSIGNED_SHORT /* type of index elements */,
  indices );

现在,普通旧顶点数组和 VBO 之间的主要区别在于,VBO 将数据置于 OpenGL 的监管之下——仅此而已。如果您了解 VA,您就会了解 VBO。然而,与 VA 不同的是,您不能毫不费力地更改 VBO 的内容。与着色器的不同之处在于,不再预定义属性的种类。相反,有通用的顶点属性,设置为 glEnableVertexAttribArray(而不是 glEnableClientState)和 glVertexAttribPointer.

那么如何节省上传更新数据的开销呢?好吧,这取决于您认为什么是昂贵的:数据最终必须进入 GPU。因此,将其打包到合并的缓冲区数据上传传输中可能是有益的,因为它节省了每次调用开销以分块每个 glVertex 调用。