Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







Первое знакомство с программой OpenGL





Что такое OpenGL?

 

OpenGL это интерфейс программирования приложений (“application programming interface”, сокр. “API”), который является лишь библиотекой программного обеспечения для доступа к работе с графическим оборудованием. Описываемая в данном тексте версия 4.3 библиотек OpenGL содержит более 500 различных команд, которые вы можете использовать для создания объектов, изображений и процессов внутри интерактивного трехмерного приложения.

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

Поскольку OpenGL на рынке давно - версия 1.0 программы была выпущена Silicon Graphics Computer Systems еще в июле 1994 года - существует множество как версий самой программы, так и версий библиотек программного обеспечения, собранного в OpenGL для облегчения разработки приложений, вне зависимости от того, разрабатываете ли видео игру, или создаете визуализацию для научного или медицинского проекта, или даже просто демонстрируете изображения. Однако, современная версия программы отличается от исходной в нескольких важных аспектах. В данной книге мы описываем принцип использования современной версии OpenGL для создания таких приложений.

Следующий список вкратце описывает основные процессы, которые выполняет OpenGL при рендеринге изображений. (см. “Конвейер визуализации OpenGL” для более подробного описания).

● определяет данные для построения фигур на основе геометрических примитивов OpenGL.

● применяет различные “шейдеры” к введенным примитивам с целью определения их положения, цвета, и других свойств рендеринга.

● конвертирует математическое описание введенных примитивов в их “фрагменты”, привязанные к их положению на экране. Этот процесс называется “растеризация”.

● наконец, выполняет шейдеры фрагментов для каждого растрированного ранее фрагмента с целью определить их конечные положение и цвет.

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

 

OpenGL функционирует как система “клиент-сервер”, к которой программа, которую вы пишете, является “клиентом”, а процесс исполнения OpenGL, который предоставляет производитель графического оборудования вашего компьютера - “сервер”. В некоторых случаях исполнения (на таких операционных системах как X Windows Systems) процессы “клиента” и “сервера” выполняются на разных вычислительных машинах, связанных в компьютерную сеть посредством сетевых протоколов. В таком случае, “клиент” подает команду OpenGL, которая конвертируется в “протокол” оконной системы, которые потом передается по компьютерной сети на “сервер”, где преобразуется в конечное изображение.

 

Синтаксис OpenGL

Как вы возможно уже могли заметить, все команды в OpenGL начинаются с “gl” и состоят из одного или более слов с заглавной буквы без пробелов для обозначения выполняемой функции. Например, glBindVertexArray(). Все команды в OpenGL выглядят так. В программе вы также видели функции, начинающиеся на “glut”, которые входят в OpenGL Utility Toolkit (GLUT), библиотеку данных, написанную Марком Дж. Килгардом. Это популярный меж-платформный пакет для открытия окон и обработки ввода данных, помимо прочего. Мы использовали версию GLUT под названием “Freeglut”, изначально написанную Pawel W. Olszta и дополненную Андреасом Умбахом и Стивом Бэйкером (который продолжает работу с библиотекой на данный момент). Это современный вариант оригинальной библиотеки. Также вы можете заметить одну функцию “glewInit()”, которая была взята из библиотеки OpenGL Extension Wrangler, написанной Миланом Икитсом и Марцело Магайоном. Подробнее об этих двух библиотеках написано в Appendix X.

Согласно принятым в OpenGL наименованием функций, постоянные вроде GL_COLOR_BUFFER_BIT, которая встречалась вам в diplay(), предназначены для библиотек OpenGL. Все постоянные начинающиеся с GL_ используют нижнее подчеркивание для разделения слов. Их опдеделители только #defines, которые можно найти в файлах заголовков OpenGL: glcorearb.h и glext.h.

Для облегчения задачи переноса приложения на OpenGL на другие операционные системы, OpenGL также определяет разные виды данных по их функциям. Как например, GLfloat это данные чисел с плавающей запятой, используемые в определении вершин (vertices) в примере 1.1. OpenGL задает типы всем видам данных, которые применимы в программе. Они пречислины в Таблице 1.1. Кроме того, так как OpenGL пользуется языком “С”, нам не приходится говорить от перегрузке данных. Программа использует условное наименование данных для организации множества функций, в зависимости от ситуации. Например в Главе 2, “Основные понятия о шейдерах”, мы столкнемся с функцией glUniform*(), которая также может выглядить как glUniform2f() или glUniform3fv(). Приставки в конце “ядра” названия функции указывают независимые переменные. Например, “2” в glUniform2f() указывает, что два числовых значения будут переданы фукции (есть и другие параметры, но они одинаковы для всез 24 разновидностей glUniform*(); мы используем * для обозначения общего вида функции). Также обратите внимание на “f”после “2”. Это указывает, что эти два числовых параметра - порядка GLfloat. Наконец, некоторые версии функции glUniform*() заканчиваются на “v” - это означает вектор (vector). Таким образом в glUniform2fv() 2 цифровых значения с плавающей запятой проходят как одномерная полоса GLfloat, а не 2 раздельных параметра.

Для распознавания всех этих параметров, все буквы используемые как приставки перечислены в таблице 1.1, вместе с типами данных.

[таб. 1.1 Командные приставки и типы данных независимых переменных]

примечание:

 

Вершинные шейдеры

Для каждой вершины графа, заданной командой отрисовки, будет вызван вершинный шейдер для обработки данных связанных с этой вершиной. Взависимости от того, задействованы ли какие-либо другие пред-растровые шейдеры, вершинные шейдеры могут быть довольно простыми, например, просто копирование и передача данных (так называемый, сквозной шейдер) на следующую стадию более сложному шейдеру, который обрабатывает большое количество различных данных с целью определить конечное положение вершины на экране. Часто эта стадия использует матрицы трансформации, о которых читайте в Главе 5. Определение цвета вершины с использованием вычисления освещения описывается в Главе 7. Так на этой стадии можно применить любое множество других техник.

Обычно любое приложение представляет из себя множество вершинных шейдеров, но одновременно работать могут не больше одного.

 

Мозаичные шейдеры

После того, как вершинный шейдер обработал данные вершин графов, мозаичный шейдер продолжит обработку этих данных, если его задействовать. Как мы сможем увидеть в Главе 9, мозаичные шейдеры используют пятна для описания формы объекта, что позволяет при мозаикирование создать из геометрических примитивов более сложные и приятные глазу модели. На стадии мозаичного шейдинга возможно применение фактически двух шейдеров для умножения количества пятен и генерирования конечной формы.

 

Геометрические шейдеры

Следующая стадия - геометрический шейдинг. Здесь можно применить шейдери индивидуально к каждому геометрическому примитиву, а также создать новые перед тем, как перевести изображение в растры. Эта стадия также необязательна, но представляет собой очень мощный инструмент для создания изображений, как мы сможем увидеть в Главе 10.

 

Примитивная сборка

Все предыдущие стадии оперируют вершинами, и информацией о том, как эти вершины собираются в геометрические примитивы, которые переносятся в OpenGL. Стадия примитивной сборки превращает вершины в в соответствующие примитивы, подготавливая их к отборке и растрированию.

 

Отборка

Периодически будет так получаться, что некоторые вершины будут выходить за пределы поля зрения - области, в которой вы можете рисовать, - тогда примитивы связанные с такой вершиной будут изменяться для того, чтобы не один из ех пикселей не был за пределами зрения. Это и есть так называемая “отборка” и это действие программа OpenGL выполняет автоматически.

 

Растрирование

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

Фрагментарные шейдеры

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

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

 

По-фрагментные операции

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

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

Как вы видели в Диаграмме 1.2, есть также путь доступа для пиксельной информации. В целом, пиксельные данные поступают из файла изображения, хотя их также можно создать при визуализации OpenGL. Обычно пиксельная информация хранится в карте текстур для обрабокте при текстурном маппинге, что позволяет разным стадиям текстурирования обратиться к этим данным. Текстурный маппинг подробнее описан в Главе 6.

 

Это было короткое описание конвейера OpenGL, теперь разберем Пример 1.1 и перенаправим операции обратно в конвейер визуализации.

Ввод main()

Начнем с самого начала: как исполняется наша программа. Для начала взглянем, что происходит в main(). Открывающие шесть строчек используют библиотеку OpenGL Utility Toolkit для создания и открытия окна нашей программы. Так как детали каждой процедуры описаны в Appendix A, давайте взглянем на порядок выполнения команд.

int

main(int argc, char** argv)

{

glutInit(&argc, argv);

glutInitDisplayMode(GLUT_RGBA);

glutInitWindowSize(512, 512);

glutInitContextVersion(4, 3);

glutInitContextProfile(GLUT_CORE_PROFILE);

glutCreateWindow(argv[0]);

if (glewInit()) {

cerr << "Unable to initialize GLEW... exiting" << endl;

exit(EXIT_FAILURE);

}

init();

glutDisplayFunc(display);

glutMainLoop();

}

 

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

glutInitDisplayMode() определяет вид окна программы, который мы хотели бы использовать для нашего приложения. В данном случае мы лишь указали, что окно должно использовать цветовую схему RGBA (для подробностей обратитесь к Главе 4). Есть и другие функции, которые мы добавим для конфигурации окон, такие как, например, глубина буферизации или анимация запуска.

glutInitWindowSize() задает размер окна, как вы могли догадаться. И хотя в этом примере мы не применяем эту функцию, вы можете задать изменение окна программы в соответствии с параметрами вашего дисплея.

Следующие два запроса: glutInitContextVersion() и glutInitContextProfile() задают тип контекста OpenGL, по есть исходную форму данных OpenGL для отслеживания настроек и операций, которые мы хотим использовать. Здесь мы запрашиваем корневой профиль OpenGL версии 4.3 для нашего контекста. Выбранный нами профиль контролирует, можем ли мы использовать только функции, совместимые с новыми версиями, или же те, что подходят для всех сборок вплоть до OpenGL версии 1.0.

Последняя группа запросов - glutCreateWindow(), функция которой ясна из названия. Если возможно создать окно с параметрами отображения, заданными вами в glutInitDisplayMode, оно и будет создано (в сопряжении с оконной системой вашего компьютера). Только после того, как GLUT создал для вас окно (сюда входит создание контекста OpenGL), вы можете начать использовать функции OpenGL.

Далее по списку, запрос glewInit() инициализирует еще одну используемую нами библиотеку - OpenGL Extesion Wrangler. GLEW облегчает работу с функциями доступа и другими интересными феноменами компьютерного программирования, представленными в разных вариациях операционных систем в OpenGL. Без GLEW пришлось бы проделать огромное количество дополнительной работы, чтобы запустить приложение.

С этого момента мы готовы наконец приступить к действительно интересной работе с OpenGL.

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

Слудющая процедура, glutDisplayFunc() задает отклик дисплея. Это процедура, запрашиваемая GLUT, когда программа считает, что содержание экрана пора обновить. Мы указываем GLUT здесь куда нужно направиться: display(), об этом тоже чуть позднее. GLUT использует несколько функций отклика для обработки, к примеру, ввода пользовательских данных, изменение размера окон, и также многое другое. Полностью GLUT описан в разделе Appendix A, “Основы GLUT: The OpenGL Utility Toolkit”.

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

 

Запуск OpenGL

 

Следующая процедура, которую нам стоит рассмотреть - это init() из Примера 1.1. Вот еще раз код, чтобы вы вспомнили:

[код]

Инициализация нашей сетки вершинных объектов

Много чего происходит в функциях и данных init(). Начиная сверху, мы приступаем к размещению сетки вершинных объектов вызовом функции glGenVertexArrays(). OpenGL выдаст несколько наименований объектов для нашего использования; в нашем случае, NumVAOs, которые мы указали в разделе глобальных переменных кода. glGenVertexArrays() возвращает это же количество наименований в массиве, VAOs в нашем случае.

Вот полное описание glVertexArrays():

void glGenVertexArrays(GLsizei n, GLuint *arrays);

Returns n currently unused names for use as vertex-array objects in the

array arrays. The names returned are marked as used for the purposes

of allocating additional buffer objects, and initialized with values

representing the default state of the collection of uninitialized vertex

arrays.

 

Мы встретим множество команд OpenGL формы glGen* для задания наименований разным видам объектов OpenGL. Наименование это что-то вроде переменной точечного указателя в языке С, сходство в том, что пока ты не отведешь некое количество памяти и не задашь ему наименование, само наименование не особо помогает. В OpenGL точно также. Наша схема распределения называется “привязка объекта”, и выполняется она набором функций OpenGL порядка glBind*. Например, мы создаем и привязываем массив вершинных объектов с помощью команды glBindVertexArray().

void glBindVertexArray(GLuint array);

glBindVertexArray() does three things. When using the value array that

is other than zero and was returned from glGenVertexArrays(), a new

vertex-array object is created and assigned that name. When binding to a

previously created vertex-array object, that vertex array object becomes

active, which additionally affects the vertex array state stored in the

object. When binding to an array value of zero, OpenGL stops using

application-allocated vertex-array objects and returns to the default state

for vertex arrays.

A GL_INVALID_OPERATION error is generated if array is not a value

previously returned from glGenVertexArrays(), or if it is a value that has

been released by glDeleteVertexArrays().

 

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

Представьте привязку объектов, как железнодорожную стрелку. Когда стрелка переводится в какое-либо положение, все поезда едут на эти пути. Если снова перевести стрелку - туда и поедут все поезда опять. Так же и для объектов OpenGL. Проще говоря, вы используете привязку объектов по 2м основным причинам: сначала, когда вы впервые создаете и инициализируете объект и его данные, и потом снова, когда вы хотите его использовать и сделать “текущим”. Мы столкнемся с такой ситуацией при рассмотре строки display(), когда к glBindVertexArray будет повторное обращение.

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

void glDeleteVertexArrays(GLsizei n, GLuint *arrays);

Deletes the n vertex-arrays objects specified in arrays, enabling the names

for reuse as vertex arrays later. If a bound vertex array is deleted, the

bindings for that vertex array become zero (as if you had called

glBindBuffer() with a value of zero) and the default vertex array becomes

the current one. Unused names in arrays are released, but no changes to

the current vertex array state are made.

 

Наконец, для полноты примера, вы можете определить, было ли задано наименование для массива вершинных объектов.

GLboolean glIsVertexArray(GLuint array);

Returns GL_TRUE if array is the name of a vertex-array object that was

previously generated with glGenVertexArrays(), but has not been

subsequently deleted. Returns GL_FALSE if array is zero or a nonzero

value that is not the name of a vertex-array object.

 

Вы найдете такие примеры команд glDelete* и glls* для разных типов объектов OpenGL.

 

Void

main()

{

gl_Position = vPosition;

}

Да, вот и всё. На самом деле, этот пример - это сквозной шейдер из более раннего примера. Он лишь копирует данные ввода в данные вывода. Тем не менее, здесь есть, о чем поговорить.

Первая строка “ #version 430 core” определяет, какая версия OpenGL Shading Language будет использоваться. “430” означает, что мы хотим использовать версию привязанную к OpenGL 4.3. Название версий GLSL коррелирует с версиями OpenGL вплоть до версии 3.3. В более ранних версиях была другая система наименования, о которой подробнее в Главе 2. “Core” относится к применению основного профиля OpenGL, который коррелирует с нашим запросом к GLUT, когда мы вызываем процедуру glInitContextProfile(). Каждый шейдер должен начинаться со строки “#version”, иначе по умолчанию будет использоваться версия 110, которая совместима с базовой версией всех сборок OpenGL. Мы будем использовать шейдеры версии 330 и выше, в зависимости от свойств. Использование более ранних версий облегчает программу, если только вам не абсолютно необходимы более поздние функции новых шейдеров.

Далее, мы задаем переменные шейдера. Переменные шейдера - это его связь с внешним миром. То есть, сам шейдер не знает, откуда приходит на него информация, он лишь видит данные ввода при исполнении. Это наша задача связать его с “трубопроводом” шейдинга (это наш личный термин, но вы скоро поймёте, почему мы называем это так). Мы сделаем так, чтобы данные могли “протекать” через наше приложение и между стадиями шейдинга.

В нашем простом примере, данные ввода называются vPosition, и вы можете определить её по “in” строчке определения. На самом деле, много что происходит только в этой одной строке:

layout (location = 0) in vec4 vPosition;

Проще будет рассмотреть эту строку справа налево.

● vPosition это, как мы уже знаем, название переменной. Для удобства определения мы используем “v” от “vertex”, то есть данные переменные относятся к информации о вершинах.

● далее vec4, это тип vPosition. В данном случае, это 4-компонентный вектор переменных с плавающей запятой в языке GLSL. Есть много видов данных GLSL, о них в Главе 2.

Вы могли заметить, что когда мы уточняли данные каждой вершины в Примере 1.1, мы указывали лишь 2 координаты, но в нашем вершинном шейдере мы используем vec4. Откуда же еще 2 координаты? OpenGL автоматически дополняет недостающую информацию информацией по умолчанию. Дефолтные значения для vec4 будут (0,0,0,1), так что если мы укажем только xy координаты, другие две оси (z, w) будут со значениями 0 и 1 соответственно.

● Перед типом указан параметр “in”, о котором мы уже говорили, и он указывает на то, в каком направлении данные продвигаются в шейдер. Если вы задумались, есть ли “out”, то да, есть. И об этом позднее.

● И наконец, layout (location = 0), так называемый квалификатор макета, задача которого - передавать мета-данные для определения переменных. В этот квалификатор можно вложить много опций, некоторые будут специфичны для каждого шейдера.

В этом примере, мы просто поставили значения расположения vPosition на 0. Мы используем эти данные в привязке к последним двум процедурам в init().

И наконец, ядро шейдера определяется в его разделе main(). Вне зависимости от того, к какой стадии шейдинга он относится, каждый шейдер OpenGL имеет процедуру main(). Для данного шейдера, всё, что в ней происходит, это копирование положения вершин в специальные данные вывода для вершинных шейдеров - gl_Position. Вы вскоре узнаете, что есть несколько типов переменных шейдеров OpenGL, начинающихся с префикса gl_.

Также нам нужен фрагментарный шейдер в дополнение нашему вершинному. Вот один из Примера 1.3.

Пример 1.3 Fragment Shader for triangles.cpp: triangles.frag

#version 430 core

out vec4 fColor;

Void

main()

{

fColor = vec4 (0.0, 0.0, 1.0, 1.0);

}

Надеемся, многое выглядит уже знакомым, хотя это и совершенно новый шейдер. У нас есть ряд версии, указание переменных, и процедура main(). Есть несколько отличий, но вам предстоит узнать, что почти все шейдеры имеют схожую с этой структуру.

Вот на что стоит обратить внимание в нашем примере:

● Указание переменной для fColor. Как вы могли догадаться, есть квалификатор “out”, и это он и есть. Шейдер будет выводить данные в виде цвета фрагмента (отсюда выбор префикса “f” от “fragment”).

● Назначение цвета фрагмента. В этом случае, каждому фрагменту приписан этот вектор из четырех значений. В OpenGL цвета представлены в так называемой сетка цвета RGB, и каждый параметр цвета имеет значение от [0, 1]. RGB означает “red, green, blue” (красный, зеленый и синий цвета). Наблюдательный читатель наверное заметит: “Но как же так? Ведь параметра 4…”. И действительно, OpenGL на деле использует сетку RGBA, но A не означает цвета. Этот канал называется альфа-канал, и означает полупрозрачность, а не цвет как таковой. Подробнее мы его обсудим в Главе 4, а пока просто зададим значение 1.0, что означает, что цвет полностью непрозрачен.

Фрагментарные шейдеры - весьма мощные. Мы сможем проделать с ними множество приемов.

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

Чтобы привязать данным попадающие в наш вершинный шейдер, который является воротами во все вершинные данные, с которыми работает OpenGL, нам нужно соединить “in”-переменные в массив вершинных атрибутов, что делается с помощью glVertexAttribPointer().

void glVertexAttribPointer(GLuint index, GLint size,

GLenum type, GLboolean normalized,

GLsizei stride, const GLvoid *pointer);

Specifies where the data values for index (shader attribute location) can be

accessed. pointer is the offset from the start of the buffer object (assuming

zero-based addressing) in basic-machine units (i.e., bytes) for the first set

of values in the array. size represents the number of components to be

updated per vertex, and can be either 1, 2, 3, 4, or GL_BGRA. type

specifies the data type (GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,

GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FIXED,

GL_HALF_FLOAT, GL_FLOAT, or GL_DOUBLE) of each element in the

array. normalized indicates that the vertex data should be normalized

before being stored (in the same manner as glVertexAttribFourN*()).

stride is the byte offset between consecutive elements in the array. If stride

is zero, the data is assumed to be tightly packed.

 

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

 

Таблица 1.2 Пример заданных параметров для glVertexAttribPointer()

Название параметра Значение Объяснение
index   это значение расположения для соответствующей переменной вершинного шейдера. В нашем слечае, vPosition. Этот параметр можно задать напрямую в квалификаторе layout, или определить после компиляции шейдера.
size   Это количество значений для каждого нашего шейдера в массиве. vertices было задано NumVertices, каждое по 2 числа.
type GL_FLOAT Перечисленные данные типа GLfloat
normalized GL_FALSE Мы задали здесь GL_FALSE по двум причинам: во-первых, самое важное это то, что значения координат могут иметь любое значение, и мы не хотим их ограничивать до рамок [-1, 1]; во-вторых, значения не являются интегральными (т.е. не принадлежат GLint или GLshort).
stride   Поскольку наши данные следуют друг за другом “впритык”, мы можем оставить это значение на 0.
pointer BUFFER_OFFSET(0) Здесть стоит 0 потому, что наши данные начинаются от начала файла (нулевого байта) нашего объекта буфера.

 

Надеемся, объяснение выбора нами параметров в дальнейшем поможет вам выбать параметры для вашей собственной программы. Вас ждет еще много объяснений по работе с glVertexAttribPointer().

Еще одна дополнительная техника, которую мы использовали это макрос BUFFER_OFFSET в glVertexAttribPointer(), для указания смещения. В этом макросе ничего удивительно, вот его данные:

#define BUFFER_OFFSET(offset) ((void *)(offset))

И хотя есть длинная предыстория, почему используется этот макрос2, здесь мы использовали его только для того, чтобы утвердить, что мы ссылаемся на объект буфера, а не на указатель, как могло бы быть понято из прототипа glVertexAttribPointer().

Теперь нам осталось выполнить только одну задачу в init() - задействовать наш массив атрибутов вершин. Это делается вызовом glEnablelVertexAttribArray() и передачей индекса указателя массива атрибутов, инициализированного в glVertexAttribPointer(). Далее приведены параметры glEnablelVertexAttribArray().

void glEnableVertexAttribArray(GLuint index);

void glDisableVertexAttribArray(GLuint index);

Specifies that the vertex array associated with variable index be

enabled or disabled. index must be a value between zero and

GL_MAX_VERTEX_ATTRIBS 1.

 

Теперь осталось только что-нибудь нарисовать.

 

Void

display(void)

{

glClear(GL_COLOR_BUFFER_BIT);

glBindVertexArray(VAOs[Triangles]);

glDrawArrays(GL_TRIANGLES, 0, NumVertices);

glFlush();

}

 

Мы начинаем рендеринг с очищения кадрового буфера. Это делается командой glClear().

void glClear(GLbitfield mask);

Clears the specified buffers to their current clearing values. The mask

argument is a bitwise logical OR combination of the values listed in

 

Таблица 1.3 Очистка буфера

 

Буфер Именование
Буфер цвета GL_COLOR_BUFFER_BIT
Буфер глубины GL_DEPTH_BUFFER_BIT
Стенсил буфер GL_STENSIL_BUFFER_BIT

 

Мы обсудим стенсил и глубину, а также подробно поговорим про буфер цвета в Главе 4, “Color, Pixles and Framebuffers”.

Возможно вы спросите, как же установить цвет очистки для glClear(). Дефолтный цвет очистки в OpenGL - черный. Настроить другой цвет можно командой glClreaColour().

void glClearColor(GLclampf red, GLclampf green, GLclampf blue,

GLclampf alpha);

Sets the current clear color for use in clearing color buffers in RGBA mode.

(See Chapter 4 for more information on RGBA mode.) The red, green,

blue, and alpha values are clamped if necessary to the range [0, 1]. The

default clear color is (0,0,0,0), which is the RGBA representation of black.

 

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

На примере цвета очистки, предположим вы хотите, чтобы очистка экрана всегда была белой. Вам нужно вызвать glClreaColor(1,1,1,1). Но где делать этот запрос? Конечно можно задать его непосредственно перед glClrear() в display(), но тогда первый запрос будет резервным - OpenGL будет менять цвет с белого на черный каждый последующий раз при очистке. Более логично было бы прописать значение цвета очистки в init(). Таким образом мы можем сократить дополнительные внесения изменений в код программы. Все значения по умолчанию устанавливаются init(). Хотя конечно, ничего плохого в том, чтобы вносить каждый раз временные изменения, но это может замедлить выполнение вашей программы.

 

Попробуйте

Пропишите запрос glClrear() в triangles.cpp.

 

Рисование в OpenGL

 

Следующие два запроса, которые мы совершим, дадут команду к отрисовке и рендерингу вершин, которые мы запросим. Сначала мы вызываем glBindVertexArray() для выбора массива вершин, который хотим использовать как вершинные данные.

Далее мы вызываем glDrawArrays(), которая собственно и посылает вершинные данные в конвейер OpenGL.

"> void glDrawArrays(GLenum mode, GLint first, GLsizei count);

Constructs a sequence of geometric primitives using the elements from

the currently bound vertex array starting at first and ending at

first + count − 1. mode specifies what kinds of primitives are constructed

and is one of GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP,

GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, and

GL_PATCHES.

 

В нашем примере, мы запрашивает рендеринг отдельных треугольников, устанавливая метод рендеринга GL_TRIANGLES, начиная с нулевого байта, как мы указали в glVertexAttribPointer() для буфера, и до числа, указанного в NumVertices (у нас их 6).

Попробуйте

Измените triangles.cpp для визуализации разных типов геометрических примитивов, таких как GL_POINTS или GL_LINES. Могут быть использованы любые из вышеупомянутых примитивов, но некоторые результаты могут оказаться не совсем тем, что вы ожидаете. В случае GL_PATCHES вы не сможете увидеть резуьтат, так как для его отображения требуется мозаичный шейдер, о котором Глава 9.

 

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

void glFlush(void);

Forces previously issued OpenGL commands to begin execution, thus

guaranteeing that they complete in finite time.

 

Продвинутый уровень

В какой-то точке своей карьеры программиста OpenGL вы зададитесь вопросом: “Сколько времени это отняло?”. “Это” может быть временем визуализации объекта, отрисовки полого экрана, или любой другой команды, выполняемой OpenGL. Чтобы делать это точно, вам нужно знать, когда OpenGL заканчивает выполнение команд, которые вы хотите измерить.

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

void glFinish(void);

Forces the completion of all pending OpenGL commands and waits for

their completion.

Прим.: пользуйтесь командой glFinish() только при разработке приложения, а после окончания работы удалите ее из запросов. Хотя она помогает определить скорость выполнения некоторых команд OpenGL, она вредить скорости исполнения вашей программы в целом.

 

Цель данной главы.

 

После прочтения этой главы вы сможете:

● Определить, какие различные шейдеры использует OpenGL для создания изображения;

● Создавать и компилировать шейдеры на языке OpenGL Shading Language;

● Перенаправлять данные в шейдеры посредством различных приемов, достуных в программе OpenGL;

● Применять продвинутые возможности GLSL для создания более функциональных шейдеров.

 

Эта глава дает понятие о том, как использовать настраиваемые шейдеры в OpenGL. По ходу главы, мы расскажем о приминение OpenGL Shading Language (GLSL, как его называют), и о том, как шейдеры влияют на ваше приложение в OpenGL.

 

В этой главе вы найдете следующие подразделы:

● “Шейдеры и OpenGL” расскажет о настраиваемых шейдерах в контексте приложений OpenGL;

● “Настраиваемый конвейер OpenGL” детализирует каждую стадию настраиваемого конвейера OpenGL;

● “Обзор OpenGL Shading Language” дает вводную информацию об OpenGL Shading Language;

● “Блоки интерфейса” покажет, как организовать переменные шейдеров с приложением или между стадиями;

● “Компилирование шейдеров” описывает процесс конвертирования GLSL в настраиваемый шейдер программ, которые можно использовать на OpenGL;

● “Подпрограммы шейдоров” описывает метод увеличения эффективности шейдеров, который поможет сделать выбор исполняемых процессов без переписывания всего шейдера;

● “Раздельные объекты шейдинга” детализирует процесс составления элементов из нескольких шейдеров в один, настраиваемый графический конвейер.

 

 

Шейдеры и OpenGL

Современный конвейер визуализации OpenGL опирается по большей части на использование шейдеров для обработки данных, которые вы на него посылаете. Пожалуй, единственная визуализация, которая проходит без шейдеров в OpenGL, это очистка окна, отсюда понятно, насколько важны они при работе с программой. Версии по 3.0 (включительно) и те, которые работают с профилем совместимости, включают в себя конвейер с фиксированным назначением, который обрабатывает геометрические и пиксельные данные без применения шейдеров. Начиная с версии 3.1 этот конвейер был <







Что делает отдел по эксплуатации и сопровождению ИС? Отвечает за сохранность данных (расписания копирования, копирование и пр.)...

ЧТО И КАК ПИСАЛИ О МОДЕ В ЖУРНАЛАХ НАЧАЛА XX ВЕКА Первый номер журнала «Аполлон» за 1909 г. начинался, по сути, с программного заявления редакции журнала...

Конфликты в семейной жизни. Как это изменить? Редкий брак и взаимоотношения существуют без конфликтов и напряженности. Через это проходят все...

Что вызывает тренды на фондовых и товарных рынках Объяснение теории грузового поезда Первые 17 лет моих рыночных исследований сводились к попыткам вычис­лить, когда этот...





Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:


©2015- 2024 zdamsam.ru Размещенные материалы защищены законодательством РФ.