Урок 9. Шейдеры в OpenGL4
В этом уроке вы узнаете: какая архитектура шейдеров; какую роль выполняют шейдеры в OpenGL приложении; что такое язык GLSL и для чего он предназначен, ознакомитесь со базовыми принципами программирования GLSL.
Шейдеры
Из предыдущих уроков вы уже знакомы с шейдерами и применяли их практически. В данном уроке мы не будем использовать их как дополнение к приложению, а взглянем на сами шейдеры и на их код.
Сейчас шейдеры являются ключевым аспектом OpenGL приложения. Фактически, трудно что-нибудь сделать без их использования. Вся графика игр, многочисленные спецэффекты, и тот реализм, который на сегодняшний день достигла графическая часть игр – это всё получилось благодаря использованию шейдеров. Взглянем лишь на некоторые примеры использования шейдеров:
- Обычное применение: матрицы камеры и освещениями в вершинномшейдере, текстуры и определение текущего цвета пикселя в пиксельном шейдере
- Композиции из нескольких текстур, использования глубоких текстур, для тонкой детализации ландшафта и моделей
- Эффекты освещения и теней
- Системы частиц – туман, задымление а также различные виды вспышек
На самом деле, данный список можно продолжать, кажется бесконечно. Существует слишком много примеров применения шейдеров, чтобы даже их просто перечислить. Запустите любую новую игру и существующий список применения шейдеров может быть пополнен несколькими, а то и десятками новых применений шейдеров.
Особенности компиляции и загрузки шейдера
Хотя шейдер является очень низкоуровневой структурой – он должен выполняться очень быстро, но вы можете его писать на языке, подобном C++, этот язык называется GLSL. Это потому, что шейдер на ходу компилируется в машинный код видеоускорителя. Кроме того, не нужен специальный компилятор. Язык GLSL является стандартным и его компиляция осуществляется на ходу любой 3d системой. Таким образом, вы просто указываете на исходный файл формата .fx и он автоматически загружается, компилируется и выполняется непосредственно в вашем 3d приложении.
Язык GLSL
Взглянем на пример шейдера с точки зрения программирования. Посмотрим на типичный код шейдера:
//------------------------------------------------------------------------------------ // Constant Buffer Variables //------------------------------------------------------------------------------------ #version 330 uniform mat4 mWorld; uniform mat4 mView; uniform mat4 mProjection; layout (location = 0) in vec3 inPosition; layout (location = 1) in vec3 inNormal; layout (location = 2) in vec2 inTex; out vec3 theColor; out vec2 texCoord; //------------------------------------------------------------------------------------ // Vertex Shader //------------------------------------------------------------------------------------ void main() { gl_Position = mProjection*mView*mWorld*vec4(inPosition, 1.0); theColor = vec3(0.25,0.5,1); texCoord = inTex; } //------------------------------------------------------------------------------------ // Pixel Shader Variables //------------------------------------------------------------------------------------ #version 330 in vec3 theColor; in vec2 texCoord; out vec4 outputColor; uniform sampler2D gSampler; //------------------------------------------------------------------------------------ // Pixel Shader //------------------------------------------------------------------------------------ void main() { outputColor = texture2D(gSampler, texCoord)* vec4(theColor,1); } |
Вы видите, что весь код шейдера может быть разбит на четыре ключевых раздела. Это: объявления глобальных переменных – констант шейдера для вершинного шейдера, код функции вершинного шейдера, объявления констант для пиксельного шейдера и, наконец код функции пиксельного шейдера. В дальнейших разделах мы поэтапно рассмотрим структуру языка GLSL. Обратите внимание, что в OpenGL принято помещать вершинный шейдер с константами в один файл, а пиксельный шейдер с его переменными – в другой.
Типы данных GLSL
В каждом языке существуют различные встроенные типы данных. В языке GLSL типы данных носят некоторый математический оттенок, то есть в нем много типов данных наподобие векторов и матриц. Итак, таблица типов данных GLSL:
Тип данных | Примечание |
Array | Массив |
Scalar | Одно компонентная скалярная величина |
Vector,Matrix | Несколько компонентный вектор или матрица |
Sampler | Встроенный тип данных – сэмплер |
Struct, User Defined | Пользовательский тип данных |
Рассмотрим эти типы данных. Тип данных Scalar определяет скалярную величину и может быть одним из следующих:
bool int uint float |
Тип данных Vectorобозначается добавлением количества компонент к типу данных scalar. Например vec4 определяет четырехкомпонентный вектор. Матрицы Matrixв OpenGL обозначаются mat4. Пример определения вектора и матрицы:
vec3 fVector = { 0.2f, 0.3f, 0.4f }; mat2 fMatrix = { 0.0f, 0.1, 2.1f, 2.2f }; |
Типы Sampler используется для указания слота источника текстуры. Пример использования типа данных Sampler:
uniform sampler2D gSampler; ... outputColor = texture2D(gSampler, texCoord)* vec4(theColor,1); ... |
Операции ветвления кода в GLSL
В большинстве случаев код GLSL выполняется построчно, то есть одна строка, или инструкция потом следующая и так далее. Но в некоторых случаях, можно добавлять циклы и условные переходы. Обратите внимание, что так как GLSL должен работать быстро, то ветвления в коде GLSL должны быть скорее исключением, чем практикой.
Операция ветвления | Примечание |
break | Операция прерывания цикла |
continue | Продолжение цикла |
discard | Специфическое ветвление для пиксельногошейдера |
do | Ветвление |
for | Цикл |
if | Условный переход |
switch | Операция выбора |
while | Ветвление |
Операции ветвления в GLSLимеют тот же синтаксис, что и в языке C++.
Определения функций и блоки в GLSL
Остальная структура языка может быть сведена к следующей таблице:
Элемент | Примечание |
Функции | Определение функций |
Операции ветвления | См. предыдущий раздел |
Блоки | Блок определяется символами {} |
Выражение return | Операция возвращения из блока функции |
Выражения | Любое математическое выражение а также оператор присваивания. |
Операторы | Пример символов операторов: +,-,*,/,=,+=,++,<<,>>,==,!=,>=,! |
Встроенные функции | Intrinsic Functions |
Определение функций имеет некоторые характерные черты в GLSL. Об этом далее.
Константы шейдера
Для того, чтобы иметь возможность изменять определенные глобальные переменные шейдера существуют константы шейдера. В GLSL все константы объявляются ключевым символом uniform, предшествующим объявлению глобальной переменной шейдера.
uniform mat4 mWorld; uniform mat4 mView; uniform mat4 mProjection; |
Вы можете догадаться, что константы могут быть и других типов, не только типа mat4, как во фрагменте кода, приведенном выше.
Типы данных для функций вершинного и пиксельного шейдера
Для вершинного и пиксельного шейдера существуют определенные типы данных, присущие им в качестве входящих данных и выходящих функций. Итак, вершинныйшейдер принимает в себя входящие данные, которые должны полностью соответствовать формату вершинного буфера. Пример формата входящих данных для вершинного шейдера:
layout (location = 0) in vec3 inPosition; layout (location = 1) in vec3 inNormal; layout (location = 2) in vec2 inTex; |
Данный фрагмент кода назначает входящими дынными для вершинного шейдера координату, нормаль и текстурную координату. Входящий формат данных вершинного шейдера может быть различным и отличаться от этого примера. Выходящими данные вершинный шейдер возвращает операцией присвоения переменной, имеющей тип out соответствующего значения. Перед тем как вершинныйшейдер что-то сможет возвратить, переменные out должны быть обявлены
out vec3 theColor; out vec2 texCoord; |
Выходящие данные вершинногошейдера являются входящими данными пиксельного шейдера и имеют идентичный формат:
in vec3 theColor; in vec2 texCoord; |
Пиксельный шейдер может возвратить единственную переменную формата vec4, определяющую цвет точки.
out vec4 outputColor; ... outputColor = texture2D(gSampler, texCoord)* vec4(theColor,1); ... |
Встроенные функции GLSL
Встроенные функции GLSL позволяют осуществлять многие операции, которые невозможно выполнить обычным кодом GLSL. Встроенные функции обозначаются IntrinsicFuncions. В число этих функций входят математические функции, операции с матрицами и векторами, операции выборки из текстур а также много другое. Вот пример некоторых из встроенных функций GLSL:
abs sin cos dot cross fmod log mul max min round sqrt tan texture1d texture2d textureCUBE |
В этом отрывке кода из некоторого вершинного шейдера
vec3 lightVec = normalize(LightPosition - ecPosition); float diffuse = max(dot(lightVec, tnorm), 0.0); |
легко найти места, в которых вызывается несколько встроенных функций OpenGL, таких как: операция нормализации вектора, определение максимума и операция скалярного умножения векторов.
Заключение
В этом уроке вы познакомились непосредственно с тем, что представляет из себяшейдер. GLSL не так сложен как многие другие языки, так как программы на нем – точнее фрагменты кода для пиксельного и вершинного шейдера – получаются очень короткими.
Заполнен: OpenGL4
