OpenGL pixel and vertex shaders.
Автор: Сергей Маковкин
Часть I. Что такое шейдер?
Часть II. Вертексные программы
Инструкции и их применение
Заключение
Часть III. Пиксельные программы
Часть I. Что такое шейдер?
Чтобы понять, что такое шейдер, разберемся для начала, как видео карта рисует примитивы (треугольники, полигоны и др.) На вход поступают данные о каждой вершине примитива. Например, положение вершины в пространстве, нормаль и текстурные координаты. Эти данные называются вершинными атрибутами (vertex attributes). GPU на их основе вычисляет выходные значения: положение вершины в экранных координатах, цвет вершины, рассчитанный в зависимости от освещения и т.д. До выхода видео карт GeForce 3 и Radeon 8500 этот процесс был неуправляемым. Если вас, например, не устраивали те формулы, по которым считается освещение в OpenGL, и вы хотели применить свои, то ничего нельзя было поделать. Приходилось либо довольствоваться тем, что умеет GPU, либо выполнять расчеты для каждой вершины на процессоре, что намного медленнее. Решением этой проблемы стали вертексные программы (в Direct3D они называются вертексные шейдеры). Вертексная программа - это программа, написанная на специальном языке низкого уровня, которая выполняется на GPU и преобразует входные вертексные атрибуты в выходные, которые поступают на вход пиксельного шейдера. Важной особенностью вертексных и пиксельных программ является то, что все инструкции работают с векторами. Например, чтобы посчитать скалярное произведение, надо выполнить всего лишь одну инструкцию, а не 5 (2 сложения и 3 умножения), как на CPU. Благодаря этому можно выполнить массу операций небольшим числом инструкций. Например, умножение матрицы на вектор - всего 4 инструкции. А если инструкций мало, то скорость выполнения такой программы довольно высокая.
Значения, вычисленные в вертексном шейдере, интерполируются по треугольнику. На видео картах, не поддерживающих пиксельные шейдеры, для каждого пикселя определяется его цвет и цвет текстуры (или нескольких текстур) в данной точке. Потом эти цвета умножаются или складываются, в зависимости от параметров выполненной ранее функции glTexEnv(), и результат записывается в буфер кадра. Если же видео карта поддерживает пиксельные шейдеры, то все намного интереснее. Интерполированные по треугольнику значения поступают на вход некоторой программы, называемой пиксельным шейдером. Это программа, состоящая из ряда арифметических и других инструкций, рассчитывает цвет пикселя, который записывается в буфер кадра. По сравнения с вертексными программами, скорость выполнения пиксельных шейдеров намного выше. Можно почти моментально выполнять штук 10 векторных инструкций для каждого пикселя! На CPU такое сделать просто невозможно.
Пиксельные и вертексные шейдеры позволяют на аппаратном уровне создавать потрясающие эффекты: освещение на пиксельном уровне, bump mapping, отражение и преломление, волны на воде, скелетная анимация персонажей, тени и многое другое!
Часть II. Вертексные программы
OpenGL расширения.
Чтобы использовать вертексные программы на OpenGL, надо подключить соответствующее расширение. В таблице приведены видео карты и расширения, которые они поддерживают:
GeForce 3,4 | NV_vertex_program
NV_vertex_program1_1 ARB_vertex_program |
Radeon 8500 | EXT_vertex_shader |
Radeon 9700 | EXT_vertex_shader
ARB_vertex_program |
NV30 | NV_vertex_program
NV_vertex_program1_1 ARB_vertex_program NV_vertex_program2 |
Так как NV_vertex_program - самое первое расширение, которое позволило использовать вертексные шейдеры, и в то же время оно наиболее распространенное, то рассказывать я буду сначала о нем.
Быстро выполнять программу для каждого вертекса - это непростая задача для изготовителей GPU. Поэтому на вертексные программы наложены довольно сильные ограничения. Давайте посмотрим на сравнительную таблицу возможностей вершинных шейдеров:
GeForce 3,4 | Radeon 8500 | Radeon 9700 | NV30 | |
Максимальное число статических инструкций | 128 | 128 | 256 | 256 |
Число временных регистров | 12 | 12 | 32 | 16 |
Число констант | 96 | 96 | 256 | 256 |
Условные переходы | нет | нет | да | Да |
Переходы и циклы | нет | нет | заранее | Динамически! |
Первая программа на NV_vertex_program.
Вертексная программа представляет собой массив символов (строку). Хранить ее можно либо в исходном файле программы, либо в отдельном текстовом файле и загружать непосредственно во время выполнения программы.
Думаю, пора приступить к написанию нашего первого шейдера.
unsigned int vp_transform; char VP_Transform[] = "!!VP1.0" // compute position "DP4 o[HPOS].x, c[0], v[OPOS];" "DP4 o[HPOS].y, c[1], v[OPOS];" "DP4 o[HPOS].z, c[2], v[OPOS];" "DP4 o[HPOS].w, c[3], v[OPOS];" "MOV o[COL0], v[COL0];" "MOV o[TEX0], v[TEX0];" "END";
Пока не будем задумываться, что делает такой шейдер. Загрузим его.
glGenProgramsNV (1, &vp_transform); glLoadProgramNV ( GL_VERTEX_PROGRAM_NV, vp_transform, strlen( VP_Transform), ( const byte *) VP_Transform); if ( glGetError( ) != GL_NO_ERROR) printf( "Error loading vertex program vp_transform.\n"); glBindProgramNV ( GL_VERTEX_PROGRAM_NV, vp_transform); glTrackMatrixNV ( GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);
Надеюсь, что кроме функции glTrackMatrixNV, все понятно. Сначала генерируем идентификатор программы, потом загружаем программу, дальше делаем ее текущей. Все аналогично загрузке текстур в OpenGL. Вызов функции glTrackMatrix() означает, что при рисовании треугольников с использованием данного шейдера в константах с[0] - c[3] будет находиться текущая матрица преобразования, равная Projection * Modelview.
Теперь рисуем треугольники:
glEnable(GL_VERTEX_PROGRAM_NV); glBindProgramNV( GL_VERTEX_PROGRAM_NV, vp_transform); glBegin( GL_TRIANGLES); // ... glEnd( ); glDisable( GL_VERTEX_PROGRAM_NV);
Что произойдет в результате такой операции? Нарисуется треугольник заданного цвета (при помощи glColor или glColorPointer) с текстурой (если она была включена, и были заданы текстурные координаты).
В данном примере в константы c[0..3] автоматически записывались нужные значение. Если требуется записать туда что-то другое, то применяется функция glProgramParameter. Например, чтобы записать в константу c[20] значение {1, 1, 0.5, 0} надо вызвать следующую функцию:
glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 20, 1.0f, 1.0f, 0.5f, 0);
20 февраля 2002 (Обновление: 4 фев 2011)
Комментарии [1]