GLSL имеет доступ к части функционала OpenGL. В этом уроке вы увидите, как прочитать цвет, установленный OpenGL с помощью glColor..().

В GLSL есть attribute-переменная, которая хранит значение цвета. В этом уроке мы применим эффект toon-шейдера на каждый фрагмент. Чтобы сделать это, нам нужно получить доступ к нормалям каждого пиксела. Поскольку вершинному шейдеру нужно всего лишь записать нормаль в varying-переменную, пикселному шейдеру остается всего лишь прочитать интерполированное значение вершины.

Вершинный шейдер будет упрощён, так как расчет интенсивности цвета будет теперь вычисляться в пикселном шейдере. uniform-переменная lightDir также переехала в пикселный шейдер, так как в вершинном оа больше не используется. Следующий код — код нового вершинного шейдера:

toon.vert

    varying vec3 normal;

void main()
{
normal = gl_Normal;

gl_Position = ftransform();
}

 

Теперь в пикселном шейдере нужно обьявить переменную lightDir, так как интенсивность основывается на этой переменной. Также нужно обьявить новую varying-переменную для хранения интерполированных нормалей. Код для пикселного шейдера:


uniform vec3 lightDir;

varying vec3 normal;

void main()
{

float intensity;
vec4 color;

intensity = dot(lightDir,normal);

if (intensity > 0.95)
color = vec4(1.0,0.5,0.5,1.0);
else if (intensity > 0.5)
color = vec4(0.6,0.3,0.3,1.0);
else if (intensity > 0.25)
color = vec4(0.4,0.2,0.2,1.0);
else
color = vec4(0.2,0.1,0.1,1.0);

gl_FragColor = color;
}


И в результате:

Нет, это не баг! Такой же результат, как и в предыдущем разделе. Так в чем же дело?

Давайте взглянем повнимательнее на эти две версии. В первой версии мы вычислили интенсивность в вершинном шейдере и использовали интерполированное значение в пикселном. Во второй версии мы интерполируем нормаль в вершинном шейдере для пикселного шейдера, в котором уже вычисляем dot product. Интерполяция и dot product — линейные операции, так что не важно, какая
из них выполняется раньше.

Неверно здесь использование интерполированной нормали для формулы dot() в пикселном шейдере!
И это неправильно, потому что нормаль, даже если имеет верное направление, скорее всего не имеет длинны.

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

Главная причина перемещения вычисления интенсивности из вершинного шейдера в пикселный — использование надлежащей нормали для каждого пиксела. У нас есть вектор нормали, имеющий правильное направление, но не единичную длинну. Чтобы исправить это, нужно нормализовать входящий вектор нормали в пикселном шейдере.
Следующий код — корректный и завершенный пикселный шейдер:

Файл toon.frag

    uniform vec3 lightDir;

varying vec3 normal;

void main()
{

float intensity;
vec4 color;

intensity = dot(lightDir,normalize(normal));

if (intensity > 0.95)
color = vec4(1.0,0.5,0.5,1.0);
else if (intensity > 0.5)
color = vec4(0.6,0.3,0.3,1.0);
else if (intensity > 0.25)
color = vec4(0.4,0.2,0.2,1.0);
else
color = vec4(0.2,0.1,0.1,1.0);

gl_FragColor = color;
}

 

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