Подтвердить что ты не робот

Концепция функций OpenGL 'Bind'

Я изучаю OpenGL из этот учебник. Мой вопрос касается спецификации в целом, а не конкретной функции или темы. При просмотре кода, как показано ниже:

glGenBuffers(1, &positionBufferObject);
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

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

В справочной странице указано, что:

glBindBuffer позволяет создавать или использовать именованный объект буфера. Вызов glBindBuffer с настройкой цели        GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER или GL_PIXEL_UNPACK_BUFFER и буфер, установленный на        имя нового объекта буфера привязывает имя объекта буфера к цели. Когда буферный объект привязан к        target, предыдущее связывание для этой цели автоматически прерывается.

Что такое понятие/полезность "привязки" чего-то к "цели"?

4b9b3361

Ответ 1

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

поэтому, когда вы вызываете функцию, то, что она делает, зависит, конечно, от аргументов, но также и от внутреннего состояния opengl - от контекста/объекта.

это очень ясно при привязке, которая говорит: "установите это как текущий X". затем более поздние функции изменяют "текущий X" (где X может быть буфером, например). [update:] и, как вы говорите, то, что задается (атрибут в объекте или "член данных" ), является первым аргументом для привязки. поэтому GL_ARRAY_BUFFER указывает конкретную вещь, которую вы устанавливаете.

и ответить на вторую часть вопроса - установив его на 0, просто удаляется значение, поэтому вы случайно не делаете незапланированные изменения в другом месте.

Ответ 2

Метод OpenGL может быть невероятно непрозрачным и запутанным. Я знаю! Я пишу 3D-двигатели на основе OpenGL в течение многих лет (вкл. И выкл.). В моем случае часть проблемы заключается в том, что я пишу движок, чтобы скрыть базовый 3D API (OpenGL), поэтому, как только я получу что-то, я больше не вижу код OpenGL.

Но вот один из методов, который помогает моему мозгу понять "путь OpenGL". Я думаю, что этот способ думать об этом правдив (но не всю историю).

Подумайте об аппаратной графике/графических картах. У них есть определенные возможности, реализованные в аппаратных средствах. Например, GPU может только обновлять (писать) одну текстуру за раз. Тем не менее, обязательно, чтобы графический процессор содержал много текстур внутри ОЗУ внутри графического процессора, потому что передача между памятью процессора и памятью графического процессора происходит очень медленно.

Итак, что делает OpenGL API, это создать понятие "активной текстуры". Затем, когда мы вызываем функцию OpenGL API для копирования изображения в текстуру, мы должны сделать это следующим образом:

1:  generate a texture and assign its identifier to an unsigned integer variable.
2:  bind the texture to the GL_TEXTURE bind point (or some such bind point).
3:  specify the size and format of the texture bound to GL_TEXTURE target.
4:  copy some image we want on the texture to the GL_TEXTURE target.

И если мы хотим нарисовать изображение на другой текстуре, мы должны повторить тот же процесс.

Когда мы, наконец, готовы отобразить что-то на дисплее, нам нужен наш код, чтобы сделать одну или несколько текстур, которые мы создали, и скопировать изображения, чтобы стать доступными нашим шейдером фрагментов.

Как оказалось, шейдер фрагмента может получить доступ к нескольким текстурам за один раз, обратившись к нескольким "единицам текстуры" (одна текстура на единицу текстуры). Таким образом, наш код должен связывать текстуры, которые мы хотим сделать доступными для текстурных блоков, которые наши шейдеры фрагментов ожидают от них.

Итак, мы должны сделать что-то вроде этого:

glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, mytexture0);

glActiveTexture (GL_TEXTURE1);
glBindTexture (GL_TEXTURE_2D, mytexture1);

glActiveTexture (GL_TEXTURE2);
glBindTexture (GL_TEXTURE_2D, mytexture2);

glActiveTexture (GL_TEXTURE3);
glBindTexture (GL_TEXTURE_2D, mytexture3);

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

error = glSetTexture (GL_TEXTURE0, GL_TEXTURE_2D, mytexture0);
error = glSetTexture (GL_TEXTURE1, GL_TEXTURE_2D, mytexture1);
error = glSetTexture (GL_TEXTURE2, GL_TEXTURE_2D, mytexture2);
error = glSetTexture (GL_TEXTURE3, GL_TEXTURE_2D, mytexture3);

БАМ. Не нужно устанавливать все это состояние снова и снова. Просто укажите, какой текстурный блок будет прикреплять текстуру, плюс текстурный тип, чтобы указать, как получить доступ к текстуре, плюс идентификатор текстуры, которую я хочу прикрепить к текстурному блоку.

Мне также не нужно было связывать текстуру как активную текстуру, чтобы скопировать изображение в нее, я бы просто дал идентификатор текстуры, которую я хотел скопировать. Зачем его нужно связывать?

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


По крайней мере, как я держу "OpenGL путь" прямо в моей голове. Честно говоря, если кто-то действительно, действительно, ДЕЙСТВИТЕЛЬНО понимает все причины, по которым OpenGL (и должен быть) структурирован так, как он есть, я бы хотел, чтобы они опубликовали свой собственный ответ. Я считаю, что это важный вопрос и тема, и обоснование редко бывает когда-либо описано вообще, а тем более так, как может понять мой тщеславный мозг.

Ответ 3

Из раздела Введение: что такое OpenGL?

Сложные агрегаты, подобные структурам, никогда не отображаются непосредственно в OpenGL. Любые такие конструкции скрыты за API. Это упрощает раскрытие OpenGL API на языках, отличных от C, без сложного уровня конверсии.

В С++, если вам нужен объект, содержащий целое число, float и строку, вы должны создать его и получить доступ к нему следующим образом:

struct Object
{
    int count;
    float opacity;
    char *name;
};

//Create the storage for the object.
Object newObject;

//Put data into the object.
newObject.count = 5;
newObject.opacity = 0.4f;
newObject.name = "Some String";

В OpenGL вы должны использовать API, который выглядит примерно так:

//Create the storage for the object
GLuint objectName;
glGenObject(1, &objectName);

//Put data into the object.
glBindObject(GL_MODIFY, objectName);
glObjectParameteri(GL_MODIFY, GL_OBJECT_COUNT, 5);
glObjectParameterf(GL_MODIFY, GL_OBJECT_OPACITY, 0.4f);
glObjectParameters(GL_MODIFY, GL_OBJECT_NAME, "Some String");

Никакие из них не являются действительными командами OpenGL, конечно. Это просто пример того, как будет выглядеть интерфейс с таким объектом.

OpenGL владеет хранилищем для всех объектов OpenGL. Из-за этого пользователь может получить доступ только к объекту по ссылке. Почти все объекты OpenGL обозначаются целым числом без знака (GLuint). Объекты создаются функцией вида glGen *, где * - тип объекта. Первый параметр - количество создаваемых объектов, а второе - массив GLuint *, который получает вновь созданные имена объектов.

Чтобы изменить большинство объектов, они должны быть сначала привязаны к контексту. Многие объекты могут быть привязаны к различным местоположениям в контексте; это позволяет использовать один и тот же объект по-разному. Эти разные местоположения называются мишенями; все объекты имеют список допустимых целей, а некоторые имеют только один. В приведенном выше примере фиктивная цель "GL_MODIFY" - это место, где привязывается objectName.

Так работают большинство объектов OpenGL, а объекты буфера - это "большинство объектов OpenGL".

И если это не так хорошо, учебник снова охватывает его в Глава 1: После данных:

void InitializeVertexBuffer()
{
    glGenBuffers(1, &positionBufferObject);

    glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

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

Функция glBindBuffer связывает вновь созданный объект буфера с целью привязки GL_ARRAY_BUFFER. Как упоминалось во введении, объекты в OpenGL обычно должны быть привязаны к контексту, чтобы они могли что-либо сделать, а объекты-буферы не являются исключением.

Функция glBufferData выполняет две операции. Он выделяет память для буфера, привязанного в настоящий момент к GL_ARRAY_BUFFER, который мы только что создали и связали. У нас уже есть некоторые данные вершин; проблема в том, что это в нашей памяти, а не в памяти OpenGL. Sizeof (vertexPositions) использует компилятор С++ для определения размера байта массива vertexPositions. Затем мы передаем этот размер в glBufferData как размер памяти для выделения для этого объекта буфера. Таким образом, мы выделяем достаточно памяти GPU для хранения наших данных вершин.

Другая операция, выполняемая glBufferData, - это копирование данных из нашего массива памяти в объект-буфер. Третий параметр контролирует это. Если это значение не равно NULL, как в этом случае, glBufferData скопирует данные, на которые указывает указатель, в объект-буфер. После этого вызова функции, буферный объект хранит точно, что хранится в вершинах.

Четвертый параметр - это то, что мы рассмотрим в будущих учебниках.

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

Ответ 4

Связывание буфера с целью - это что-то вроде установки глобальной переменной. Последующие вызовы функций затем работают с этими глобальными данными. В случае OpenGL все "глобальные переменные" вместе образуют контекст GL. Практически все функции GL читаются из этого контекста или каким-то образом модифицируют его.

Вызов glGenBuffers() похож на malloc(), выделяя буфер; мы устанавливаем глобальное указание на него с помощью glBindBuffer(); мы вызываем функцию, которая работает на этом глобальном (glBufferData()), а затем мы устанавливаем глобальное значение в значение NULL, поэтому он не будет непреднамеренно работать с этим буфером снова, используя glBindBuffer().

Ответ 5

OpenGL - это то, что известно как "машина состояний", с этой целью OpenGL имеет несколько "целевых объектов привязки", каждая из которых может иметь только одну вещь, связанную сразу. Связывание чего-то другого заменяет текущее связывание и, таким образом, изменяет его состояние. Таким образом, путем связывания буферов вы (re) определяете состояние машины.

В качестве конечного автомата любая связанная с вами информация будет влиять на следующий вывод устройства, в OpenGL, который является его следующим обратным вызовом. Как только это будет сделано, вы сможете привязать новые данные вершин, привязать новые пиксельные данные, привязать новые цели и т.д., А затем инициировать другой вызов рисования. Если вы хотели создать иллюзию движения на экране, когда вы были удовлетворены, вы нарисовали всю свою сцену (концепция 3D-движка, а не концепция OpenGL), вы можете перевернуть фреймбуфер.