OpenGL шейдеры. Простой шейдер на GLSL
Речь пойдет оПримечание: для их использования потребуется дополнительная библиотека Cg (от NVidia).
Шейдер представляет собой часть шейдерной программы, которая заменяет собой часть графического конвейера видеокарты. От того, какую часть конвейера будет заменена, происходят их типы. Каждый шейдер должен выполнить свою обязательную работу, то есть записать какие-то данные и передать их дальше по графическому конвейеру.
Шейдерная программа - это небольшая программа, состоящая из шейдеров (вершинного и фрагментного, возможны и другие) и выполняющаяся на GPU(Graphics Processing Unit), то есть на графическом процессоре видеокарты.
Рисунок 1. Графический конвейер OpenGL 2.0.
Типы шейдеров
- Вершинный шейдер - заменяет часть графического конвейера, выполняющего преобразования, связанные с данными вершин. Такие как умножение вершин и нормалей на матрицу проекции и моделирования, установка цветов вершин, установка материалов освещения. Он работает для каждой отрисованной вершины. Обязательной работой для вершинного шейдера является запись позиции вершины, в встроенную переменную gl_Position.
- Геометрический шейдер - шейдер, способный обработать не только одну вершину, но и целый примитив. Он может либо отбросить (от обработки) примитивы, либо создать новые, то есть геометрический шейдер способен генерировать примитивы. А также способен изменять тип входных примитивов (геометрический шейдер полностью вошел в OpenGL в версии 3.2).
- Фрагментный шейдер - заменяет часть графического ковейера (ГК), обрабатывая каждый полученный на предыдущих стадиях ГК фрагмент (не пиксель). Обработка может включать такие стадии, как получение данных из текстуры, просчет освещения, просчет смешивания. Обязательной работой для фрагментного шейдера является запись цвета фрагмента во встроенную переменную gl_FragColor или его отбрасывания специальной командой discard. В случае отбрасывания фрагмента, никакие расчеты дальше с ним производиться не будут, и фрагмент уже не попадет в буфер кадра.
Загрузка и компиляция
GLSL-шейдеры принято хранить в виде исходных кодов (хотя в OpenGL 4.1 и появилась возможность загружать шейдеры в виде бинарных данных). Такой подход был использован для лучшей переносимости шейдеров на различные аппаратные и программные платформы. Исходные коды компилируются драйвером. Они могут быть скомпилированы лишь после создания действующего контекста OpenGL. Драйвер сам генерирует внутри себя оптимальный двоичный код, который понимает данное оборудование. Это гарантирует, что один и тот же шейдер будет правильно и эффективно работать на различных платформах.
Исходный код может быть представлен в виде ANSI-строк, завершающихся переносом строки ('\n') или без него. В случаи если переноса нет, нужно передать массив длин каждой строки.
Шаги загрузки и компиляции:
- Сначала выделяются идентификаторы в виде GLuint, под шейдеры - glCreateShader, а под шейдерную программу - glCreateProgram.
- На идентификатор шейдера загружается исходный код, который передается драйверу glShaderSource.
- После этого шейдер компилируется glCompileShader.
- Несколько шейдеров разных типов прикрепляются к программе glAttachShader
- Последний шаг - линкование прикрепленных шейдеров в одну шейдерную программу glLinkProgram.
Мы будем использовать вершинный и фрагментный шейдеры, так как без них в современных версиях OpenGL ничего не нарисуешь. Остальные типы шейдеров пока рассматривать не будем, так их нет в OpenGL 2.0, и они не являются обязательными в OpenGL 3.3 и выше.
Вершинный шейдер
Код:
|
Код
|
Создаем атрибут в виде двухмерного вектора с именем coord. Именно в него и будут приходить данные о координатах вершины.
Атрибут (Attribute) - это данные, передаваемые программой вершинному шейдеру (другим шейдерам данные не доступны). Причем данные приходят шейдеру на каждую вершину. Эти данные доступны только для чтения.
vec2 - это двумерный вектор типа Float.
void main() - вход в программу.
Код:
|
gl_Position - это встроенная переменная для записи обработанной шейдером позиции вершины. Так как она имеет тип vec4, мы создаем вектор из четырех компонентов, беря X и Y из атрибута, который является двумерным вектором, Z ставим = 0, а W = 1.0. Затем наши данные о вершине идут дальше по конвейеру.
Фрагментный шейдер
Код:
|
uniform vec4 color; - в этой переменной типа четырехкомпонентного вектора передадим примитиву желаемый цвет.
Юниформ (Uniform) - это данные, посылаемые в шейдер приложением. В отличии от данных, посылаемых атрибутом, они глобальны и для шейдеров, и для вершин. То есть если объявить юниформ-переменную с одинаковым именем в вершинном и фрагментом шейдере, они будут общими и для них. Также данные не зависят от того, какая сейчас вершина обрабатывается, они остаются неизменными, пока их не изменит приложение.
gl_FragColor - это встроенная переменная, имеющая тип vec4, в нее записывается обработанный фрагментным шейдером цвет фрагмента.
Для сборки примера, Вам понадобzтся библиотеки
Данный пример очень простой нужен, чтобы показать, как загружаются и подключаются шейдеры, а также как будут выглядеть самые простые вершинный и фрагментный шейдеры.
Код:
|
Вот и всё. Результат работы:
Рисунок 2. Простой шейдер.
В примере много рутинной, повторяющейся работы, которую было бы неплохо вынести в отдельный функционал вспомогательных классов, однако в данном случае это не сделано для простоты и легкости понимания процесса компиляции и подключения шейдеров.
Некоторые детали кода не пояснены, к примеру, здесь использовались VBO (Vertex Buffer Object), это было сделано для того, чтобы избавиться от устаревших функций в новых версиях OpenGL.
Полезные ссылки:
Книги: