Elite Games - Свобода среди звезд!

Уроки для программистов - Шейдеры

Глава 8. Шейдеры

Сейчас мы поговорим о шейдерах. С их помощью делают все современные спецэффекты. Но что же это?

Когда вы рисуете что-либо, вы отправляете в видеокарту информацию о вершинах. Но контролировать дальнейший процесс вы не можете. Видеокарта сама высчитывает цвет каждого пикселя, положение вершин в экранном пространстве...

Шейдеры представляют нам уникальную возможность добраться и туда. Шейдеры – микропрограммы (кстати, в OpenGL они называются именно программами) на ассемблероподобном языке видеокарты. Существуют два типа шейдеров:

- Пиксельный (Фрагментный в OpenGL).
- Вершинный.

http://www.gamedev.ru/articles/read.shtml?id=20104&page=1
http://www.gamedev.ru/community/opengl/articles/vp

Здесь очень много полезной информации, ознакомьтесь с ней, прежде чем читать дальше.


Вершинный вызывается для каждой вершины. Ему на вход поступает координата вершины в мировой системе координат, текстурные координаты, нормали и еще много разных параметров (в том числе и любые другие, которые вы захотите, их можно передать с помощью специальной команды — glProgramLocalParameter4fARB). Скорость выполнения вершинного шейдера на процессоре видеокарты (GPU) довольно высока, по крайней мере, превышает скорость CPU в разы. Но пиксельные шейдеры – вот настоящая скорость...

Пиксельный вызывается для каждого (внимание!) пикселя. Скорость его настолько высока, что у меня, например, на GeForce 6600 пиксельный шейдер, на котором сделано гауссово размытие, замедляет счетчик FPS всего лишь на 20 кадров в секунду. Ни один CPU не сравнится по скорости с GPU.
Пиксельному шейдеры поступают на вход измененные данные из вершинного шейдера и из вашей программы (с помощью той же glProgramLocalParameter4fARB), а на выход должен выйти лишь один параметр – цвет пикселя (в новых версиях шейдеров это не совсем так).

Существует много версий шейдеров, но некоторые только для, например, GeForce, некоторые для Radeon`ов (на самом деле родные шейдеры у радеонов это кошмар).
Но мы остановимся на универсальных – ARB шейдерах. Правда за универсальность придется платить – на, например, GeForce 2 нет пиксельных ARB шейдеров, зато есть родные и Register Combiners`ы (что-то вроде пиксельных шейдеров, были даже на Riva TNT). Поэтому желательно делать разные шейдеры, выполняющие одно и то же. По крайней мере, так делают во всех крупных проектах.

Значит, для начала загрузите два расширения:

- GL_ARB_fragment_program
- GL_ARB_vertex_program

Потом вам нужно загрузить шейдер из файла, делается так:

function LoadASMShader(target: Cardinal; filename: String): Cardinal;
var
txt: TStringList;
err: String;
i: Integer;
s: Cardinal;
begin

txt := TStringList.Create;
txt.LoadFromFile(filename);

glGenProgramsARB(1, @ s);
glBindProgramARB(target, s);
glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, Length(txt.Text),
PChar(txt.Text));

txt.Free;

err := PChar(glGetString(GL_PROGRAM_ERROR_STRING_ARB));
if err <> '' then
begin
glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, @ i);
raise Exception.CreateFmt('Shader program «%s» contains errors:' + #10#10 + err + #10#10 + ' (pos %d)', [ExtractFileName(filename), i]);
end;

Result := s;

end;

Это не сильно оптимальная загрузка, т.к. используется TStringList.
Функция возвращает Handle шейдера, который будет использован далее. Вот список основных команд для работы с шейдером:

glGenProgramsARB(1, @ Handle);
сгенерировать Handle

glBindProgramARB(target, Handle);
сделать шейдер текущим::/b::
где target или GL_VERTEX_PROGRAM_ARB, или GL_FRAGMENT_PROGRAM_ARB

glDeleteProgramsARB(1,@ Handle);
удалить Handle

glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, Length(txt.Text), PChar(txt.Text));
загрузить шейдер из строки
GL_PROGRAM_FORMAT_ASCII_ARB – формат текста
где target или GL_VERTEX_PROGRAM_ARB, или GL_FRAGMENT_PROGRAM_ARB

glEnable(target);
нужно включить шейдеры
где target или GL_VERTEX_PROGRAM_ARB, или GL_FRAGMENT_PROGRAM_ARB

glProgramLocalParameter4fARB(target, n, p1,p2,p3,1);
передать в шейдер что-то своё
где target или GL_VERTEX_PROGRAM_ARB, или GL_FRAGMENT_PROGRAM_ARB
n – порядковый номер. Будет использован уже в шейдере
p1,p2,p3 – параметры, которые передаём.


Механизм шейдеров похож на механизм текстур.
— включаем шейдеры
— делаем шейдер текущим
— рисуем
— выключаем шейдеры

Теперь, как же выглядят эти шейдеры?

Вот вершинный:

!!ARBvp1.0
ATTRIB inPos = vertex.position;
ATTRIB inTex = vertex.texcoord[0];

OUTPUT outPos = result.position;
OUTPUT outTex = result.texcoord[0];

PARAM mvp[4] = { state.matrix.mvp };

DP4 outPos.x, mvp[0], vec;
DP4 outPos.y, mvp[1], vec;
DP4 outPos.z, mvp[2], vec;
DP4 outPos.w, mvp[3], vec;

MOV outTex, inTex;

END


Итак, приступим к разбору...

!!ARBvp1.0 – заголовок шейдера, сообщающий нам об использовании вершинного (vp) ARB шейдера версии 1.0

ATTRIB inPos = vertex.position;
Значит ATTRIB –это обозначение входных данных
inPos – название, просто чтоб было проще
vertex.position – это параметр вершини – его координаты в мировом пространстве.

ATTRIB inTex = vertex.texcoord[0];
Тут то же, но только
vertex.position – это параметр вершини – его текстурные координаты.


OUTPUT outPos = result.position;
OUTPUT outTex = result.texcoord[0];


OUTPUT – исходящие данные (в пиксельный шейдер)
result.position – сюда пишется уже трансформированные координаты.
result.texcoord[0] — пишется уже трансформированные текстурные координаты.
(да, трансформировать или нет, решать вам)

PARAM mvp[4] = { state.matrix.mvp };

PARAM – параметр, который передается в шейдер из вашей программы или явно (с помощью glProgramLocalParameter4fARB), или вот как этот – неявно. state.matrix.mvp – это матрица mvp, т.е. матрица modelview projection, если на неё умножить входную вершину (в мировых координатах), то получим вершину в координатах экрана.

DP4 outPos.x, mvp[0], vec;
DP4 outPos.y, mvp[1], vec;
DP4 outPos.z, mvp[2], vec;
DP4 outPos.w, mvp[3], vec;


Это, собственно, и есть умножение. Если не понятно, перечитайте статью, ссылка на которую есть выше.

Для простоты понимания можно представить себе это так

::/b::outPos.x := DP4(mvp[0], vec);
outPos.y := DP4(mvp[1], vec);
outPos.z := DP4(mvp[2], vec);
outPos.w := DP4(mvp[3], vec);

Но к сожалению это не имеет смысла в синтаксисе шейдеров.

::b::MOV outTex, inTex;


Это просто передает текстурные координаты далее в пиксельный шейдер, без изменения.
Что-то вроде outTex := inTex;

Да еще кое что об особенностях записи/чтения в переменные...

ATTRIB – только читать из них

OUTPUT – только писать в них

PARAM – только читать

TEMP – и читать и писать




Вот пиксельный:


!!ARBfp1.0
ATTRIB Tdecal = fragment.texcoord[0];

TEMP n;

TEX n, Tdecal, texture[0], 2D;

MOV result.color, n;
END


!!ARBfp1.0 – заголовок шейдера, сообщающий нам об использовании пиксельного (fp) ARB шейдера версии 1.0

ATTRIB Tdecal = fragment.texcoord[0];
Мы берем на вход текстурные координаты. Это переменная Tdecal

TEMP N;
Объявляем временную переменную N

TEX N, Tdecal, texture[0], 2D;
Записываем в неё цвет, взятый из 2D текстуры, которая находится в нулевом слоте текстур (glActiveTextureARB(GL_TEXTURE0ARB) задает нулевой слот; по умолчанию он включен) и по текстурным координатам Tdecal.
Т.е. команда TEX производит считывание из текстуры.

MOV result.color, N;
Помещаем пиксель из текстуры в конечный цвет.


Это довольно примитивные шейдеры, но они показывают основы работы.
Кроме ARB шейдеров существуют и высокоуровневые шейдеры. Когда вышел cg – один из первых таких шейдеров он упростил жизнь многим игровым программистам (в том числе и решил проблемы с совместимостью). Но когда у DirectX вышел его высокоуровневый шейдер HLSL многие всерьёз задумались о судьбе OpenGL. Однако совет ARB выпустил GLSL – шейдерный язык, который уровнял возможности этих API (хотя я считаю, что GLSL обгоняет HLSL намного). В GLSL встроена информация об стейтах OpenGL, есть возможность делать циклы, условные переходы, структуры, функции, массивы и т.д. Но что самое интересное, что в зарезервированных словах, для будущих версий, есть, например, class (ООП на шейдерах??).

Я не буду особо углубляться в GLSL. Но основу опишу.

Расширения следующие:
GL_ARB_shader_objects
GL_ARB_shading_language_100
GL_ARB_vertex_shader
GL_ARB_fragment_shader


У GeForce это где-то с 6 серии (5 FX не особо дружат даже с ARB).

Итак, у шейдеров GLSL тоже есть Handle.

glUseProgramObjectARB(Handle);
установить текущий
glUseProgramObjectARB(0);
перестать использовать шейдеры

Чтоб передать свои параметры в шейдер нужно использовать Uniform
Псевдокод:
glUniform4fARB(shader.GetUniformByName('blur_offset'+ #0).Handl, 0, 0, 0, 1);

Теперь посмотрим на шейдеры на GLSL.

Вершинный:

void main(void)
{
gl_Position = ftransform();
gl_TexCoord[0] = gl_MultiTexCoord0;
}


Пиксельный:

uniform sampler2D image;
void main(void)
{

// Sample the center pixel:
vec4 color = texture2D(image, gl_TexCoord[0].xy);

gl_FragColor = color;
}

Это тот же шейдер, что и в примере для ARB. Все предельно просто. ftransform-это специальная команда, говорящая что мы ничего не будем делать с вершинами.

Теперь с полученными знаниями ваша задача внедрить шейдеры себе в движок.
Jurec
К началу раздела | Наверх страницы Сообщить об ошибке
Уроки для программистов - Шейдеры
Все документы раздела: Для тех, кто хочет писать игры | Движок на OpenGL | Создание игр в Game Maker | Bump mapping | Использование Direct Input | XNA framework |


Дизайн Elite Games V5 beta.18
EGM Elite Games Manager v5.17 02.05.2010