С введением GLSL в OpenGL появилось новое понятие - хэндл (handle), и, соответственно, новый префикс glCreate* к функциям, которые заведуют созданием хэндлов.
Итак, объявляем нужные переменные:
var
po: GLhandleARB; // программный объект (program object)
vs: GLhandleARB; // вершинный шейдер
fs: GLhandleARB; // фрагментный шейдер
u_angle: GLuint; // адрес uniform-переменной 'u_angle'
a_phase: GLuint; // адрес attribute-переменной 'a_phase'
u_texture: GLuint; // адрес uniform-переменной 'u_texture'
Для проверки ошибок и вывода лога создадим процедуру CheckGLSLError;
procedure CheckGLSLError(Handle: GLhandleARB; Param: GLenum);
var
l, glsl_ok: GLint;
s: PChar;
begin
CheckGLError; // Проверяем ошибку OpenGL (см. демку к статье).
glGetObjectParameterivARB(Handle, Param, @glsl_ok); // Получаем успешность операции // 'Param' для хэндла 'Handle'.
s := StrAlloc(1000);
glGetInfoLogARB(Handle, StrBufSize(s), @l, PGLcharARB(s)); // Получаем лог последней операции// над хэндлом 'Handle'.
Writeln(s); // Вывод лога на экран.
Assert(glsl_ok = GL_TRUE, 'glSLang error');
end;
Программный объект - это контейнер для различных по функциональности шйдеров, он нужен для линковки их друг с другом. Создаем его:
po := glCreateProgramObjectARB;
// Теперь создадим сами шейдеры.
vs := glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
CheckGLError;
fs := glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
CheckGLError;
Укажем для шейдеров текст программ:
procedure LoadSources;
var
src: PChar; // PGLcharARB = PChar;
len: GLuint;
begin// Загружаем текст вершинного шейдера.
src := PChar(LoadSourceFromFile('Shaders\GLSL-demo.vert'));
len := StrLen(src);
glShaderSourceARB(vs, 1, @src, @len);
CheckGLError;
// Загружаем текст фрагментного шейдера.
src := PChar(LoadSourceFromFile('Shaders\GLSL-demo.frag'));
len := StrLen(src);
glShaderSourceARB(fs, 1, @src, @len);
CheckGLError;
end;
где:
shaderObj - хэндл нужного шейдера
count - число строк в массиве
string_ - указатель на массив строк
len - указатель на массив длин строк
Вызов glShaderSource принимает указатель на массив указателей на строку (GLcharARB **string_). Переменная string_ может быть null-terminated, в этом случае длину строки нужно ставить в -1. Чаще всего, используют одноэлементный массив, т.е. указатель на указатель на строку, соотв. так же и с длиной строки.
// Компилируем шейдеры:
glCompileShaderARB(vs);
CheckGLSLError(vs, GL_OBJECT_COMPILE_STATUS_ARB);
glCompileShaderARB(fs);
CheckGLSLError(fs, GL_OBJECT_COMPILE_STATUS_ARB);
// Затем присоединяем шейдеры к программному объекту:
glAttachObjectARB(po, vs);
glAttachObjectARB(po, fs);
Присоединять к программному объекту можно несколько шейдеров одного типа, т.е. функция, используемая в одном шейдере, может быть реализована в другом, однако, перед использованием этой функции нужно объявить ее прототип. Шейдерные функции связываются при линковке.
// Линкуем шейдеры в программном объекте (при линковке varying в vs // соединяются с varying в fs, объединяются общие uniform'ы).
glLinkProgramARB(po);
CheckGLSLError(po, GL_OBJECT_LINK_STATUS_ARB);
// Проверяем слинкованные шейдеры на предмет различных falls - в идеале эта функция в логе // должна возвращать различные советы по улучшении оптимизации текста шейдер, но опять таки,// это зависит от вендора и реализации.
glValidateProgramARB(po);
CheckGLSLError(po, GL_OBJECT_VALIDATE_STATUS_ARB);
Получаем адрес нужных uniform-, varying-, и attribute-переменных:
А почему бы не обращаться по имени? Парсинг имени и поиск по строке каждый раз при обращении к uniform'у - это не самый лучший вариант по производительности, поэтому и было сделано через индексы.
Все. Инициализация и установка GLSL-шейдеров закончена, переходим к собственно рендерингу сцены:
glUniform1fARB(u_angle, angle); // Пишем в 'u_angle' значение переменной angle.
CheckGLError;
angle :=angle + angleStep; // angle принимает значения от 0 до 2*Pi,// с каждым кадром увеличиваясь на angleStep.if angle >2*Pi then angle := 2*Pi - angle;
glUniform1iARB(u_texture, 0); // Пишем в 'u_texture' номер используемого tiu, // в данном случае 0. Разумеется, до этого нужно установить // текстурный образ и параметры фильтрации, разрешать// GL_TEXTURE_2D не обязательно.
CheckGLError;
glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0);
glTexCoord2f(0.0, 0.0);
glVertexAttrib1fARB(a_phase, 0.0); // Пишем в 'a_phase' требуемую фазу для вершина,// в данном случае 0.
glVertex3f(-10.0, -10.0, -30.0);
glColor3f(0.0, 1.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertexAttrib1fARB(a_phase, 1.0); // Пишем в 'a_phase' требуемую фазу для вершина,// в данном случае 1.0.
glVertex3f(10.0, -10.0, -30.0);
glColor3f(0.0, 0.0, 1.0);
glTexCoord2f(0.5, 1.0);
glVertexAttrib1fARB(a_phase, 2.0); // Пишем в 'a_phase' требуемую фазу для вершина,// в данном случае 2.0.
glVertex3f(0.0, 10.0, -30.0);
glEnd;
Все. Запускаем и наслаждаемся работающим GLSL-шейдером.
Заключение
Вполне ожидаемый вопрос - достаточно ли мощен и нов GLSL, и стоит ли переходить на него? Думаю, да. На самом деле, реальных альтернатив GLSL нет, так как Cg - это лишь полумера, некая надстройка над OpenGL в попытке объединить существующие шейдеры в язык высокого уровня, и уменьшить количество vendor-specific кода. Тем более, что GLSL является неотъемлемой частью грядущего OpenGL 2.0, призванного стать новым этапом в неувядающей гонке с D3D ;)
Кроме того:
1. Для GLSL придуман удобный, практичный, расширяемый шейдерный framework. Т.е. кроме vs/ps, туда легко вписать texture shaders, pixel shaders (т.е. шейдеры, работающие с передачей пиксельных данных, например, функцией glTexImage; и, с дальнейшим развитием железа, geometry shaders, или surface shaders (по типу Stanford Shading Language).
2. Компиляция GLSL-шейдеров возложена на драйвер, поэтому не требуются внешние библиотеки (как в Cg). Из-за высокоуровневости языка можем получить более оптимизированный код, чем при использовании ассемблероподобных шейдеров, так как у компилятора в этом случае больше простора для творчества ;)
Демка к статье (с исходным кодом на Delphi) - 20040102.rar. Требуемые dll для запуска демки (devil.dll, ilu.dll, ilut.dll, glut32.dll) - 20040102_1.rar.
1. Чтение из текстуры в вершинном шейдере, функция шума и циклы не реализованы в текущих драйверах (ATI Catalyst 3.10).
2. При компиляции шейдера с комментариями на русском языке в кодировке Windows-1251 компилятор выдавал ошибку 'pre-mature EOF', поэтому я использовал DOS-кодировку.