Здравствуйте. Обращаюсь к специалистам OpenGL. Рисую трехмерную сцену с группой объектов VAO
for(auto VAO: vao_list)
{
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, nullptr);
glBindVertexArray(0);
}
Все по "букварю", все работает. Но вот один из объектов, размером этак 40К вершин, потребовалось немного изменить: удалить 3 из 40 000 вершин. Можно конечно перезалить модифицированный объект, но для этого его образ надо вначале построить в оперативной памяти а потом перезалить в память графического процессора. А если меняющихся объектов в сцене большинство, то все их (копии) придется держать в оперативке, там править и перезаливать в графичекий буфер. Получается двойная работа, наверно тогда проще использовать "glDrawArrays", который рисует массивы прямо из оперативной памяти:
glEnableVertexAttribArray(idx_vertex_coord3d);
glEnableVertexAttribArray(idx_texture_coord);
glDrawArrays(GL_TRIANGLES, 0, count_of_vertices);
glDisableVertexAttribArray(idx_texture_coord);
glDisableVertexAttribArray(idx_vertex_coord3d);
но в этом случае память графического процессора (очень дорогая и быстрая) простаивает. Вот собственно вопрос: а можно ли как-то частично изменять объект размещенный в VAO (в графической памяти), удаляя/добавляя часть вершин в нем не перезаписывая VAO полностью?
На всякий случай вначале скажу немного про VAO и VBO. VAO не хранит данные объекта, а хранит только ссылки на один или несколько VBO и прочее состояние, необходимое для рисования объекта. Поэтому работать мы будем с VBO. Если несколько атрибутов вершин хранятся в нескольких VBO, то все их придётся править по отдельности. Изменение VBO будет сразу же доступно пользователям связанного VAO.
Рассуждения об удаленииНачнём с экстремально простого случая: при удалении вершин с конца вообще ничего изменять не нужно. Просто рисуем вызовом
glDrawElements(GL_TRIANGLE_STRIP, num_elements - 3, GL_UNSIGNED_BYTE, nullptr);
вместо
glDrawElements(GL_TRIANGLE_STRIP, num_elements, GL_UNSIGNED_BYTE, nullptr);
Если же удаляемые вершины хранятся в начале или в середине, то задача их удаления аналогична задаче удаления элементов из середины обычного массива в памяти. Например, если есть массив:
int a[] = {1,2,3,4,5,6};
то мы не можем удалить из него элементы 2 и 3, не оставив на их месте нули или какие-нибудь специальные значения значения, указывающие на то, что данные удалены. Чтобы не оставлять этих значений, мы можем либо перевыделить память под новый массив нужной длины и скопировать туда значения, либо скопировать 5 и 6 на место 2 и 3 и запомнить, что теперь длина массива равна 4 вместо 6. Оставшаяся в хвосте память под 2 элемента будет потрачена впустую, но при 40000 элементов ей можно пренебречь.
То же самое можно применить и к массиву в видеопамяти:
Вышеперечисленные методы могут иметь смысл при изменении/"удалении" большого количества данных, но, как вы заметили, совершенно не подходят для удаления трёх точек из 40000. То есть полностью удалить точки не получится, и придётся искать обходной путь.
Возможное решениеФункция glBufferSubData позволяет указывать смещение и длину заменяемых данных. Поэтому с её помощью можно переписать как весь буфер, так и любую его часть, хоть один единственный элемент. Если нам нужно удалить 3 из 40000 точек из модели, то не обязательно их удалять из буфера! Давайте просто перезапишем их значениями подходящей соседней вершины. Например, пусть есть ломаная из 8 вершин на плоскости:
2 3 5 7
/-------\ /\ /----8
/ \ / \ /
/ \/ \/
/1 4 6
Если мы сделаем вершину 5 равной вершине 6, то получим тот же эффект, что и при удалении вершины 5: появится отрезок, визуально соединяющий вершины 4 и 6, а длина отрезка 5-6 станет нулевой:
2 3 7
/-------\ /----8
/ \ /
/ \------/
/1 4 5,6
В выборе между перерасходом памяти на 3 вершины или расходом времени на пересоздание буфера на 40000 вершин логично отдать предпочтение первому варианту.
Usage patternИз вашего описания кажется, что "удалять" удалять точки вы будете редко, т.е. не на каждом кадре. Буфер же будет использоваться только для вывода графики, но не для transform feedback и прочих таких хитростей. Если так, то при создании буфера не забудьте в последнем аргументе glBufferData указать GL_DYNAMIC_DRAW
. Однако, без тестов производительности конкретного приложения здесь нельзя давать однозначную рекомендацию. При очень редком обновлении буфера GL_STATIC_DRAW
может оказаться быстрее. Напротив, при очень частом лучшим выбором может быть GL_STREAM_DRAW
.
комментарий от автора вопроса: Спасибо, все встало на свои места. В добавление к вашему ответу хочу добавить демо-код для одного из перечисленных вами вариантов решения:
GLuint vboId;
GLfloat* vertices = new GLfloat[vCount*3]; // элемент из 3 вершин
...
// запросим индекс для нового VBO
glGenBuffersARB(1, &vboId);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
// передаем данные в VBO
glBufferDataARB(GL_ARRAY_BUFFER_ARB, dataSize, vertices, GL_STATIC_DRAW_ARB);
// освободим память после переноса данных в VBO
delete [] vertices;
...
// В нужный момент этот VBO можно удалить
glDeleteBuffersARB(1, &vboId);
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Делаю задания по книге Бьярне Страуструпа "Программирование: принципы и практика с использованием C++, 2-е издание"Во главе 4, задании 16 надо...
Раньше просто компилировал с помощью g++ на Ubuntu, вытягивая компилятор с помощью пакетного менеджера:
Почему gcc компилирует этот код для стандарта GNU C++, но не компилирует для GNU C++11?