Обзор graphics pipeline
От массива вершин до пикселя — данные проходят строго определённую последовательность стадий.
Graphics pipeline (конвейер рендеринга) — фиксированная последовательность стадий, через которые GPU превращает геометрию в пиксели на экране.
Зачем это знать
Шейдеры — это не «магия», а программируемые куски строго определённого конвейера. Зная порядок стадий, вы понимаете, где можно вмешаться (вершинный и фрагментный шейдер), а что делает железо автоматически (растеризация, тест глубины).
Стадии по порядку
Ниже — упрощённая, но корректная схема современного растеризационного конвейера.
1. Вершинные данные массив вершин (позиции, нормали, UV)
|
v
2. Вершинный шейдер преобразует каждую вершину в clip space
| (программируемая стадия)
v
3. Сборка примитивов вершины группируются в треугольники
|
v
4. Клиппинг + деление отсечение по краям экрана, деление на w
|
v
5. Растеризация треугольник -> набор фрагментов (пикселей)
| атрибуты интерполируются
v
6. Фрагментный шейдер считает цвет каждого фрагмента
| (программируемая стадия)
v
7. Тесты на фрагмент глубина (z-buffer), трафарет, блендинг
|
v
8. Фреймбуфер итоговый цвет записан в пиксель
Что программируете вы
Жёлтым выделены две главные программируемые стадии: вершинный шейдер (стадия 2) и фрагментный шейдер (стадия 6). Остальное — клиппинг, растеризация, тест глубины, блендинг — выполняет железо по вашим настройкам, но не вашим кодом построчно.
Сколько фрагментов рождает один треугольник
Растеризация может породить тысячи фрагментов из трёх вершин. Грубая оценка для треугольника, занимающего прямоугольную область — примерно половина её пикселей.
# Грубая оценка числа фрагментов от треугольника
def approx_fragments(bbox_w, bbox_h):
# треугольник покрывает ~половину ограничивающего прямоугольника
return (bbox_w * bbox_h) // 2
print("Маленький треугольник 10x10:", approx_fragments(10, 10), "фрагментов")
print("Большой 500x400:", approx_fragments(500, 400), "фрагментов")
print("Каждый фрагмент = один запуск фрагментного шейдера")
Вывод:
Маленький треугольник 10x10: 50 фрагментов Большой 500x400: 100000 фрагментов Каждый фрагмент = один запуск фрагментного шейдера
Вот почему фрагментный шейдер — самая нагруженная стадия: он исполняется десятки тысяч раз на один треугольник.
Как работает под капотом
Конвейер устроен как поток: пока одни вершины ещё в вершинном шейдере, другие треугольники уже растеризуются, а готовые фрагменты тестируются на глубину. GPU держит все стадии загруженными одновременно (pipeline parallelism), как заводской конвейер. Поэтому простой одной стадии (например, слишком тяжёлый фрагментный шейдер) тормозит весь поток.
Частые ошибки
- Думать, что фрагментный шейдер вызывается один раз на треугольник — он вызывается на каждый пиксель.
- Путать вершинный и фрагментный шейдер: первый работает с геометрией, второй — с цветом пикселей.
- Ожидать, что можно «вписать» свой код между растеризацией и тестом глубины — эти стадии фиксированы.
Итоги
- Конвейер: вершины → вершинный шейдер → сборка → клиппинг → растеризация → фрагментный шейдер → тесты → фреймбуфер.
- Программируете вы две стадии: вершинную и фрагментную.
- Один треугольник рождает тысячи фрагментов — фрагментный шейдер самый горячий.
- GPU держит все стадии параллельно, как конвейер завода.