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

时间:2015-04-19 07:00:23

标签: java opengl game-engine

我对VBO和现代openGL感到困惑。在这篇文章的最后有一个直接的问题,然后在那里有一捆它们。如果您对此有任何疑问,我会非常感谢您的回复。如果你回复,请把我视为一个完全白痴,不知何处。

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

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

我为所有不同的地图集和顺序做了这个,以便我得到适当的深度。

但时代确实发生了变化。我想转而使用VBO和着色器。我过去曾多次尝试,但失败了。我只能通过谷歌找到一些东西让我对它有一个完整的了解,以及如何使用它来加速我的游戏。

我知道基础知识。我可以简单地存储我在启动阶段需要的所有内容,然后使用着色器来计算最终结果,而不是通过总线将所有信息发送到每个渲染调用的gpu。但...

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

但至于四肢椎体我输了。这些将不断移动。它们将是可见的,然后消失,等等。这意味着我的四维VBO将在每次渲染调用时改变,而且我已经看到更新VBO的代码非常难看。我见过类似的东西:

  • 将4个四坐标存储在一个数组中。
  • 创建一个floatbuffer,将它们放在那里
  • 操纵缓冲区。
  • 将缓冲区发送到VBO

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

另外,我如何跟踪"深度"得到的图像。我做2d,但是"深度"我指的是渲染的顺序,例如确保object2呈现在object1的ontop上。每个深度的VBO可能不同?或者我应该使用z坐标这个和enbale深度的东西。后者不会给出一个表现吗?

还有2d因素。我非常尊重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的东西?

1 个答案:

答案 0 :(得分:3)

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

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

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

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从哪里获取顶点数据和然后告诉它要绘制多少顶点,或者给出一系列索引从阵列中挑选哪些顶点。

使用VAs看起来像这样:

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的监管之下 - 这完全是关于它的。如果您已经了解VOA,那么您就无法体验VBO。然而,与VA不同,您无法改变VBO'内容如同毫不费力。着色器的不同之处在于,不再预定义属性类型。而是存在通用顶点属性,使用glEnableVertexAttribArray(而不是glEnableClientState)和glVertexAttribPointer进行设置。

那么如何节省上传更新数据的开销呢?嗯,这取决于你认为昂贵的东西:数据最终必须转向GPU。因此,将其打包到合并缓冲区数据上传传输中可能是有益的,因为它可以节省每个调用开销,以便对每个glVertex调用进行分块。