Формулы этого урока взяты из раздела «The Mathematics of Lighting» книги «OpenGL
Programming Guide», aka «The Red Book». Мы начнем с рассеянного света. Рассеянный свет в OpenGL подразумевает, что свет падает на полигоны с одинаковой интенсивности независимо от положения зрителя. Его интенсивность пропорциональна отношению диффузной интенсивности света и коэффициенту диффузного отражения материала. Интенсивность также пропорциональна углу между направлением света и нормалью полигона.

Следующая формула используется OpenGL для рассчета рассеянного света:

Где ‘I’ — интенсивность отражения (reflection), Ld = диффузный цвет источника (gl_LightSource[0].diffuse), и Md — диффузный коэффициент материала (gl_FrontMaterial.diffuse).

Эта формула известна как «Lambertian Reflection». «Закон косинуса Ламберта» говорит, что яркость диффузного света на поверхности плоскости пропорциональна косинусу угла между лучем света и нормалью плоскости. Эта формула была выведена более двухсот лет назад (Johann Heinrich Lambert, 1728-1777)!

Чтобы применить эту формулу, вершинный шейдер будет использовать параметры света, а именно его позицию и диффузную интенсивность. Также шейдер будет использовать диффузные параметры материала. Для использования этого шейдера просто установить свет в OpenGL, как делали это раньше. Однако имейте в виду, что так как мы не используем фиксированный функционал, нет необходимости включать в OpenGL освещение (glEnable…).

Так как нам нужно вычислить косинус, сначала убедимся, что векторы нормали и направления света (gl_LightSource[0].position) нормализованы, а затем используем формулу dot product для получения косинуса. Учтите, что OpenGL хранит направление света как вектор от вершины к источнику света, что противоречит вышеприведенному рисунку.

OpenGL хранит направление света в координатах нашего «взгляда», поэтому нам нужно трансформировать нормаль в координаты «взгляда», чтобы рассчитать dot product. Чтобы трансформировать координаты нормали будем использовать зарезервированную uniform-переменную mat3 gl_NormalMatrix. Эта матрица — инверсия в верхне-левую подматрицу 3х3 из матрицы моделей.

Следующий вершинный шейдер показывает GLSL-код для всего этого:

    void main() {

vec3 normal, lightDir;
vec4 diffuse;
float NdotL;

/* сначала трансформируем нормаль в нужные координаты и нормализуем результат */
normal = normalize(gl_NormalMatrix * gl_Normal);

/* Теперь нормализуем направление света. Учтите, что согласно спецификации
OpenGL, свет сохраняется в пространстве нашего взгляда. Также, так как мы
говорим о направленном свете, поле «позиция» — это и есть направление. */

lightDir = normalize(vec3(gl_LightSource[0].position));

/* вычислим косинус угла между нормалью и направлением света. Свет у нас
направленный, так что направление — константа для каждой вершины. */

NdotL = max(dot(normal, lightDir), 0.0);

/* вычисляем диффуз */
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;

gl_FrontColor =  NdotL * diffuse;

gl_Position = ftransform();
}

 

Теперь всё, что остаётся на долю пикселного шейдера — установить цвет пикселов, используя varying-переменную gl_Color:

    void main()
{
gl_FragColor = gl_Color;
}

 

Следующее изображение показывает этот шейдер, применённый на чайнике. Заметьте, что низ чайника очень темный. Это потому, что мы не учитываем мировое (ambient) освещение, которое можно установить в OpenGL.

Собственно, мировое освещение рассчитать легко. Есть глобальные ambient-свет и «легкий» ambient-свет. Формула окружающего света:

В вершинный шейдер придется внести некоторые изменения для расчета ambient:

    void main()
{
vec3 normal, lightDir;
vec4 diffuse, ambient, globalAmbient;
float NdotL;normal = normalize(gl_NormalMatrix * gl_Normal);
lightDir = normalize(vec3(gl_LightSource[0].position));
NdotL = max(dot(normal, lightDir), 0.0);
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;

ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
globalAmbient = gl_LightModel.ambient * gl_FrontMaterial.ambient;

gl_FrontColor =  NdotL * diffuse + globalAmbient + ambient;

gl_Position = ftransform();
}

 

Следующее изображение демонстрирует результат:

В следующем разделе будет описано specular-освещение.