Уроки по OpenGL с сайта OGLDev


Урок 09 - Интерполяция

В этом уроке демонстрируется важная часть 3D конвейера - интерполяция, которую растеризатор через переменные выводит из вершинного шейдера. Как вы уже заметили, чтобы получить на экране какие-либо объекты, необходимо назвать и задать одну из переменных вершинного шейдера как 'gl_Position'. Это 4-вектор, содержащий однородные координаты вершин. XYZ компоненты вектора делятся на W элемент (процесс, называемый нахождением перспективы (perspective divide) и рассматривается в уроке, посвященной этой теме) и любые элементы, выходящие за размеры окна ([-1,1] для XY; [0,1] для Z) будут обрезаны. Результат преобразуется в координаты пространства экрана и затем треугольник (или любой другой примитив) рендерится на экран растеризатором.

Растеризатор выполняет интерполяцию между тремя вершинами треугольника (или проходя от строки к строке или каким-то другим способом) и "посещает" каждый пиксель внутри треугольника запуская для него фрагментный шейдер. От пиксельного шейдера ожидается цвет пикселя, который растеризатор помещает в буфер цвета для отображения (не считая несколько дополнительных проверок типа теста глубины и т.д.). Любые другие данные, выходящие из вершинного шейдера не проходят эти этапы. Если фрагментный шейдер не требует переменную явно (а вы можете смешивать и сочетать несколько пиксельных шейдеров с одним и тем же вершинным), тогда драйвер оптимизации отбросит любые ее упоминания в вершинном (но только для данной комбинации шейдеров). А если фрагментный шейдер использует эту переменную, то растеризатор интерполирует ее во время растеризации и каждый вызов пиксельного шейдера будет получать свое интерполированное значение в соответствии с его позицией. Обычно это означает, что числа для пикселя, который немного правее, будут слегка различаться (хотя чем дальше треугольник от камеры, тем различия все меньше).

Две вещи, которым необходима интерполяция - это нормали для треугольника и координаты текстуры. Нормаль у вершины чаще подсчитывается как среднее между всеми нормалями у треугольников, которые содержат эту вершину. Если объект не полностью плоский, то обычно это значит, что все 3 нормали вершин одного треугольника будут отличаться друг от друга. В этом случае мы полагаемся на интерполяцию для расчетов освещения, чтобы получить более реалистичные эффекты света. Случай координат текстур аналогичен. Эти координаты - часть модели, и они указываются для каждой вершины. Для "покрытия" треугольника текстурой, необходимо выполнить одинаковые операции для каждого пикселя и указать правильные координаты текстуры для этого пикселя. Эти координаты и есть результат интерполяции.

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

Прямиком к коду!

out vec4 Color;

Переменные, передающиеся между этапами конвейера должны быть глобальными, а так же объявлены с ключевым словом 'out'. Цвет - это 4-й вектор, первые 3 элемента которого RGB значения (соответственно) и W это альфа значение (прозрачность пикселя).

Color = vec4(clamp(Position, 0.0, 1.0), 1.0);

Цвета в графическом конвейере обычно представлены в виде вещественных чисел в отрезке [0.0, 1.0]. Позже эти числа будут переведены в целочисленные от 0 до 255 для каждого канала цвета (всего 16M оттенков). Мы устанавливаем цвет вершины как функцию от ее позиции. Сначала мы используем заготовленную функцию clamp(), которая проверит, не выходят ли наши значения из промежутка 0.0-1.0. Мы делаем это потому, что нижняя левая вершина треугольника имеет координаты -1,-1. Если взять значения как есть, то они будут интерполированы растеризатором, и пока и X и Y равны 0 мы ничего не увидим, потому что любые значения меньше либо равные 0 будут рендерится черными. Это значит, что половина стороны во всех направлениях будет черной до тех пор, пока цвет не дойдет до 0 и после станут чем-то другим. С помощью ограничения мы сделали только нижнюю левую вершину черной, дальше цвет быстро будет набирать яркость. Поэкспериментируйте с функцией clamp - удалите ее совсем или измените параметры, что бы увидеть эффект.

Результат функции clamp не сразу переходит в выходную переменную, поскольку она 4-вектор, а позиция - 3-вектор (clamp не изменяет количество элементов, только их значения). В GLSL не существует такого конвейера по умолчанию, поэтому мы должны сделать это явно. Для этого мы используем обозначение 'vec4(vec3, W)', которое создает 4-вектор объединением 3-вектора с полученным числом W. В нашем случае мы используем 1.0 чтобы пиксель был полностью непрозрачным.

in vec4 Color;

Противоположный выходному цвету в VS входящий цвет в FS. Эта переменная претерпевает интерполяцию растеризатором, поэтому каждый FS будет (возможно) выдавать разный цвет.

FragColor = Color;

Мы используем интерполяционный цвет в качестве цвета пикселя без каких-либо изменений и на этом завершаем этот урок.

powered byDisqus