|
Инициализация вершинных и фрагментарных шейдеровДля каждой программы, рассчитанной на использование 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()
Надеемся, объяснение выбора нами параметров в дальнейшем поможет вам выбать параметры для вашей собственной программы. Вас ждет еще много объяснений по работе с 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.
Теперь осталось только что-нибудь нарисовать.
![]() ![]() Конфликты в семейной жизни. Как это изменить? Редкий брак и взаимоотношения существуют без конфликтов и напряженности. Через это проходят все... ![]() ЧТО ПРОИСХОДИТ, КОГДА МЫ ССОРИМСЯ Не понимая различий, существующих между мужчинами и женщинами, очень легко довести дело до ссоры... ![]() Система охраняемых территорий в США Изучение особо охраняемых природных территорий(ООПТ) США представляет особый интерес по многим причинам... ![]() Что вызывает тренды на фондовых и товарных рынках Объяснение теории грузового поезда Первые 17 лет моих рыночных исследований сводились к попыткам вычислить, когда этот... Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:
|