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;
}
Результирующее изображение показано ниже. Оно выглядит уже лучше, но всё ещё не идеально.
К тому же на нём присутствует алиасинг, но его устранение выходит за рамки этого урока 😉