В этом уроке мы модифицируем предыдущие шейдеры для рассчета направленного света на каждый пиксел.
Мы разделим работу на два шейдера, чтобы рассчет освещения происходил для каждого пиксела.

Информация, которую мы получаем для каждой вершины:

  • Нормаль
  • Полу-вектор
  • Направление света

Нам нужно трансформировать нормаль в координаты нашей камеры и нормализовать её. Также нужно нормализовать полу-вектор и вектор направления света, которые уже в пространственных координатах камеры. Эти нормализованные векторы должны быть интерполированы, а затем посланы пикселному шейдеру, поэтому нам нужно объявить varying-переменную для их хранения.

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

Вершинный шейдер в этом случае может быть таким:

    varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;void main()
{
/* Сначала трансформируем нормаль в координаты камеры,
затем нормализируем результат */

normal = normalize(gl_NormalMatrix * gl_Normal);

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

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

/* Нормализуем полу-вектор перед передачей в пикселный шейдер */
halfVector = normalize(gl_LightSource[0].halfVector.xyz);

/* Рассчитываем diffuse, ambient иglobalAmbient значения */
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
ambient += gl_LightModel.ambient * gl_FrontMaterial.ambient;

 

Теперь пикселный шейдер. Должны быть обьявлены некоторые varying-переменные. Нам нужно будет снова нормализировать нормаль. Заметьте, что нормализировать направление света ещё раз нет необходимости. Его вектор одинаков для всех вершин, так как мы говорим о направленном свете.
Интерполяция между двумя равными векторами даст тот же вектор, так что нет необходимости нормализировать ещё раз. Затем мы вычисляем dot product между интерполированной и нормализированной нормалью и направлением света.

    varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;void main()
{
vec3 n,halfV;
float NdotL,NdotHV;

/* Значение ambient */
vec4 color = ambient;

/* Пикселный шейдер не может записывать в varying-переменную, так что
создаем новую для нормализованной интерполированной нормали */

n = normalize(normal);

/* вычисляем dot product между нормалью и направлением света */
NdotL = max(dot(n,lightDir),0.0);

….
}

 

Если dot product NdotL больше нуля, нам нужно вычислить diffuse-компонент, который является diffuse-переменной, которую мы получили от вершинного шейдера, умноженной на dot product. Также нам нужно вычислить значение отражения. Чтобы сделать это, нужно сначала нормализовать полу-вектор, полученный от вершинного шейдера, и рассчитать dot product между нормализованным полу-вектором и нормалью.

        …
if (NdotL > 0.0) {
color += diffuse * NdotL;
halfV = normalize(halfVector);
NdotHV = max(dot(n,halfV),0.0);
color += gl_FrontMaterial.specular *
gl_LightSource[0].specular *
pow(NdotHV, gl_FrontMaterial.shininess);
}gl_FragColor = color;
}

 

Следующее изображение показывает разницу между расчетом освещения повершинно и попиксельно: