Пишем GLSL шейдеры в RenderMonkey - Specular Lighting + Heat Haze

 

Здравствуйте всем!

Сегодня мы рассмотрим с Вами в одном уроке сразу два эффекта, широко применяемых при создании современных игр – это попиксельное бликовое освещение (specular lighting) и эффект искажения сцены как от горячего воздуха (или heat haze). И реализовывать все это мы будем в такой замечательной среде как ATI Render Monkey. При написании статьи использовалась версия 1.6.

Итак приступим. Начнем с того что создадим новый эффект. Для этого кликаем правой кнопкой мыши на Effect Workspace и в меню Add Effect Group выбираем пункт Effect Group w/ OpenGL Effect. Теперь переименуем наш эффект, например OpenGL_HeatHaze. Сделать это можно выделив эффект, и в его контекстном меню выбрать пункт Rename, или просто нажать F2. Теперь Вы должны видеть примерно такую картину.

Теперь раскроем наш эффект. Вы видите что он включает в себя StreamMapping, Model и Pass 0. StreamMapping это параметры определяющие атрибуты вершины, по умолчанию вершина содержит следующие атрибуты: позиция ( position ) [ vec3 ], нормаль к вершине ( normal ) [ vec3 ] и текстурные координаты ( texcoord ) [ vec2 ]. Вы имеете право изменять эти данные, добавлять новые ( например касательные вектора – тангент и бинормаль ), менять и размерность и т.д. Model – как ясно из названия, это трехмерная модель, использующаяся для рендеринга. И наконец Pass 0 – это проход рендеринга, коих может быть несколько. В свою очередь проход может содержать в себе ссылки на параметры атрибутов ( StreamMapping ), модели, тектстуры, камеры и др.
В первом проходе мы будем отрисовывать модель используя бликовую модель освещения Блинна соответствуя следующей формуле:

color = Ia * Ca + Id * Cd + Is * Cs;

Где I – интенсивность, C – цвет, a – фоновая ( ambient ) составляющая, d – рассеянная ( diffuse ) составляющая, s – бликовая ( specular ) составляющая освещения;
Фоновая составляюшая у нас будет константной. Диффузная будет задаваться текстурой и освещением данной точки, которое вычисляется формулой

D = max( dot( L, N ), 0.0 ) * texture;

где L – единичный вектор направления на источник света, N – нормаль в данной точке. В нашем примере мы будем брать нормаль в вершине и интерполировать ее вдоль всего примитива. Функция max будет предохранять нас от появления «отрицательной» освещенности, когда dot( L, N ) < 0;

Бликовую составляющую мы будем вычислять по следующей формуле:

S = ( max( dot( N, H ), 0.0 ) ^ P ) * C;

где N – нормаль в данной точке, H – полувектор направления на источник света и на камеру, который при условии единичности векторов находится по формуле

H = normalize( L + V );

где L – единичный вектор направления на источник света, V – единичный вектор направления на камеру.

P – показатель степени в которую возводится скалярное произведение; чем он больше – тем ярче и резче блик.
И наконец C – цвет блика.

Фух-х-х, с теорией закончили, перейдем к практике. Для начала поменяем модель шарика на модель скажем слоника =). Для этого найдем в нашем эффекте ( не в проходе! ) кликнем правой кнопкой мыши на пункте Model и в меню Change Model найдем ElephantBody.3ds; Теперь перед нами симпатишный слоник =). Насколько я помню, слоны в жизни не блестят, но зато блестит паркет, поэтому давайте покроем нашего слоника новеньким паркетом. Для этого добавим к нашему эффекту текстуру. Для этого кликнем на эффекте правой кнопкой мыши и выберем пункт Add Texture -> Add 2D Texture -> Wood.dds . Засада подумали Вы, слоник не изменился! И действительно, для того чтобы слоник стал паркетным этого мало, нужно добавить в проход Текстурный Объект ( Texture Object ) а также немного изменить шейдеры.
Для этого кликнем на нашем проходе правой кнопкой мыши и выберем пункт Add Texture Object. Назовем его например diffuseMap, раскроем его, кликнем правой кнопкой мыши на строке baseMap и выберем пункт Reference Node -> Wood. Текстурный объект создан. Зададим теперь все необходимые параметры для шейдеров. Параметры могут быть как стандартными ( определены самим Render Monkey, Predefined ), например положение камеры, прошедшее время и т.д., так и задаваемыми самим пользователем, например цвет, коэффициенты и т.д. Давайте добавим стандартный параметр, определяющий положение камеры. Для этого кликнем на эффекте правой кнопкой мыши и выберем пункт Add Variable -> Float -> Predefined -> vViewPosition. Это мы положение нашей камеры. Теперь зададим свои параметры. Зададим параметр отвечающий за вклад фонового освещения. Для этого кликнем правой кнопкой мыши на эффекте и выберем Add Variable -> Float -> Float. Это создаст параметр типа Float. Давайте назовем его ambientI, кликнем два раза и зададим значение 0.2. Теперь зададим позицию источника света, выберем Add Variable -> Float -> Float3, назовем его lightPosition зададим ему значения 0.0, 100.0, 100.0. Теперь добавте самостоятельно еще такие параметры:
specPower – Float = 32.0 ( сила блика )
ambientColor и specColor – Color ( цвет фонового освещения и бликов ), значения на свое усмотрения. Я установил для фонового светло-серый цвет, а для бликов чистый белый.
С параметрами мы разобрались, осталось запрограммировать шейдеры. Кликнем 2 раза на строке Vertex Program и наберем такой вот текст шейдера:

uniform vec3 lightPosition;
uniform vec4 vViewPosition;
 
varying vec3 normal;
varying vec3 lightDir;
varying vec3 vh;
varying vec2 tc0;
 
void main(void)
{
   vec3 pos    = gl_Vertex.xyz;
   lightDir    = normalize( lightPosition - pos );
   vec3 eyeDir = normalize( vViewPosition.xyz - pos );
   vh          = normalize( lightDir + eyeDir );   
   normal      = gl_NormalMatrix * gl_Normal;
 
   gl_Position = ftransform();
   tc0         = gl_MultiTexCoord0.xy;
}

а для Fragment Program:

uniform float     ambientI;
uniform vec4      ambientColor;
uniform float     specPower;
uniform vec4      specColor;
uniform sampler2D diffuseMap;
 
varying vec3 normal;
varying vec3 lightDir;
varying vec3 vh;
varying vec2 tc0;
 
void main(void)
{
   vec3 n = normalize( normal );
 
   vec4 ambient  = ambientI * ambientColor;
   vec4 diffuse  = max( dot( normalize( lightDir ), n ), 0.0 ) * texture2D( diffuseMap, tc0 );
   vec4 specular = pow( max( dot( n, normalize( vh ) ), 0.0 ), specPower ) * specColor;
   gl_FragColor  = ambient + diffuse + specular;
}

Теперь сохраните все выбрав File->Save ( CTRL+S ) и откомпилируйте шейдеры нажав

В результате наших шаманизмов Вы должны видеть примерно это.

Вот, симпатишный паркетный слоник, натертый до блеска. Хм... А что если его поджечь? Попробуем! =)

Изменим наш проход так, чтобы он рисовался в текстуру. Для этого добавим в эффект специальную текстуру – Renderable Texture. Кликнем правой кнопкой мыши на эффекте и выберем пункт Add Texture -> Add Renderable Texture. Чтобы теперь проход рисовался в эту текстуру добавим в него Render Target кликнув на нем правой кнопкой мыши и выбрав Add Render Target -> renderTexture.

Для получения конечного результата нам нужно добавить в эффект второй проход. Кликнем правой кнопкой мыши на эффекте и выберем Add Pass. Также наш второй проход должен отрисовывать текстуру на весь экран, для этого добавим в эффект новую модель, кликнув на нем правой кнопкой мыши и выбрав пункт Add Model -> ScreenAlignedQuad.3ds. Теперь рассмотренными выше способами изменим ссылку Model на нашу вновь добавленную модель. Получится примерно такая картина:

Также добавим 2D-текстуру distortion.tga в эффект и текстурный объект noise на нее ссылающийся в проход Pass 1. Добавим также в этот проход тектстурный объект elephant ссылающийся на тектуру renderTexture созданную в первом проходе. Еще добавим два своих параметра типа Float:
distortPower = 0.0532; ( коэффициент искажения )
hazeSpeed = 0.05; ( скорость движения текстуры искажения )
И один Predefined параметр fTime0_X; ( прошедшее время )

Все, необходимые параметры заданы. Теперь немного теории: в первом проходе мы отрисовываем сцену в текстуру. Во втором проходе нам нужно ее исказить по формуле:

result = tex2D( RT, newTC );
newTC =  tex2D( noise, T * speed ) * P + TC;

Где RT – Render Target текстура заполняющаяся в первом проходе.
noise – шумовая текстура.
T – прошедшее время.
speed – скорость перемещения шумовой текстуры.
P – коэффициент искажения.
TC – оригинальные текстурные координаты.

Теперь запрограммируем шейдеры:

Vertex Program:

varying vec2 tc0;
 
void main(void)
{
   vec2 screenPos = sign( gl_Vertex.xy );
 
   gl_Position = vec4( screenPos, 0.0, 1.0);
   tc0         = screenPos * 0.5 + 0.5;
}

Здесь мы располагаем наш квадрат так, чтобы он занимал весь экран. Функция sign(x) возвращает следующие значения:
-1 если x < 0 0 если х = 0 1 если x > 0
И переводим текстурные координаты из [-1,1] в [0,1].

Fragment Program:

uniform sampler2D elephant;
uniform sampler2D noise;
uniform float     distortPower;
uniform float     fTime0_X;
uniform float     hazeSpeed;
 
varying vec2 tc0;
 
void main(void)
{
   vec4 n = 2.0 * texture2D( noise, tc0 + vec2( 0.0, fTime0_X ) * hazeSpeed ) - 1.0;
   vec2 newTC = n.xy * distortPower + tc0;
 
   gl_FragColor = texture2D( elephant, newTC, 2.0 );
}

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

Проект RenderMonkey:
OGL_HeatHaze.zip (1952)

  5 Ответов в “Пишем GLSL шейдеры в RenderMonkey - Specular Lighting + Heat Haze”

  1. Огромное спасибо за статью! очень помогла с пониманием шейдеров!
    уааа, вы даже не представляете, как я рад тому, что у меня получается )

    • Очень рад что смог Вам помочь.
      Желаю успехов в освоении удивительного мира компьютерной 3D-графики.

  2. Да, действительно, отличная статья. Для тех, кто не знаком с RenderMonkey, может послужить хорошим вводом. Жаль, только, что GLSL'у и матану, окружающему его, в несколько кликов не научишься :)

    • А можете посоветовать как GLSL изучать ?

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

Оставить комментарий на Maxim Отменить ответ

(required)

(required)

Вы можете использовать HTML теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2011 3D-Orange.com.ua
e-mail me

3D-Orange.com.ua is proudly powered by WordPress.
Suffusion theme by Sayontan Sinha