Яка роль glBindVertexArrays проти glBindBuffer та які їхні стосунки?


74

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

GLuint abuffer;

glGenVertexArrays(1, &abuffer);
glBindVertexArray(abuffer);

GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

Книга пояснює, що перші три рядки створюють об’єкт масиву вершин , який використовується для об’єднання пов’язаних даних із масивом вершин. Другий рядок знаходить невикористане ім'я (я здогадуюсь цілий ідентифікатор без підпису, що зберігається в abuffer), а третій рядок створює об'єкт / робить його активним.

У книзі пояснюється, що 4-й-7-й рядки створюють буферний об'єкт для зберігання наших даних, при цьому 5-й рядок дає нам невикористаний ідентифікатор (подібний рядку 2 для об'єкта масиву вершин?), 6-й рядок, що створює буфер, і 7-й рядок, що виділяє достатньо пам'яті на процесор і створює вказівник на наші дані (точки) для GL_STATIC_DRAW.

Що означає об’єкт бути активним? Коли ви згодом будете використовувати abuffer? Що означає для масиву вершин об'єднання пов'язаних даних і коли дані були пов'язані з цим об'єктом масиву вершин?

Мене бентежать стосунки між abufferі buffer. Мене бентежить, який взаємозв'язок масиву вершин має з буферним об'єктом, і в який момент цей зв'язок формується. Я не впевнений, чи пов’язані вони насправді, але вони представлені в підручнику одразу одне за іншим.

Будь-яка допомога буде вдячна. Дякую.

Відповіді:


144

З точки зору низького рівня ви можете думати про масив, що складається з двох частин:

  • Інформація про розмір, форму та тип масиву (наприклад, 32-розрядні числа з плаваючою комою, що містять рядки векторів з чотирма елементами в кожному).

  • Дані масиву, що є трохи більше, ніж велика крапка байтів.

Незважаючи на те, що концепція низького рівня здебільшого залишається незмінною, спосіб вказівки масивів за кілька років змінювався кілька разів.

OpenGL 3.0 / ARB_vertex_array_object

Це шлях, напевно слід робити сьогодні. Дуже рідко можна зустріти людей, які не можуть запустити OpenGL 3.x, але при цьому все одно мають гроші на своє програмне забезпечення.

Буферний об'єкт у OpenGL - це велика крапка бітів. Подумайте про "активний" буфер як про просто глобальну змінну, і є купа функцій, які використовують активний буфер замість використання параметра. Ці глобальні змінні стану є потворною стороною OpenGL (до прямого доступу до стану, про що йдеться нижче).

GLuint buffer;

// Generate a name for a new buffer.
// e.g. buffer = 2
glGenBuffers(1, &buffer);

// Make the new buffer active, creating it if necessary.
// Kind of like:
// if (opengl->buffers[buffer] == null)
//     opengl->buffers[buffer] = new Buffer()
// opengl->current_array_buffer = opengl->buffers[buffer]
glBindBuffer(GL_ARRAY_BUFFER, buffer);

// Upload a bunch of data into the active array buffer
// Kind of like:
// opengl->current_array_buffer->data = new byte[sizeof(points)]
// memcpy(opengl->current_array_buffer->data, points, sizeof(points))
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

Тепер ваш типовий вершинний шейдер приймає вершини як вхідні дані, а не велику крапку бітів. Отже, вам потрібно вказати, як BLOB-біт (буфер) декодується у вершини. Це робота масиву. Так само існує "активний" масив, який ви можете сприймати як просто глобальну змінну:

GLuint array;
// Generate a name for a new array.
glGenVertexArrays(1, &array);
// Make the new array active, creating it if necessary.
glBindVertexArray(array);

// Make the buffer the active array buffer.
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// Attach the active buffer to the active array,
// as an array of vectors with 4 floats each.
// Kind of like:
// opengl->current_vertex_array->attributes[attr] = {
//     type = GL_FLOAT,
//     size = 4,
//     data = opengl->current_array_buffer
// }
glVertexAttribPointer(attr, 4, GL_FLOAT, GL_FALSE, 0, 0);
// Enable the vertex attribute
glEnableVertexAttribArray(attr);

OpenGL 2.0 (по-старому)

У OpenGL 2.x не було масивів вершин, а дані були просто глобальними. Вам все одно доводилося телефонувати glVertexAttribPointer()і glEnableVertexAttribArray(), але вам доводилося телефонувати їм кожного разу, коли ви використовували буфер. У OpenGL 3.x ви просто налаштували масив один раз.

Повертаючись до OpenGL 1.5, ви могли б насправді використовувати буфери, але ви використовували окрему функцію для прив'язки кожного виду даних. Наприклад, glVertexPointer()було для вершинних даних і glNormalPointer()для звичайних даних. До OpenGL 1.5 буферів не було, але ви могли використовувати вказівники на пам'ять програми.

OpenGL 4.3 / ARB_vertex_attrib_binding

У 4.3, або якщо у вас є розширення ARB_vertex_attrib_binding, ви можете вказати формат атрибута та дані атрибута окремо. Це приємно, оскільки це дозволяє легко перемикати один масив вершин між різними буферами.

GLuint array;
// Generate a name for a new array array.
glGenVertexArrays(1, &array);
// Make the new array active, creating it if necessary.
glBindVertexArray(array);

// Enable my attributes
glEnableVertexAttribArray(loc_attrib);
glEnableVertexAttribArray(normal_attrib);
glEnableVertexAttribArray(texcoord_attrib);
// Set up the formats for my attributes
glVertexAttribFormat(loc_attrib,      3, GL_FLOAT, GL_FALSE, 0);
glVertexAttribFormat(normal_attrib,   3, GL_FLOAT, GL_FALSE, 12);
glVertexAttribFormat(texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 24);
// Make my attributes all use binding 0
glVertexAttribBinding(loc_attrib,      0);
glVertexAttribBinding(normal_attrib,   0);
glVertexAttribBinding(texcoord_attrib, 0);

// Quickly bind all attributes to use "buffer"
// This replaces several calls to glVertexAttribPointer()
// Note: you don't need to bind the buffer first!  Nice!
glBindVertexBuffer(0, buffer, 0, 32);

// Quickly bind all attributes to use "buffer2"
glBindVertexBuffer(0, buffer2, 0, 32);

OpenGL 4.5 / ARB_direct_state_access

У OpenGL 4.5, або якщо у вас є розширення ARB_direct_state_access, вам більше не потрібно телефонувати glBindBuffer()або glBindVertexArray()просто налаштовувати ситуацію ... ви вказуєте масиви та буфери безпосередньо. Вам потрібно лише прив’язати масив в кінці, щоб намалювати його.

GLuint array;
// Generate a name for the array and create it.
// Note that glGenVertexArrays() won't work here.
glCreateVertexArrays(1, &array);
// Instead of binding it, we pass it to the functions below.

// Enable my attributes
glEnableVertexArrayAttrib(array, loc_attrib);
glEnableVertexArrayAttrib(array, normal_attrib);
glEnableVertexArrayAttrib(array, texcoord_attrib);
// Set up the formats for my attributes
glVertexArrayAttribFormat(array, loc_attrib,      3, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribFormat(array, normal_attrib,   3, GL_FLOAT, GL_FALSE, 12);
glVertexArrayAttribFormat(array, texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 24);
// Make my attributes all use binding 0
glVertexArrayAttribBinding(array, loc_attrib,      0);
glVertexArrayAttribBinding(array, normal_attrib,   0);
glVertexArrayAttribBinding(array, texcoord_attrib, 0);

// Quickly bind all attributes to use "buffer"
glVertexArrayVertexBuffer(array, 0, buffer, 0, 32);

// Quickly bind all attributes to use "buffer2"
glVertexArrayVertexBuffer(array, 0, buffer2, 0, 32);

// You still have to bind the array to draw.
glBindVertexArray(array);
glDrawArrays(...);

ARB_direct_state_access приємний з багатьох причин. Ви можете забути про прив'язку масивів та буферів (крім випадків, коли малюєте), тому вам не доведеться думати про приховані глобальні змінні, які OpenGL відстежує для вас. Ви можете забути про різницю між "генеруванням імені для об'єкта" та "створенням об'єкта", оскільки glCreateBuffer()іglCreateArray() виконуйте обидва дії одночасно.

Вулкан

Вулкан іде ще далі, і ви пишете код, як псевдокод, який я писав вище. Отже, ви побачите щось на зразок:

// This defines part of a "vertex array", sort of
VkVertexInputAttributeDescription attrib[3];
attrib[0].location = 0; // Feed data into shader input #0
attrib[0].binding = 0;  // Get data from buffer bound to slot #0
attrib[0].format = VK_FORMAT_R32G32B32_SFLOAT;
attrib[0].offset = 0;
// repeat for attrib[1], attrib[2]

6
+1 для опису селектора / засувки механізму GL ( наприклад, буферний об'єкт, до якого застосовується покажчик вершини, береться з прив'язаного об'єкта, а не з переданого параметра). З цим дизайном API можна обійтись GL_EXT_direct_state_access, але це виключає запуск коду на драйверах Mesa, Intel або Apple, на жаль: - \ Однак, я думаю, важливо зазначити, що масиви Vertex існують з GL 1.1. Єдине, що змінилося в GL 2.0, - це введення загальних слотів атрибутів вершин.
Andon M. Coleman

4
Говорячи про OpenGL 1.1, ця версія також представила об'єкти текстури . Як і об'єкти масиву Vertex, текстури вже підтримувались у GL до введення об'єктів текстури, але дані / стан не зберігалися постійно. Замість того, щоб змінювати прив'язаний об'єкт текстури в OpenGL 1.0, більшість програмного забезпечення було написано для виконання текстурних даних / стану налаштування у списках відображення. GL 3.0 зробив щось подібне для масивів вершин, він представив об'єкти масиву вершин .
Andon M. Coleman

7
Любіть історичний контекст тут. Одне з найболючіших розчарувань у вивченні GL - це те, як API та найкращі практики змінювались з часом.
Cheezmeister 02.03.15

1
Для вашого прикладу на OpenGL4.5, чи не варто використовувати glCreateVertexArrays(1, &array)over glGenVertexArrays(1, &array)? Наскільки я розумію, vao не створюється доти, доки ви вперше не прив’яжете його за допомогою glGenVertexArrays, тож жоден з ваших викликів glEnableVertexArray * / glVertexArray * не буде працювати?
Жолудь

1
@Acorn: Хороший улов, я переглянув його, щоб спробувати з'ясувати різницю між створенням імені та створенням об'єкта.
Дітріх Епп,

15

Ваша інтерпретація книги не зовсім коректна. Об’єкти масиву Vertex не зберігають даних. Вони є класом об'єктів, відомих як контейнери, як Framebuffer Objects. Ви можете приєднувати / асоціювати з ними інші об’єкти, але вони ніколи не зберігають дані самі. Як такі, вони не є ресурсом, що обмінюється контекстом.

В основному об'єкти масиву Vertex інкапсулюють стан масиву вершин у OpenGL 3.0. Починаючи з OpenGL 3.1 (замість GL_ARB_compatibility) та OpenGL 3.2+ Core профілі, ви повинні мати ненульове значення VAO постійно, щоб команди, подібні glVertexAttribPointer (...)або, glDrawArrays (...)могли функціонувати. Зв'язаний VAO формує необхідний контекст для цих команд і постійно зберігає стан.

У старих версіях GL (і сумісності) стан, що зберігається VAO, був частиною глобального автомата стану.

Варто також зазначити, що "поточне" зобов'язання для неGL_ARRAY_BUFFER є одним із станів, які відстежують VAO. Хоча це прив'язка використовується такими командами, як VAO, не зберігають прив'язку, вони зберігають лише покажчики ( розширення, представлене поряд з GL 4.3, трохи ускладнює це, тому давайте будемо ігнорувати його для простоти).glVertexAttribPointer (...)GL_ARB_vertex_attrib_binding

VAOs робити пам'ятайте , що неминуче GL_ELEMENT_ARRAY_BUFFER, проте, так , що команди проіндексовані малювання , такі як glDrawElements (...)функції , як можна було б очікувати ( наприклад , VAOs повторно використовувати масив останнього елемента буфера пов'язаний).


Чому glVertexAttribPointer вимагає прив'язки буфера, такого як GL_ARRAY_BUFFER? Чи не glVertexAttribPointer встановлює лише стан VAO?
Brad Zeis

1
@BradZeis: glVertexAttribPointerвстановлює вказівник, який відносно пам'яті, що належить тому, що прив'язане GL_ARRAY_BUFFERпри виклику функції. Чесно кажучи, це якийсь дурний дизайн - в API, розробленому на зразок D3D, ви просто передаєте ідентифікатор буферного об’єкта VertexAttribPointerі ніколи не повинні нічого прив’язувати. Але в OpenGL glVertexPointer (...)існував 10+ років до введення VBO, і вони хотіли повторно використовувати якомога більше API без змін. Вони створили цю прив'язку ненульового VBO першого бізнесу, щоб уникнути додавання іншого параметра.
Andon M. Coleman

Дякую! Набагато більше сенсу вважати VAO вказівниками.
Бред Цайс

1
Дякуємо за вказівку на те, що прив’язка для GL_ARRAY_BUFFER не є одним із станів, які відстежують VAO .
neevek

6

Зв'язок створюється при виклику glVertexAttribPointer .

Огляд VertexArrays

GL_VERTEX_ARRAY_BINDINGі GL_ARRAY_BUFFER_BINDINGє константами, але вони можуть вказувати на глобальний стан прив'язки. Я маю на увазі стан, а не константу (оранжеву) на зображенні. Використовуйте glGet, щоб знаходити різні глобальні держави.

VertexArray - це групування інформації (включаючи буфер масиву) про вершину або багато паралельних вершин.

Використовуйте за GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDINGдопомогою glGetVertexAttrib, щоб знайти, який буфер масиву атрибутів встановлений.

glBindBuffer (GL_ARRAY_BUFFER встановлює глобальний станGL_ARRAY_BUFFER_BINDING

glBindVertexArray встановлює глобальний станGL_VERTEX_ARRAY_BINDING


Гей, це чудовий графік. Будь ласка, не поділіться джерелом цього?
dawid

@dawid Я зробив це в lucidchart lucidchart.com/invitations/accept/… . Я міг би видалити його в майбутньому, оскільки кількість безкоштовних облікових записів обмежена.
Йоссі

5

OpenGL - це інтерфейс з підтримкою стану. Це погано, застаріле і потворне, але це для вас спадщина.

Об’єкт масиву вершин - це сукупність прив’язок буфера, які драйвер може використовувати для отримання даних для викликів витягування, більшість навчальних посібників використовують лише один і ніколи не пояснюють, як використовувати кілька VAO.

Прив'язка буфера говорить opengl використовувати цей буфер для пов'язаних методів, зокрема для glVertexAttribPointerметодів.


1
Деякі вражаючі евристики в StackOverflow намагалися перешкодити мені зробити вам комплімент за критику OpenGL за її державність.
ArchaeaSoftware

-2

Між VertexArray та VBO немає взаємозв’язку.

Масив вершин виділяє пам'ять в оперативній пам'яті і надсилає покажчик на API. VBO виділяє пам'ять на графічній карті - системна пам'ять не має для неї адреси. Якщо вам потрібен доступ до даних vbo із системи, вам потрібно спочатку скопіювати їх із vbo в систему.

Крім того, в новіших версіях OpenGL масиви вершин повністю видаляються (дескректуються в 3.0, видаляються в 3.1)


1
Масиви вершин не видаляються в 3.x. Об'єкти масиву Vertex були введені в 3.0 і обов'язкові в пізніших версіях основного профілю.
Дітріх Епп

2
Питання було про об’єкти масиву вершин, а не про масиви вершин загалом. І навіть незважаючи на це, масиви вершин теж не застаріли. API, що використовується для їх налаштування , докорінно змінився (більше не існує спеціалізованих VetexPointer, NormalPointer тощо ...), але масиви вершин все ще існують ( glVertexAttrib{I|L}Pointer (...)заміна речей, як glVertexPointer (...)у сучасному GL).
Andon M. Coleman
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.