Урок №25. Расширенные методы взаимодействия с данными в OpenGL

  Дмитрий Бушуев  | 

  Обновл. 22 Июн 2020  | 

 655

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

Предназначения буферов

Буфер в OpenGL — это объект, который управляет определенной частью памяти графического процессора. Мы определяем содержание буфера, когда связываем его с конкретным типом хранимых данных, тем самым определяя его предназначение/роль. Например, связав буфер с целевым типом GL_ARRAY_BUFFER, мы получим буфер, исполняющий роль буфера массива вершин. Ссылки на подобные буферы хранятся внутри OpenGL, и, уже в зависимости от предназначения/роли буфера, OpenGL взаимодействует с ними соответствующим образом.

До этого момента заполнение памяти буфера происходило через вызов функции glBufferData(), которая выделяла часть памяти графического процессора и затем помещала данные в эту память. Если мы в качестве аргумента данных передадим NULL, то функция выделит память, но заполнять её не станет. Это полезно, если мы хотим сначала зарезервировать определенный объем памяти, чтобы позже вернуться к этому буферу.

Вместо того, чтобы одним вызовом функции целиком заполнить весь буфер, у нас есть возможность вызвать функцию glBufferSubData(), заполнив лишь определенную часть буфера. Данная функция в качестве аргументов ожидает получить предназначение буфера, смещение, размер данных и фактические данные. С помощью смещения мы указываем то место, от которого следует начать заполнять буфер. Это позволяет нам вставлять/обновлять только определенные части памяти буфера. Обратите внимание, что буфер должен иметь достаточное количество выделенной памяти, поэтому нам нужно вызвать функцию glBufferData() до использования функции glBufferSubData():

Ещё один способ поместить данные в буфер — это запросить указатель на память буфера и самостоятельно скопировать данные в эту память. Вызов OpenGL-функции glMapBuffer() возвращает указатель на область памяти текущего связанного буфера:

Вызов функции glUnmapBuffer() сигнализирует OpenGL о том, что мы закончили работать с указателем, и после этого он станет недействительным. Функция возвращает GL_TRUE, если OpenGL удалось успешно поместить наши данные в буфер.

Использование функции glMapBuffer() будет полезно при непосредственном отображении данных в буфере, без их предварительного сохранения во временной памяти. Например, непосредственное считывание данных из файла и копирование их в память буфера.

Групповая обработка вершинных атрибутов


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

Например, то же самое можно было бы реализовать, если бы вместо чередования поместить все векторные данные в большие куски на каждый атрибут. Таким образом, вместо чередующегося размещения 123123123123 мы можем воспользоваться групповым подходом: 111122223333.

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

В результате, мы можем непосредственно перенести массивы атрибутов в виде единого объекта прямо в буфер, не подвергая их предварительной обработке. Мы также могли бы объединить их в один большой массив и сразу же с помощью функции glBufferData() заполнить буфер, но для подобных задач идеально подходит функция glBufferSubData().

При этом необходимо обновить указатели атрибутов вершин, чтобы отразить вышеописанные изменения:

Обратите внимание, что параметр stride равен размеру атрибута вершины, так как следующий вектор атрибута вершины можно найти непосредственно после его 3-х (или 2-х) компонентов.

Благодаря этому, мы получаем ещё один подход к заданию атрибутов вершин. Стоит отметить, что вы вправе использовать любой из вышеописанных подходов. Прибегнув к групповому методу, вы получаете более организованный способ задания атрибутов вершин, но чередующийся вариант по-прежнему является рекомендуемым методом, поскольку после каждого запуска вершинного шейдера происходит выравнивание вершин.

Копирование буферов

Как только ваши буферы будут заполнены данными, вы можете поделиться этими данными с другими буферами или, возможно, скопировать содержимое одного буфера в другой буфер. Функция glCopyBufferSubData() позволяет нам с относительной легкостью копировать данные из одного буфера в другой. Прототип функции выглядит следующим образом:

Параметр readtarget определяет целевой объект считывания (из которого мы хотим скопировать данные), а параметр writetarget — целевой объект записи (в который мы хотим скопировать данные). Мы могли бы, например, скопировать данные из буфера VERTEX_ARRAY_BUFFER в буфер VERTEX_ELEMENT_ARRAY_BUFFER, указав их, соответственно, в качестве целевых буферов чтения и записи. В результате этого указанные действия возымеют эффект на те буферы, которые в тот момент будут привязаны к соответствующим ролям.

Но что, если мы хотим читать и записывать данные в два разных буфера, которые оба являются буферами массива вершин? Мы не можем привязать два буфера одновременно к одному и тому же предназначению буфера. Именно поэтому OpenGL предоставляет нам ещё два дополнительных предназначения для буферов: GL_COPY_READ_BUFFER и GL_COPY_WRITE_BUFFER. Далее мы связываем выбранные буферы с новыми предназначениями и указываем предназначения в качестве readtarget и writetarget аргументов.

Затем, функция glCopyBufferSubData() считывает данные заданного размера size от смещения readoffset и записывает их в буфер writetarget по смещению writeoffset. Ниже показан пример копирования содержимого двух буферов массива вершин:

Мы также могли бы реализовать его, связав буфер writetarget с одним из новых предназначений буфера:

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


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (3 оценок, среднее: 5,00 из 5)
Загрузка...

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *