【问题标题】:OpenGl > v3, 2d game efficient renderingOpenGL > v3,2d 游戏高效渲染
【发布时间】:2015-04-19 18:55:53
【问题描述】:

我对 VBO 和现代 openGL 感到困惑。这篇文章的末尾有一个直接的问题,然后在途中有一堆问题。如果您对此有任何了解,我将不胜感激。如果你回复,请把我当成一个完全白痴,什么都不知道。

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

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

我为所有不同的图集按顺序执行此操作,以便获得适当的深度。

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

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

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

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

  • 将 4 个四边形坐标存储在一个数组中。
  • 创建一个浮动缓冲区,将它们放入其中
  • 操作缓冲区。
  • 将缓冲区发送到 VBO

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

另外,我如何跟踪结果图像的“深度”。我正在做 2d,但“深度”是指渲染的顺序,例如确保 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 的东西?

【问题讨论】:

    标签: java opengl game-engine


    【解决方案1】:

    第一个也是最重要的关键见解是,顶点不仅仅是位置。顶点是用于在调用 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 调用。

    【讨论】:

    • 这是一个非常好的澄清,谢谢。只是一些后续问题:
    • 如何有效地改变我的 VBO。我想我应该使用 GL_STREAM_DRAW?如果我使用 glBufferSubData,我怎么知道哪些 Vertecies 被替换,哪些没有?如何删除整个shabang并上传全新的内容?
    • 还有,深度呢。四边形的绘制顺序是否与我将它们放入 VBO 中的顺序相同?
    • @Jake:您可以通过调用 glBufferData 来完全替换 VBO 与新数据;请注意,这不会对正在进行的渲染操作产生负面影响;如果 OpenGL 仍然需要一些以前包含在 VBO 中的数据,则会保留一个影子副本(这称为孤立)。图元按照绘制调用指定的顺序呈现。 glDrawArrays 按照顶点数据在 VBO 中的顺序,glDrawElements 按照索引缓冲区中的索引顺序。
    猜你喜欢
    • 1970-01-01
    • 2014-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多