Войти
ПрограммированиеСтатьиГрафика

OpenGL pixel and vertex shaders. (3 стр)

Автор:

Часть III. Пиксельные программы

Введение.

Пиксельные программы (или пиксельные шейдеры) появились достаточно давно. Старенький первый GeForce поддерживает opengl расширение - GL_NV_register_combiners. С его помощью можно сделать освещение на пиксельном уровне с bump mapping. Это здорово, но число инструкций сильно ограничено. Реально больше 6-8 инструкций применить нельзя. Если быть точным, то там даже нет понятия "инструкция", а есть несколько стадий, на каждой из которых можно выполнить ряд операций.

С появление ускорителей следующего поколения - GeForce 3 и Radeon 8500, ситуация заметно улучшилась. Radeon 8500 поддерживает 16 арифметических и несколько текстурных инструкций. Можно даже делать  выборку из текстуры по (s,t,r,q) координатам, рассчитанным в шейдере (зависимая выборка, dependent read). Видео карта GeForce 3 способна на большее. Но в OpenGL использовать на 100% пиксельные шейдеры третьего GeForce не так-то просто. Он поддерживает два расширения: NV_texture_shader и NV_register_combiners. Используя оба расширения, можно достичь весьма большого числа инструкций на пиксель. Причем расширение NV_texture_shader позволяет выполнять floating point вычисления с 32-х битной точностью! Считаем скалярное произведение в текстурном шейдере, возводим N dot H в нужную степень, а потом в register combiners выполняем операции, которые не требуют высокой точности вычислений. Результат получается очень качественный. Вот скриншот из движка Almighty, демонстрирующей bump mapping, сделанный с помощью NV_texture_shader:
http://www.gamedev.ru/proj/images.php?id=1&img=32.

Все вроде здорово, но инструкций в шейдере явно не хватает, а программировать их непросто. Все запутанно и неправильно (по крайней мере в GL, в D3D чуть лучше). И вот выходит видео карта нового поколения - Radeon 9700. А с ней - новые пиксельные шейдеры, и все меняется. Ура! Возможности пиксельных программ на Radeon 9700 просто потрясающие, а скоро в продаже появится GeForce FX, который будет еще лучше!

В таблице приведены основные характеристики пиксельных шейдеров на видео картах последнего поколения на момент написания статьи:

 

Radeon 9700

GeForce FX

Максимальное число арифметических инструкций 

63   

1024*

Максимальное число текстурных инструкций   

31   

1024*

Число зависимых выборок из текстуры   

3   

неограниченно

Точность вычислений   

32-bit floating point   

32-bit floating point
или с фиксированной
запятой

* - суммарное число арифметических и текстурных инструкций не должно превышать 1024.

Единственное, что не появилось в новых пиксельных программах - это условные переходы. Но, думаю, и это ограничение максимум через год будет устранено.

Теперь приступим непосредственно к программированию. Я хочу познакомить вас с написанием пиксельных шейдеров под ARB_fragment_program. Это расширение поддерживается видео картой Radeon 9700 и, почти уверен, будет поддерживаться GeForce FX.

ARB_fragment_program

Для начала надо выяснить, сколько можно использовать в пиксельных программах временных регистров, инструкций, зависимых выборок и т.д. Делается это следующим образом:

int iparams[7];
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB, &iparams[0]);
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB, &iparams[1]);
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB, &iparams[2]);
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB, &iparams[3]);
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_ENV_PARAMETERS_ARB, &iparams[4]);
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEMPORARIES_ARB, &iparams[5]);
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_ATTRIBS_ARB, &iparams[6]);
    
printf("ARB_fragment_program is supported.\n"  
     "   ALU instructions:    %d\n"
     "   texture instructions: %d\n"
     "   texture indirections: %d\n"
     "   local parameters:      %d\n"
     "   env parameters:        %d\n"
     "   temp registers:          %d\n"
     "   attributes:                 %d\n",         
    iparams[0], iparams[1], iparams[2], iparams[3], iparams[4], iparams[5], iparams[6]);


ALU instructions - число арифметических инструкций.
Texture instructions - число текстурных инструкций.
Texture indirections - число зависимых выборок из текстуры.
Local parameters - локальные параметры (константы), передающиеся в пиксельный шейдер. Назначаются для каждой пиксельной программы отдельно.
Environment parameters - глобальные параметры (константы), передающиеся в пиксельный шейдер. Назначаются для всех пиксельных программ, используемых в вашем приложении.
Temp registers - число временных регистров.
Attributes - число атрибутов (4D-векторов), которые интерполируются по треугольнику.

В расширении ARB_fragment_program установлено минимальное количество каждого из параметров, описанных выше. Прямо из спецификации:


(14) What should the minimum resource limits be?
      RESOLVED: 10 attributes, 24 parameters, 4 texture indirections,
      48 ALU instructions, 24 texture instructions, and 16 temporaries.

Radeon 9700 поддерживает немного больше: 32 параметра, 63 ALU инструкции, 31 текстурных инструкции. Остальное так же.

Теперь пора написать первую пиксельную программу. Вот ее текст:

char FP_ARB_SimpleModulate[] =
"!!ARBfp1.0"

  "ATTRIB  iTex0    = fragment.texcoord[0];"
  "ATTRIB  iColor0  = fragment.color;"

  "OUTPUT  oColor    = result.color;"

  "TEMP  tmp;"

  "TXP    tmp, iTex0, texture[0], 2D;"
  "MUL    oColor, tmp, iColor0;"

  "END";

Загрузим эту программу:

unsigned int fp_arb_simplemodulate;

glGenProgramsARB  (1, &fp_arb_simplemodulate);
glBindProgramARB  (GL_FRAGMENT_PROGRAM_ARB, fp_arb_simplemodulate);
glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, 
                   strlen(FP_ARB_SimpleModulate), (const byte *)FP_ARB_SimpleModulate);

if (glGetError() != GL_NO_ERROR)
  printf("Error loading 'fp_arb_simplemodulate' fragment program.\n");

Рисуем треугольники:

glEnable(GL_FRAGMENT_PROGRAM_ARB);

glColor4f(1,1,1,1);
glBindProgramNV(GL_ FRAGMENT_PROGRAM_NV, fp_arb_simplemodulate);

glBegin(GL_TRIANGLES);
  // ...
glEnd();

glDisable(GL_FRAGMENT_PROGRAM_ARB);

В результате нарисуются треугольники с текстурой, умноженной на primary color (в данном случае цвет установлен командой glColor4f).

Замечание: При использовании пиксельных программ, команды glEnable(GL_TEXTURE_2D), glEnable(GL_TEXTURE_3D),
glEnable(GL_TEXTURE_CUBE_MAP_ARB) и glTexEnv() не имеют никакого смысла, так что их писать не надо!

Разберемся теперь с текстом самой пиксельной программы.

Первые две строки:

ATTRIB  iTex0    = fragment.texcoord[0];
ATTRIB  iColor0  = fragment.color;
- это объявление некоторых переменных и присваивание им значения. Эти переменные являются атрибутами, на что указывает слово ATTRIB. fragment.texcoord[0] - нулевые текстурные координаты, которые интерполируются по треугольнику. fragment.color - это primary color.
  OUTPUT  oColor    = result.color;

Это тоже переменная. В нее будет записан окончательный результат - цвет пикселя, который пойдет в буфер кадра.

  TEMP  tmp;

Объявление временной переменной, доступной для записи и для чтения. В ней можно хранить промежуточные результаты вычислений.

  TXP  tmp, iTex0, texture[0], 2D;

Это первая инструкция в программе. Она является текстурной инструкцией. В переменную tmp записывается значение цвета, извлеченного из нулевой 2D текстуры по координатам iTex0. Инструкция TXP извлекает пиксель из текстуры с перспективной коррекцией, т.е. по координатам (x/w, y/w) или, что одно и то же, (s/q, t/q). Есть другая инструкция - TEX, которая не выполняет перспективной коррекции.

  MUL  oColor, tmp, iColor0;

Инструкция MUL - арифметическая (ALU). В данном случае выполняет умножение переменной tmp на iColor0, и результат записываетcя в oColor.

В нашем примере производится считывание текстуры. Оно не является зависимым, т.к. значение iTex0 известно еще до начала выполнения пиксельной программы. Таких считываний может быть столько, сколько текстурных инструкций позволяет выполнять видео карта (на Radeon 9700 - 31 текстурная инструкция). А бывают зависимые считывания по координатам, рассчитанным в пиксельном шейдере. Таких считываний может быть ограниченное число. Перед загрузкой программы считается число зависимых выборок - texture indirections (перевести это словосочетание я не способен, словарь не помог). Если GPU не может выполнять такое количество зависимых считываний, то шейдер не загружается. В начале пиксельной программы texture indirection = 1. Не понимаю, почему не ноль. При каждой зависимой выборке texture indirection увеличивается на единицу.

Таким образом, если texture indirections = 4, как на Radeon 9700, то видео карта способна производить 3 зависимых выборки.

Например, в следующих программах texture indirection будет равен соответственно 1, 2 и 3, а число зависимых выборок 0, 1 и 2.

!!ARBfp1.0
# No texture instructions, but always 1 indirection
MOV result.color, fragment.color;
END

!!ARBfp1.0
# A simple dependent texture instruction, 2 indirections
TEMP myColor;
MUL myColor, fragment.texcoord[0], fragment.texcoord[1];
TEX result.color, myColor, texture[0], 2D;
END

!!ARBfp1.0
# A more complex example with 3 indirections
TEMP myColor1, myColor2;
TEX myColor1, fragment.texcoord[0], texture[0], 2D;
MUL myColor1, myColor1, myColor1;
TEX myColor2, fragment.texcoord[1], texture[1], 2D;
# so far we still only have 1 indirection
TEX myColor2, myColor1, texture[2], 2D;      # This is #2
TEX result.color, myColor2, texture[3], 2D;  # And #3
END

В ARB_fragment_program кроме обычных инструкций ADD, MUL, MAD и т.д. существуют немного отличающиеся варианты ADD_SAT, MUL_SAT, MAD_SAT. Добавка _SAT (от слова saturate) означает, что результат, который записывается в регистр-приемник, будет в диапазоне [0,1]. Т.е. если в результате вычисления получилось число 2.1, то в приемник будет записана 1. Если меньше нуля - то 0.

Еще стоит обратить внимание на инструкции SIN, COS и SCS, которая считает и sin и cos одновременно. Вычисление sin(x) и cos(x) за один такт, как вам, а? Правда я не уверен, что это делается за один такт, но точно знаю, что скорость выполнения инструкции SCS просто потрясающая. Благодаря этой инструкции можно делать поверхность воды с большим числом источников волн и маленькой длиной волны!

Вот и все :) Дальше можно ничего не объяснять, читайте спецификацию
(http://oss.sgi.com/projects/ogl-sample/registry/ARB/fragment_program.txt).
В ней вы найдете подробное описание всех инструкций. Я же буду говорить немного о более сложных и интересных вещах, чем просто формат и описание инструкций. В четвертой части этой статьи я расскажу о принципиально новом подходе к созданию освещения в играх - "все на пиксельном уровне". Именно это и было целью данной статьи. Я попытаюсь описать, как должно быть реализовано освещение в играх, которые выйдут в течение следующих 2-3 лет. Может это покажется каким-то безумием или технологией далекого будущего, но это совсем не так.

Страницы: 1 2 3

#OpenGL, #шейдеры

20 февраля 2002 (Обновление: 4 фев. 2011)

Комментарии [1]