Уточнение glVertexAttribPointer

94

Просто хочу убедиться, что я правильно это понимаю (я бы спросил в SO Chat, но он там мертв!):

У нас есть массив вершин, который мы делаем «текущим», связывая его,
затем у нас есть буфер, который мы привязываем к цели,
затем мы заполняем эту цель, через glBufferData которую, по сути, заполняется все, что было привязано к этой цели, то есть наш буфер
а затем мы вызываем, glVertexAttribPointerкоторый описывает, как данные размещены - данные связаны с тем, с чем они связаны, GL_ARRAY_BUFFER и этот дескриптор сохраняется в нашем исходном массиве вершин

(1) Я правильно понимаю? Документация немного разреженная о том , как всех коррелятах.

(2) Есть ли какой-нибудь массив вершин по умолчанию? Потому что я забыл / пропустил, glGenVertexArraysи glBindVertexArrayмоя программа отлично работала без него.


Изменить: Я пропустил шаг ... glEnableVertexAttribArray.

(3) Связан ли атрибут вершины с массивом вершин в момент вызова glVertexAttribPointer, а затем мы можем включить / отключить этот атрибут glEnableVertexAttribArrayв любое время, независимо от того, какой массив вершин в настоящее время привязан?

Или (3b) привязан ли атрибут вершины к массиву вершин в момент вызова glEnableVertexAttribArray, и, таким образом, мы можем добавить один и тот же атрибут вершины к нескольким массивам вершин, вызывая их glEnableVertexAttribArrayв разное время, когда связаны разные массивы вершин?

mpen
источник

Ответы:

212

Некоторая терминология немного неверна:

  • A Vertex Array- это просто массив (обычно a float[]), содержащий данные вершин. Его не нужно ни к чему привязывать. Не путать с a Vertex Array Objectили VAO, о которых я расскажу позже.
  • A Buffer Object, обычно называемый a Vertex Buffer Objectпри хранении вершин или сокращенно VBO, - это то, что вы называете просто Buffer.
  • Ничего не сохраняется обратно в массив вершин, glVertexAttribPointerработает точно так же, как glVertexPointerи glTexCoordPointerработает, просто вместо именованных атрибутов вы можете указать число, определяющее ваш собственный атрибут. Вы передаете это значение как index. Все ваши glVertexAttribPointerзвонки будут поставлены в очередь, когда вы в следующий раз позвоните glDrawArraysили glDrawElements. Если у вас есть привязка VAO, VAO сохранит настройки для всех ваших атрибутов.

Основная проблема здесь в том, что вы путаете атрибуты вершин с VAO. Атрибуты вершин - это просто новый способ определения вершин, текскордов, нормалей и т. Д. Для рисования. Состояние магазина VAO. Сначала я объясню, как рисование работает с атрибутами вершин, а затем объясню, как можно сократить количество вызовов методов с помощью VAO:

  1. Вы должны включить атрибут, прежде чем сможете использовать его в шейдере. Например, если вы хотите отправить вершины шейдеру, вы, скорее всего, отправите его в качестве первого атрибута, 0. Поэтому перед рендерингом вам нужно включить его с помощью glEnableVertexAttribArray(0);.
  2. Теперь, когда атрибут включен, вам нужно определить данные, которые он будет использовать. Для этого вам нужно привязать свой VBO - glBindBuffer(GL_ARRAY_BUFFER, myBuffer);.
  3. А теперь мы можем определить атрибут - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);. В порядке параметров: 0 - это атрибут, который вы определяете, 3 - это размер каждой вершины, GL_FLOAT- это тип, GL_FALSEозначает не нормализовать каждую вершину, последние 2 нуля означают, что на вершинах нет шага или смещения.
  4. Нарисуй им что-нибудь - glDrawArrays(GL_TRIANGLES, 0, 6);
  5. Следующее, что вы нарисуете, может не использовать атрибут 0 (реально будет, но это пример), поэтому мы можем отключить его - glDisableVertexAttribArray(0);

Оберните это в glUseProgram()вызовы, и у вас будет система рендеринга, которая правильно работает с шейдерами. Но предположим, что у вас есть 5 различных атрибутов, вершины, текскорд, нормали, цвет и координаты карты освещения. Прежде всего, вы должны сделать один glVertexAttribPointerвызов для каждого из этих атрибутов, и вам нужно будет заранее активировать все атрибуты. Допустим, вы определяете атрибуты 0-4, как я их перечислил. Вы можете включить их все так:

for (int i = 0; i < 5; i++)
    glEnableVertexAttribArray(i);

И тогда вам придется связать различные РВО для каждого атрибута (если не хранить их все в одном VBO и использование смещениям / шаг), то вам необходимо сделать 5 различных glVertexAttribPointerвызовов, от glVertexAttribPointer(0,...);до glVertexAttribPointer(4,...);вершин , до Lightmap координат соответственно.

Надеюсь, эта система имеет смысл. Теперь я собираюсь перейти к VAO, чтобы объяснить, как их использовать, чтобы сократить количество вызовов методов при выполнении этого типа рендеринга. Обратите внимание, что использовать VAO не обязательно.

A Vertex Array Objectили VAO используются для хранения состояния всех glVertexAttribPointerвызовов и VBO, которые были нацелены при выполнении каждого из glVertexAttribPointerвызовов.

Вы создаете его с помощью вызова glGenVertexArrays. Чтобы сохранить все необходимое в VAO, свяжите его glBindVertexArray, затем выполните полный вызов отрисовки . Все вызовы привязки отрисовки перехватываются и сохраняются VAO. Вы можете отвязать VAO с помощьюglBindVertexArray(0);

Теперь, когда вы хотите нарисовать объект, вам не нужно повторно вызывать все glVertexAttribPointerпривязки VBO или вызовы, вам просто нужно связать VAO с помощью glBindVertexArraythen call, glDrawArraysили glDrawElementsвы будете рисовать то же самое, как если бы вы делали все эти вызовы методов. Возможно, вы захотите потом отвязать VAO.

После того, как вы отвяжете VAO, все состояние вернется к тому, как было до привязки VAO. Я не уверен, сохраняются ли какие-либо изменения, которые вы вносите во время привязки VAO, но это можно легко выяснить с помощью тестовой программы. Думаю, это можно представить glBindVertexArray(0);как привязку к "дефолтному" VAO ...


Обновление: кто-то обратил мое внимание на необходимость фактического вызова отрисовки. Как оказалось, вам действительно не нужно выполнять ПОЛНЫЙ вызов отрисовки при настройке VAO, просто все привязки. Не знаю, почему раньше считал это необходимым, но теперь это исправлено.

Роберт Рухани
источник
10
«Объект буфера вершины, или VBO (иногда называемый просто объектом буфера)» Это «иногда» называют так, потому что на самом деле он так и называется. Это просто буферный объект, ничем не отличающийся от любого другого буферного объекта, который вы можете использовать для унифицированных блоков, передачи пикселей, обратной связи преобразования или любого другого использования. Спецификация OpenGL ни на что не ссылается как на «объект буфера вершин»; даже оригинальная спецификация расширения никогда не называет это так.
Никол Болас
3
Отличный ответ. Спасибо, что нашли время написать это! Тем не менее, пара дополнительных вопросов: (1) Вы сказали, что «перед рендерингом и перед определением атрибута необходимо включить его с помощью glEnableVertexAttribArray (0)» - вы уверены, что его нужно включить перед вызовом glVertexAttribPointer? В моих тестах порядок не имеет значения. (2) Если я правильно вас понял, атрибуты вершин являются глобальными, и только их включенное / отключенное состояние сохраняется в текущем привязанном VAO?
mpen 02
1
(1) Я не думаю, что порядок имеет значение, если вы включили его раньше glDrawArraysили glDrawElements. Я обновлю сообщение, чтобы отразить это (2) Да, но хранится не только состояние включения / выключения, это все, что связано с этими вызовами - что было привязано к GL_ARRAY_BUFFER в то время, тип, шаг и смещение. По сути, он хранит достаточно, чтобы вернуть все атрибуты вершин так, как вы установили их с помощью VAO.
Роберт Рухани
2
да, VAO предназначены для того, чтобы вы могли заменить большую часть метода рисования привязкой VAO. У каждого объекта может быть отдельный VAO, и он все равно будет работать нормально. Однако вам все равно придется обновить униформу и привязать свои собственные текстуры. И вы должны использовать те же индексы атрибутов, что и для привязки атрибутов шейдера к индексам атрибутов, независимо от того, используются ли они layout(location = x)в шейдере или glBindAttributeLocationпри компиляции шейдера. Пример
Роберт Рухани
8
Обратите внимание, что в современном OpenGL больше нет объекта массива вершин по умолчанию, вы должны создать его самостоятельно, иначе ваше приложение не будет работать в контексте прямой совместимости.
Overv
3

Терминология и последовательность вызываемых API действительно сбивает с толку. Еще больше сбивает с толку то, как связаны различные аспекты - буфер, общий атрибут вершины и переменная атрибута шейдера. См. OpenGL-терминологию для довольно хорошего объяснения.

Далее по ссылке OpenGL-VBO, shader, VAO показан простой пример с необходимыми вызовами API. Это особенно хорошо для тех, кто переходит из немедленного режима в программируемый конвейер.

Надеюсь, это поможет.

Изменить: как видно из комментариев ниже, люди могут делать предположения и делать поспешные выводы. Реальность такова, что новичков это сбивает с толку.

ap-osd
источник
« См. OpenGL-терминологию для довольно хорошего объяснения. » Менее чем за 1 минуту я уже нашел один фрагмент дезинформации: «Они заменены общими атрибутами вершин с идентификатором (называемым индексом), который ассоциируется с переменной шейдера (для координаты, цвет и т. д.), которые обрабатывают атрибут ". Их не называют «индексами»; они «локации». Это очень важное различие, потому что атрибуты вершин также имеют «индексы» , которые сильно отличаются от местоположений. Это очень ужасный сайт.
Никол Болас
2
Это справедливый комментарий, но не совсем точный. Если вы посмотрите на API OpenGL, чтобы определить общий атрибут glVertexAttribPointer , void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer)идентификатор упоминается как index. Этот же идентификатор в контексте программы вызывается locationв API glGetAttribLocation .
ap-osd