Обзор 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 держит все стадии параллельно, как конвейер завода.
Проверьте себя
1. Какие две стадии конвейера программирует разработчик шейдеров?
AРастеризацию и блендинг
BВершинный и фрагментный шейдеры
CКлиппинг и тест глубины
DСборку примитивов и фреймбуфер
2. Сколько раз выполняется фрагментный шейдер для одного большого треугольника?
AОдин раз
BТри раза (по числу вершин)
CПо разу на каждый покрытый пиксель — тысячи раз
DНоль раз
3. Что делает стадия растеризации?
AСчитает освещение
BПревращает треугольник в набор фрагментов и интерполирует атрибуты
CЗагружает текстуры с диска
DСжимает кадр