Войти
ПрограммированиеФорумОбщее

OpenGL самый старт. C++. VBO, VAO. Как правильно обрабатывать N объектов.

Страницы: 1 2 Следующая »
#0
22:59, 10 авг. 2017

Доброго времени суток!

Сразу говорю, что я новичок в разработке игр. Практиковался с движком Unity, но сейчас захотелось сделать что-то своё. Выбрал OpenGL. На момент написания темы версия OpenGL - 4.5.0. Язык C++.
Читал несколько туториалов, но ни в одном не мог найти достаточно качественного объяснения самых основ, а именно VBO и VAO для рисования большого количества объектов. Кроме того, в этих туториалах основной упор делается на рисование статического треугольника. Очевидно, что в реальной игре скорее всего большая часть объектов будет менять координаты достаточно часто.

Но начнём с простого. Я работаю в 2D. Предположим, у меня 10+ объектов, у которых очень часто меняются координаты. Пусть для простоты это даже будут треугольники.
Пусть класс треугольника описывается следующим кодом:

class MyTriangle
{
public:
  MyTriangle(const std::vector<GLfloat> &verticles):
    m_verticles(verticles)
  {
    glGenVertexArrays(1, &m_vertexArrayID);
    glBindVertexArray(m_vertexArrayID);
    glGenBuffers(1, &m_vertexBufferID);
    glBindVertexArray(0);
  }

  ~MyTriangle()
  {
    glDeleteBuffers(1, &m_vertexBufferID);
    glDeleteVertexArrays(1, &m_vertexArrayID);
  }

  GLuint getVAO()
  {
    return m_vertexArrayID;
  }

  GLuint getVBO()
  {
    //Всякий раз получая VBO, обновляем его координаты (на случай, если они были изменены где-то во вне)
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * m_verticles.size(), &m_verticles[0], GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    return m_vertexBufferID;
  }

private:
  std::vector<GLfloat> m_verticles;
  GLuint m_vertexArrayID;
  GLuint m_vertexBufferID;
};

Структура класса крайне плохая (хотя бы из-за того, что такой деструктор не позволяет его копировать), но для примера сойдёт.

Заполняю вектор треугольников:

  std::vector<MyTriangle> triangles;

  triangles.emplace_back(std::vector<GLfloat>({
    -0.5f, -0.7f, 0.0f,
     0.5f, -1.0f, 0.0f,
     0.0f,  1.0f, 0.0f,
  }));

  triangles.emplace_back(std::vector<GLfloat>({
    -0.6f, -0.2f, 0.0f,
     0.3f, -0.8f, 0.0f,
     0.4f,  0.7f, 0.0f,
  }));

// ....

Отрисовываю:

  // Для управления окном использую glfw (не принципиально)
  while(!glfwWindowShouldClose(window))
  {
    glClear( GL_COLOR_BUFFER_BIT );

    for (MyTriangle& tr: triangles)
    {
      GLuint vao = tr.getVAO();
      glBindVertexArray(vao);

      GLuint vbo = tr.getVBO();

      // programID - ID программы шэйдеров
      glUseProgram(programID);

      glEnableVertexAttribArray(0);
      glBindBuffer(GL_ARRAY_BUFFER, vbo);
      glVertexAttribPointer(
        0,
        3,
        GL_FLOAT,
        GL_FALSE,
        0,
        (void*)0
      );

      glDrawArrays(GL_TRIANGLES, 0, 3);

      glDisableVertexAttribArray(0);
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      glBindVertexArray(0);
    }

    glfwSwapBuffers(window);
    glfwPollEvents();
  }

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

Заранее спасибо!


#1
5:39, 11 авг. 2017

Обычно по одному треугольнику не рисуют. Делают обычно так:
1. Либо для каждого объекта грузят сетку из треугольников (Mesh) в отдельный буфер и потом рисуют каждый буфер.
2. Либо создают один огромный буфер и льют в него те треугольники, которые надо отрисовать. Рисуют этот буфер.

Ну и создавать класс для треугольника - это имхо перебор.

#2
7:06, 11 авг. 2017

Спасибо за ответ!

Обычно по одному треугольнику не рисуют.
Ну и создавать класс для треугольника - это имхо перебор.

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

Либо создают один огромный буфер и льют в него те треугольники, которые надо отрисовать. Рисуют этот буфер.

То есть грузят все координаты, а потом отрисовывают каждую тройку, указывая соответствующий сдвиг?

Либо для каждого объекта грузят сетку из треугольников (Mesh) в отдельный буфер и потом рисуют каждый буфер.

Спасибо, почитаю про это. Но что-то мне подсказывает, что это чисто треугольная тема. Если у меня 2d прямоугольники, то фиг знает, подойдёт ли это.

#3
12:24, 11 авг. 2017
То есть грузят все координаты, а потом отрисовывают каждую тройку, указывая соответствующий сдвиг?

Ну в общем... скорее да. Можно смещения не указывать, а разруливать всё в шейдерах.
Если у меня 2d прямоугольники, то фиг знает, подойдёт ли это.

Любой прямоугольник это 2 треугольника.
Если надо рисовать просто 2д прямоугольники, то есть такая штука как инстансинг, либо вариант с большим буфером.
Вообще лучше погугли как рисуют. Про сортировку по материалам например.
#4
18:03, 11 авг. 2017

Лучше делать свой vao для каждого обьекта и записывать в его vbo каждый экземпляр, тогра доступ к отрисовке конкретного экземпляра удобно оргонизовать по его id используемого в качестве индекса смещения обьекта в vbo, т.е. у вас 100 уникальных обьектов у каждого свой vao c vbo, и с помощью менеджера добавляющего каждому экземпляру обьекта порядковый id и хранящий инфу о экземплярах (т.е. хранящий id, трансформации и т.д.) управляете заливкой в буфера данных экземпляра. получается есть обьект с vao и vbo 1 штука (естественно индекс смещения 0), вы добавляете ещё копию, значит менеджер генерит  id (1) и заливает (добавляет) данные в буфер обьекта и далее по смещению представленным в id вы рисуете нужный экземпляр плюс этот же id используете в передаче трансформаций или любых атрибутов обьекта в шейдера.

И вообще вот классные обучающие сайты.
https://learnopengl.com/
http://vbomesh.blogspot.ru/2012/02/vbo-opengl.html

#5
18:46, 11 авг. 2017

Спасибо за ответ!

Лучше делать свой vao для каждого обьекта и записывать в его vbo каждый экземпляр,

Поясните пожалуйста, что вы имеете ввиду под терминами "объект" и "экземпляр". Давайте на живом примере. Пусть моя игра - типичный платформер, в котором есть один управляемый персонаж и N врагов. Каждый из них движется и, следовательно, постоянно меняет координаты. Что здесь будет "объектом", а что "экземпляром"?

И вообще вот классные обучающие сайты.

Спасибо) Вот на первом уроке второго сайта описывается VBO. Говорится, что с его помощью можно передать координаты в видеопамять на хранение и обращаться к ним по индексу буфера. У меня возникает вопрос. Есть ли смысл мне использовать VBO для объектов, координаты которых постоянно меняются? Ведь в таком случае я буду каждый раз слать обновлённые координаты в видеопамять, и никакого прироста в производительности не получу в принципе.

Если же я прав, то как лучше это реализовать? И если можно, то с небольшими примерами. Спасибо!

#6
20:25, 11 авг. 2017

AccumPlus
> Ведь в таком случае я буду каждый раз слать обновлённые координаты в
> видеопамять,
А нужно ли это делать каждый кадр? Допустим у тебя 2500 фпс, данные в буфере нужно обновлять 2500 раз в секунду или достаточно 60?
Так же для буферов появились биты GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT, через функцию glBufferStorage + glMapBufferRange можно отмапить эти буферы на системную память, таким образом ты будешь с ними работать как с обычными массивами, тоесть любое изменение сразу же будет учитываться.

#7
22:08, 11 авг. 2017

AccumPlus

Экземпляр это копия обьекта, но с уникальными параметрами (не знаком с высокоуровневыми языками, пишу в fasm, но вроде это называется наследование), обьект= меш с атрибутами, текстурками и т.д.). Вот у вас есть игрок и противники игрок это один обьект, противник другой, вот их экземпляры например 1 экземпляр игрока и 10 экземпляров противников.
В общем дело обстоит так при движении не надо вручную менят координаты вершин обьекта это должен делать шейдер т.е. вот вы создали меш (спрайт в вашем случае) залили в VBO и больше атрибуты его не изменяются вообще никогда, но меняются параметры его трансформации в шейдере т.е. перенос, поворот, масштаб. (я думаю вам надо ознакомится с принципом работы преобразования координат в opengl ну типо локальная, мировая система координат, преобразование и проекции). Я сначала простыню раскатал но вспомнил вот про этот сайт: http://opengl-tutorial.blogspot.ru/p/2.html

#8
22:57, 11 авг. 2017

Fantom09
> можно отмапить эти буферы на системную память, таким образом ты будешь с ними
> работать как с обычными массивами, тоесть любое изменение сразу же будет
> учитываться

Опа, вот это здорово, годится. Мне создавать VAO и VBO для каждого объекта отдельно?

Ivashka
> т.е. вот вы создали меш (спрайт в вашем случае) залили в VBO и больше атрибуты
> его не изменяются вообще никогда, но меняются параметры его трансформации в
> шейдере т.е. перенос, поворот, масштаб

И это действительно будет правильнее, чем каждый раз менять координаты? Ведь объекты будут взаимодействовать, и мне обязательно нужно будет знать их текущие координаты. Смогу ли я потом получить эти обновлённые координаты после трансформации с помощью шейдеров? За ссылку спасибо, почитаю)

#9
0:39, 12 авг. 2017

AccumPlus
Это и есть правильно. Трансформации хранятся в обьекте. Вот у меня к премеру экземпляр обьекта содержит структуру с разными параметрами включая трансформации, и эти трансформации передаются шейдеру при рисовании, вариант сразу хранить массив трансформаций в случае если в vao куча разных обьектов, т.е. к примеру например перенос храниться не в шейдере а в структуре описывающей обьекта.При расчёте физики просто надо делать преобразования из одной системы координат в другую и обратно используя эти трансформации. Для лудшего понимания этих процессов сделайте рей-пикинг (выбор обьекта) как раз в этой нужной для игр функции применяется принцип определения пересечения луча и плоскости (по сути зачатки обсчёта физики), и учитываются эти трансформации и переходы из мировых в локальные координаты и обратно.
И насчёт VAO, да вариант создавать для  экземпляра свой VAO  но это плохо с точки зрения оптимизации, ведь тогда на каждый VAO надо делать вызов отрисовки и если у тебя 1000 экземпляров ты 1000 раз вызовишь функцию рисования.
Вот собственно ссылки http://antongerdelan.net/opengl/raycasting.html
тут пикинг на директиксе но суть одинакова http://pmg.org.ru/rm/pick.htm

#10
22:03, 12 авг. 2017

Ivashka
> И насчёт VAO, да вариант создавать для экземпляра свой VAO но это плохо с
> точки зрения оптимизации, ведь тогда на каждый VAO надо делать вызов отрисовки
> и если у тебя 1000 экземпляров ты 1000 раз вызовишь функцию рисования.

Ага, сработало.
Я правильно понял, что на каждый экземпляр у меня свой VBO?
И ещё я могу использовать один вектор для любого количества экземпляров? Не думаю, что у меня их количество будет близко к тысяче (да даже вряд ли будет сто), но вопрос чисто с точки зрения производительности.

В остальном понятно - надо углубляться в шейдеры и преобразования координат. Спасибо!

#11
23:29, 12 авг. 2017

Момент. Ни фига не сработало. Делаю так:

// Генерирую VAO
glGenVertexArrays(1, &vertexArrayID);

// Генерирую два буфера
glBindVertexArray(vertexArrayID);

glGenBuffers(1, &vertexBufferID_1);
glGenBuffers(1, &vertexBufferID_2);

glBindVertexArray(0);

// Заношу в буферы данные
GLfloat tr1[] = {
    -0.5f, -0.7f, 0.0f,
     0.5f, -1.0f, 0.0f,
     0.0f,  1.0f, 0.0f};

glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID_1);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 9, tr1, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

GLfloat tr2[] = {
    -0.6f, -0.2f, 0.0f,
     0.3f, -0.8f, 0.0f,
     0.4f,  0.7f, 0.0f,};

glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID_2);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 9, tr2, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

// Обрабатываю каждый буфер
glBindVertexArray(vertexArrayID);
glUseProgram(programID);

// Первый буфер
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID_1);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, 0);

// Второй буфер
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID_2);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, 0);

// Отрисовываю...
glDrawArrays(GL_TRIANGLES, 0, 3);

glBindVertexArray(0);

Но рисуется только один треугольник (второй). Что исправить в программе?

#12
0:32, 13 авг. 2017

1 буффер лишний, в VAO только один vbo используется (вроде), вот и рисуется последний прибинденый буфер. Либо грузить вершины в один VBO либо делать отдельный vao для каждого обьекта.

#13
1:00, 13 авг. 2017

Ivashka
> И насчёт VAO, да вариант создавать для экземпляра свой VAO но это плохо с
> точки зрения оптимизации, ведь тогда на каждый VAO надо делать вызов отрисовки
> и если у тебя 1000 экземпляров ты 1000 раз вызовишь функцию рисования.

Ivashka
> 1 буффер лишний, в VAO только один vbo используется (вроде), вот и рисуется
> последний прибинденый буфер. Либо грузить вершины в один VBO либо делать
> отдельный vao для каждого обьекта.

Мы ходим по кругу)) Свой VAO для каждого экземпляра - плохая оптимизация. Окей, но один VAO = 1 VBO, а это значит, что я должен подготовить один массив вершин для всех объектов. Если в процессе игры будут появляться и исчезать объекты, то этот массив придётся обновлять, а значит обновлять VBO, и мы вновь приходим к вопросу целесообразности использования VBO. И даже если я отмаплю этот буфер в системную память, то мне придётся его мапить снова, так как меняются не только значения элементов массива, но и их количество.

Ладно, пока что остановлюсь на варианте 1 экземпляр = 1 VAO + 1 VBO. По крайней мере это работает. Может быть в будущем мне попадётся пример программы, в которой реализовывается задуманное мной единственно верным образом)

#14
1:30, 13 авг. 2017

В процессе игры у вас обьекты же не появляются и изчезают каждый кадр, плюс вариант оставить обьект в vbo, но не рисовать (если обьект изчезает). Суть в VBO (имхо) избавиться от транспортировки данных по шине в видяху каждый кадр. Просто надо ещё уяснить одну штуку, без нормального менеджера этой хрени (кучи vao vbo ibo и т.д.) не достигнуть хорошей оптимизации.

Страницы: 1 2 Следующая »
ПрограммированиеФорумОбщее

Тема в архиве.