Этот урок слегка отличается от предыдущих. Вместо изучения возможностей технологий OpenGL мы собираемся рассмотреть GLFX, библиотеку эффектов OpenGL. Эффект - это текстовый файл, который, возможно, содержит несколько шейдеров и упрощает комбинирование их в программе. Это позволяет обойти ограничение функции glShaderSource(), которая требует указать текст только одного этапа шейдеров. Она насильно заставляет использовать различные текстовые файлы для каждого шейдера (или различные буферы, как мы делали в прошлых уроках). Помещение всех шейдеров в один файл упрощает обмен определением структур между ними. Кроме того, GLFX предоставляет простой API для перевода эффектов в программу GLSL, что частично скрывает сложность функций OpenGL.
Идея файлов эффектов не нова. На самом деле, Microsoft уже годами раннее реализовало это в мире DirectX. Я уверен, что игровые студии имеют их собственный инструментарий, но к стыду говорят, в OpenGL нет для этого стандарта. Библиотека эффектов, которую мы будем использовать, - свободный проект Max Aizenshtein. Домашняя страница библиотеки тута.
Существует 2 способа установки GLFX. Если вы используете Ubuntu, то вы можете просто установить deb пакет из моего ppa на launchpad.net, или вы можете скачать исходный код и собрать самостоятельно.
Получаем исходники и собираем их с помощью следующих команд:
Внимание: GLFX зависит от GLEW. В этом нет проблемы, если вы используете эти уроки как фреймворк или ранее использовали GLEW в своем приложении. Если нет, то вернитесь во 2 урок для информации об инициализации GLEW.
Добавьте следующее для получения доступа к api GLFX:
#include <glfx.h>
Создание указателя на эффект:
int effect = glfxGenEffect();
Проход по файлу эффекта (мы получим его содержание мгновенно):
if (!glfxParseEffectFromFile(effect, "effect.glsl")) {
#ifdef __cplusplus // C++ error handling
std::string log = glfxGetEffectLog(effect);
std::cout << "Error parsing effect: " << log << std::endl;
#else // C error handling
char log[10000];
glfxGetEffectLog(effect, log, sizeof(log));
printf("Error parsing effect: %s:\n", log);
#endif
return;
}
Компилирование программы (комбинация из VS, FS и прочих) определяется в файле эффекта следующим образом:
int shaderProg = glfxCompileProgram(effect, "ProgramName");
if (shaderProg < 0) {
// таже ошибка с указателем, что и ранее
}
Теперь программа может быть использована в OpenGL как обычно:
glUseProgram(shaderProg);
Так как эффект больше не требуется, то удаляем его через
glfxDeleteEffect(effect);
Теперь у нас есть базовая инфраструктура, поэтому давайте погрузимся в файлы эффекта. Прелесть GLFX в том, что вы можете продолжить писать шейдеры GLSL практически в том же стиле, что и ранее. Есть несколько отличий, на которых мы фокусируемся.
Добавился раздел 'program' для комбинации этапов шейдеров в полной программе GLSL.
program Lighting
{
vs(410)=VSmain();
fs(410)=FSmain();
};
В примере выше файл эффекта где-то содержит определение функций VSmain() и FSmain(). Раздел 'program' определяет программу OpenGL с названием 'Lighting'. Вызов glfxCompileProgram(effect, "Lighting") приведет к компиляции и линковки VSmain() и FSmain() в единую программу. Оба шейдера будут компилироваться в GLSL версии 4.10 (аналогично объявлению '#version 410' в обычном GLSL).
Использование 'shader' вместо 'void' для объявления главной функции шейдера.
Главная точка входа в шейдер должна быть объявлена как 'shader' вместо 'void'. Вот пример: void calculate_something() { … }
shader VSmain()
{
calculate_something();
}
Включение нескольких шейдеров и программ в единый файл эффекта.
Вы можете разместить несколько разделов 'program' в единственный файл эффекта. Просто вызовете glfxCompileProgram() для каждой программы, которую хотите использовать.
Использование структур для передачи вершинных атрибутов между этапами шейдеров.
Вместо определения in/out переменных в глобальной секции шейдера мы можем использовать структуру GLSL и обмениваться ей между несколькими этапами шейдеров. Вот пример:
struct VSInput
{
vec3 Position;
vec2 TexCoord;
vec3 Normal;
};
struct VSoutput
{
vec2 TexCoord;
vec3 Normal;
};
shader VSmain(in VSInput VSin, out VSOutput VSout)
{
// преобразуем 'VSin' и обновляем 'VSout'
}
shader FSmain(in VSOutput FSin, out vec4 FragColor)
{
// 'FSin' соответствует 'VSout' из VS. используем ее
// для вычисления света и записываем результат в 'FragColor'
}
Использование включений для обмена функционалом между файлами эффектов.
Ключевое слово 'include' может быть использовано для включения одного файла эффекта в другой:
#include "another_effect.glsl"
Предостережение с включением файлов в том, что они не пробегаются GLFX. Они просто добавляются как есть в место, указанное словом 'include'. Это значит, что вы можете поместить только чисто GLSL код в них, без GLFX. Совет: поскольку часть синтаксиса GLSL аналогична C/C++ (#define), вы можете даже обмениваться определениями между файлом эффекта и вашем приложением.
Использование суффикса структур для определения позиции атрибута
В прошлых уроках мы использовали ключевое слова 'layout(location = …)' для определения позиции входящего атрибута в VS. Поместив двоеточие перед числом после входящего параметра VS мы можем достичь того же эффекта. Вот пример:
struct VSInput1
{
vec3 Position;
vec2 TexCoord;
};
struct VSInput2
{
vec3 Normal;
vec3 Tangent;
};
shader VSmain(in VSInput1 VSin : 5, in float colorScale : 10, in VSInput2 : 12)
VS выше получает позицию в атрибуте 5, координаты текстуры в 6, цвет в 10, нормаль в 12 и тангент в 13. Идея крайне проста - число после двоеточия определяет его позицию. В случае структур вы определяете позицию только первого атрибута. Остальные будут получать следующие значения согласно их типу (т.е. вектор возьмет 1 атрибут, а матрица 4x4 - 4 атрибута). Если суффикс отсутствует, то отсчет начнется с 0.
Использование 'interface' вместо 'struct' для размещения квалификаторов
GLSL предоставляет несколько квалификаторов, такие как 'flat' и 'noperspective', которые могут быть размещены перед атрибутами, которые посылаются из VS в FS. Эти квалификаторы не могут быть использованы для членов структур. Решение, которое предоставляет GLFX - новое ключевое слово 'interface', которое позволяет то, что 'struct' не может. 'interface' может быть только передан между этапами шейдеров. Если вам требуется передать его целиком в другую функцию, то вы должны копировать его содержимое в структуру. Пример:
interface foo
{
flat int a;
noperspective float b;
};
struct bar
{
int a;
float b;
}
shader VSmain(out foo f)
{
// ...
}
void Calc(bar c)
{
// ...
}
shader FSmain(in foo f)
{
struct bar c;
c.a = f.a;
c.b = f.b;
Calc(c);
}
Внимание: 'interface' - это ключевое слово, зарезервированное на будущее (согласно OpenGL 4.2). Его использование в GLFX будет зависеть от изменений в спецификации OpenGL.
Совет: используйте 'glfxc' для проверки файлов эффектов
'glfxc' - это приложение, часть GLFX. Он проходит по файлу эффекта, компилирует его и сообщит об ошибках. Запускается от так:
glfxc <effect file name> <program name>
Код этого урока был изменен для работы с GLFX. Поскольку изменения незначительны, я не буду показывать их. Лучше посмотрите на исходники классов Technique и LightingTechnique. Кроме того, шейдеры, который используются в 'lighting_technique.cpp', были перемещены в файл эффекта, названный 'lighting.glsl' в папке 'shaders'. Этот файл содержат те же шейдеры; вы уже знакомы с ними. Они были слегка изменены согласно правилам выше.