Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







Инициализация вершинных и фрагментарных шейдеров





Для каждой программы, рассчитанной на использование OpenGL версии 3.1 или выше, необходима установка как минимум двух шейдеров: вершинного и фрагментарного. В нашем примере, это делается с помощью вспомогательной функции LoadShaders(), которая создает массив структур ShaderInfo (все подробные данные этих структур перечислены в файле заголовка LoadShaders.h).

На данной стадии, для программы на OpenGL, шейдер это маленькая функция написанная на языке шейдинга OpenGL (OpenGL Shading Language (GLSL)), языке программирования для конструирования шейдеров OpenGL очень похожем на С++. GLSL используется для всех шейдеров OpenGL, хотя не все особенности языка применимы на всех стадиях шейдеров. Вы передаете ваш GLSL шейдер в OpenGL в виде ряда цифровых значений. Для упрощения примера и облегчения вашей работы с шейдрами, мы храним наши шейдеры в виде файлов и используем команду LoadShaders() для чтения файлов и создания программ шейдинга. Все сочные подробности работы с шейдерами в OpenGL будут описаны в Главе 2.

Чтобы получить одобрение шейдеров, нам нужно показать вам несколько не вдаваясь в подробности. Для разбора всех подробностей GLSL у вас впереди ещё вся книга, а пока обойдёмся примером шейдера из Примера 1.2.

Пример 1.2 Vertex Shader for triangles.cpp: triangles.vert

#version 430 core

layout (location = 0) in vec4 vPosition;

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.

 

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

 







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

ЧТО ПРОИСХОДИТ ВО ВЗРОСЛОЙ ЖИЗНИ? Если вы все еще «неправильно» связаны с матерью, вы избегаете отделения и независимого взрослого существования...

Что делать, если нет взаимности? А теперь спустимся с небес на землю. Приземлились? Продолжаем разговор...

ЧТО ТАКОЕ УВЕРЕННОЕ ПОВЕДЕНИЕ В МЕЖЛИЧНОСТНЫХ ОТНОШЕНИЯХ? Исторически существует три основных модели различий, существующих между...





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


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