Вершинный шейдер
Вершинный шейдер — первая программируемая стадия: он берёт сырую вершину и решает, где ей оказаться в кадре.
Вершинный шейдер запускается ровно один раз на каждую вершину, преобразует её позицию в clip space и передаёт атрибуты дальше по конвейеру.
Зачем это знать
Именно здесь объект попадает в нужное место, под нужным углом и в перспективе — через умножение на MVP. Здесь же делают анимацию вершин: волны на воде, развевающийся флаг, «скининг» персонажей. Без вершинного шейдера геометрия не сдвинется с места.
Вход и выход
| Вход | Выход |
| атрибуты вершины: позиция, нормаль, UV, цвет | обязательно: gl_Position (clip space) |
| uniform: матрицы MVP, время, параметры | varying: данные для фрагментного шейдера |
Главная обязанность: gl_Position
Каждый вершинный шейдер обязан записать gl_Position — позицию вершины в clip space. Это умножение на MVP. Всё прочее (передача UV, нормалей дальше) — по необходимости.
// Вершинный шейдер с передачей данных дальше
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
uniform mat4 MVP;
varying vec3 vNormal; // полетит во фрагментный шейдер
varying vec2 vUV;
void main() {
vNormal = normal;
vUV = uv;
gl_Position = MVP * vec4(position, 1.0);
}
Анимация вершин: волна
Вершинный шейдер может смещать позиции — так делают воду. Идея: к высоте вершины прибавить синус от её координаты и времени. Смоделируем на Python для одной линии вершин.
import math
def wave_height(x, t, amp=0.5, freq=1.0, speed=2.0):
return amp * math.sin(freq * x + speed * t)
t = 1.0 # момент времени
for x in range(0, 7):
h = wave_height(x, t)
bar = "#" * int((h + 0.5) * 10)
print(f"x={x} height={h:+.2f} {bar}")
Вывод:
x=0 height=+0.45 ######### x=1 height=+0.07 ##### x=2 height=-0.38 # x=3 height=-0.48 x=4 height=-0.14 ### x=5 height=+0.33 ######## x=6 height=+0.49 #########
Высота каждой вершины зависит от её x и времени t — на GPU это даст бегущую волну. Меняя t каждый кадр, получаем анимацию.
Что вершинный шейдер не может
Он не создаёт новые вершины (это умеет геометрический/тесселяционный шейдер) и не знает о соседних вершинах — обрабатывает свою изолированно. Он не определяет цвет пикселей напрямую: только готовит данные (varying), которые растеризатор интерполирует, а уже фрагментный шейдер красит.
Как работает под капотом
Выходные varying-переменные не попадают во фрагментный шейдер как есть — они интерполируются по поверхности треугольника во время растеризации. Если в трёх вершинах UV равны (0,0), (1,0), (0,1), то в середине треугольника фрагмент получит промежуточное UV. Об этой интерполяции — следующий урок.
Частые ошибки
- Не записать gl_Position — вершина не появится, рендер сломается.
- Пытаться в вершинном шейдере обратиться к текстуре цвета пикселя — это работа фрагментного.
- Ожидать, что varying придёт во фрагмент неизменным — он интерполируется между вершинами.
Итоги
- Вершинный шейдер запускается на каждую вершину и обязан задать gl_Position.
- Вход — атрибуты вершины и uniform; выход — clip-позиция и varying.
- Он умеет анимировать геометрию (волны, флаги), но не создаёт вершины.
- Varying-переменные интерполируются по треугольнику перед фрагментным шейдером.