OpenGL › v3, эффективный рендеринг 2D-игр

Я запутался в VBO и современном openGL. Там прямой вопрос в конце этого поста, значит там их пачка на подходе. Если у вас есть какие-либо сведения об этом, я был бы признателен за ответ. И если вы ответите, пожалуйста, считайте меня полным идиотом, ничего не знающим.

Итак, моя история такова:

У меня есть игра, которая представляет собой 2D-игру сверху вниз. Я использовал режим immideate для рендеринга 2D-спрайтов. Фактические координаты текстур моих текстурных атласов были статическими и предопределенными в отдельном классе. Координаты четырехугольника были определены в каждом объекте и обновлялись по ходу игры. При рендеринге я просто связывал определенную текстуру с именем glBegin(triangles), а затем вызывал метод рендеринга каждого видимого объекта. Это, в свою очередь, отправило координаты квадрата и координаты текстуры моему классу Renderer, который сделал вызовы openGl. Затем я сбросил текстуру, которая вызывает только glEnd().

Я делал это для всех разных атласов и в том порядке, чтобы получилась нужная глубина.

Но времена действительно меняются. Я хочу перейти на использование VBO и шейдеров. Я пытался несколько раз в прошлом, но потерпел неудачу с треском. Есть просто несколько вещей, которые я не могу найти в Google, чтобы дать мне полное представление о нем и о том, как я могу использовать его для ускорения моей игры.

Я знаю основы. Вместо отправки всей информации по шине на графический процессор при каждом вызове рендеринга я могу просто сохранить все, что мне нужно, на этапе инициализации, а затем использовать шейдеры для расчета конечного результата. Но...

У меня есть идея для текстурных координат. Они будут статичными, так как никогда не изменятся. Имеет смысл хранить их на GPU. Но как мне узнать, какие координаты соответствуют каждому QUAD/TRIANGLE. Я думаю, что вместо четырех поплавков у каждого рендеримого объекта в игре может быть какой-то индекс, который он передает как атрибут вершинному шейдеру. вершинный шейдер использует индекс для поиска четырех координат текстуры в VBO. Это осуществимое решение? Как бы вы реализовали нечто подобное?

Но что касается четырехъядерных вершин, я потерялся. Они будут постоянно перемещаться. Они будут видны, затем исчезнут и т. д. Это означает, что мой четырехъядерный VBO будет меняться при каждом вызове рендеринга, а код, который я видел, который обновляет VBO, довольно уродлив. Я видел что-то вроде:

  • сохраните 4 четырехъядерных координаты в массиве.
  • создать floatbuffer, поместить их туда
  • управлять буфером.
  • отправить буфер в VBO

Мне кажется довольно дорого. И я не понимаю, как я могу удалить определенную запись (если объект перемещается за пределы экрана и т. д.) и как я могу манипулировать определенной записью (объект перемещается). И если мне придется обновлять VBO таким образом при каждом вызове рендеринга, каков прирост производительности? Для меня это больше похоже на потерю...

Кроме того, как я могу отслеживать «глубину» полученного изображения. Я делаю 2d, но под «глубиной» я подразумеваю порядок рендеринга, например. убедиться, что объект2 отображается поверх объекта1. Возможно, разные 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?


person Jake    schedule 19.04.2015    source источник


Ответы (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}
};

За исключением их значений, их расположение идентично: четыре двухэлементных атрибута подряд. Это тривиально позволяет использовать общий массив координат текстуры, соответствующий макету этих двух четырехугольников:

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

Я думаю, вам должно быть очевидно, как использовать вызовы непосредственного режима для рисования quad_pos_a и quad_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.

Так как же сэкономить на загрузке обновленных данных? Ну, это зависит от того, что вы считаете дорогим: в конечном итоге данные должны поступать на графический процессор. Таким образом, упаковка его в объединенный буфер передачи данных для загрузки, вероятно, выгодна, так как это экономит накладные расходы на каждый вызов, чтобы разделить каждый вызов glVertex.

person datenwolf    schedule 19.04.2015
comment
Это действительно хорошее разъяснение, спасибо. Просто несколько уточняющих вопросов: - person Jake; 19.04.2015
comment
Как мне эффективно изменить свой VBO. Я полагаю, мне следует использовать GL_STREAM_DRAW? Если я использую glBufferSubData, как узнать, какие вершины заменены, а какие нет? Как стереть весь шабанг и загрузить совершенно новый контент? - person Jake; 19.04.2015
comment
А насчет глубины. Будут ли квадроциклы отрисовываться в том же порядке, в котором я поставил их в VBO? - person Jake; 19.04.2015
comment
@Jake: вы можете полностью заменить VBO, просто вызвав glBufferData с новыми данными; обратите внимание, что это не окажет негативного влияния на текущие операции рендеринга; если OpenGL по-прежнему нужны некоторые данные, ранее содержащиеся в VBO, будет сохранена теневая копия (это называется осиротением). Примитивы визуализируются в порядке, указанном вызовом отрисовки. С glDrawArrays в порядке, в котором данные вершин находятся в VBO, с glDrawElements в порядке индексов в индексном буфере. - person datenwolf; 19.04.2015