Компьютерная графика

GPU Graphics Pipeline

Каждую секунду GPU современной видеокарты обрабатывает 10-50 миллиардов пикселей. За 16 миллисекунд одного кадра 60fps конвейер трансформирует миллионы вершин, растеризует миллионы треугольников и запускает миллиарды фрагментных шейдеров - параллельно, на тысячах ядер. Понять этот конвейер значит понять, почему реалистичная графика возможна в реальном времени.

  • **Игровые движки (Unreal, Unity):** весь рендеринг - это набор draw calls, каждый из которых прогоняет геометрию через описанный конвейер; G-buffer deferred rendering позволяет тысячи источников света.
  • **WebGL и WebGPU:** браузерные игры и 3D-визуализации используют тот же конвейер через JavaScript; Three.js абстрагирует шейдеры, но под капотом - идентичные стадии.
  • **Machine learning на GPU (CUDA):** GPU изначально проектировались для графического конвейера; параллелизм растеризации напрямую применим к матричным операциям нейросетей.

Вершинный шейдер и MVP-трансформация

3D-сцена хранится в объектных координатах: вершины куба заданы относительно центра куба. Чтобы отобразить сцену на экран, нужно последовательно применить три матричных преобразования: Model (объект → мир), View (мир → камера), Projection (камера → clip space). Произведение MVP = Projection × View × Model вычисляется один раз на вершину в вершинном шейдере.

После вершинного шейдера идёт Primitive Assembly: GPU собирает вершины в треугольники по индексному буферу (EBO). Затем - Geometry Shader (опционально): генерирует новые примитивы. Потом Clipping: треугольники, выходящие за frustum, обрезаются (Sutherland-Hodgman в пространстве clip coordinates). После деления на w получаем Normalized Device Coordinates (NDC) в кубе [-1,1]³.

**Нормальная матрица:** простое преобразование нормалей матрицей model даёт неверный результат при неравномерном масштабировании. Правильная матрица для нормалей: transpose(inverse(mat3(model))). Это классическая ошибка начинающих шейдерщиков.

Вершинный шейдер применяет MVP-матрицу и выводит gl_Position в clip space. Какое преобразование GPU выполняет автоматически после шейдера перед растеризацией?

Растеризация и барицентрические координаты

Растеризация - преобразование геометрического треугольника в набор пикселей (фрагментов). GPU проверяет каждый пиксель экрана: попадает ли его центр внутрь треугольника? Проверка - через три тест полуплоскостей (edge functions). Современные GPU выполняют эту проверку для блоков 2×2 пикселей параллельно (quad parallelism).

Перспективно-корректная интерполяция критична для текстур: при простой линейной интерполяции в screen space текстурные координаты искажаются при острых перспективных углах (шахматная доска на полу выглядит «компрессированной»). GPU автоматически выполняет perspective-correct interpolation.

**Quad parallelism и производительность:** GPU тестирует фрагменты блоками 2×2. Если треугольник покрывает только один пиксель из блока - все 4 шейдерных вызова всё равно запускаются (для вычисления производных dFdx/dFdy для mip-mapping). Очень маленькие треугольники неэффективны: overshading может достигать 75%.

При растеризации атрибуты (UV, нормали) интерполируются барицентрически. Почему простая линейная интерполяция в screen space даёт неверный результат для текстур?

Фрагментный шейдер и PBR-освещение

Фрагментный шейдер вычисляет цвет каждого пикселя. Он получает интерполированные атрибуты (нормаль, UV, позицию) и вычисляет освещение. Physically Based Rendering (PBR) - стандарт в современных играх и движках (Unreal, Unity HDRP) - моделирует взаимодействие света с поверхностью физически корректно.

PBR основан на уравнении рендеринга (rendering equation, Kajiya 1986): Lo(p, ωo) = Le + ∫ fr(p, ωi, ωo) · Li(p, ωi) · (n·ωi) dωi. BRDF fr описывает, как поверхность рассеивает свет. Cook-Torrance BRDF для specular части: D (distribution of normals, GGX) × F (Fresnel, Schlick) × G (geometry attenuation, Smith) / (4 · NdotV · NdotL).

**Texturing pipeline:** перед чтением текстуры GPU вычисляет LOD (level of detail) через производные dFdx(uv), dFdy(uv) - отсюда quad parallelism из предыдущего концепта. Mip-mapping выбирает нужный уровень детализации и interpolates между уровнями (trilinear filtering) для устранения алиасинга.

В PBR-материале параметр roughness = 0 и metallic = 1 (полированный металл). Как это повлияет на вид поверхности?

Output Merger: z-буфер, стенсил, альфа-смешение

Output Merger - финальная стадия GPU-конвейера: определяет, какие фрагменты попадут в framebuffer и как. Три теста выполняются в порядке: Scissor Test (прямоугольная область), Stencil Test (маска из stencil buffer), Depth Test (z-буфер). Прошедшие все тесты фрагменты участвуют в Alpha Blending.

Прозрачные объекты требуют сортировки от дальнего к ближнему (painter's algorithm) перед отрисовкой, так как alpha blending некоммутативен: порядок смешения влияет на результат. В производственных рендерах используется Order-Independent Transparency (OIT) - Depth Peeling или Weighted Blended OIT (WBOIT) для избежания сортировки.

**Render targets и G-buffer (Deferred Rendering):** вместо одного framebuffer GPU может рисовать одновременно в несколько текстур (MRT - Multiple Render Targets). Deferred shading рисует геометрию в G-buffer (albedo, normals, depth), затем освещение применяется отдельным проходом - это позволяет эффективно обрабатывать тысячи источников света.

GPU выполняет фрагментный шейдер для каждого фрагмента, и только потом z-тест отбрасывает скрытые. Значит, шейдер работает впустую для скрытых пикселей.

Early-Z: современные GPU выполняют z-тест ДО фрагментного шейдера, отбрасывая заведомо скрытые фрагменты. Фрагментный шейдер запускается только для потенциально видимых пикселей. Исключение: если шейдер пишет в gl_FragDepth или вызывает discard - Early-Z отключается.

Early-Z - ключевая оптимизация производительности. Без неё сцена с много скрытыми поверхностями тратила бы всё время GPU на шейдинг невидимых пикселей. Именно поэтому избегают discard в шейдерах там, где это возможно.

При рендеринге прозрачного стекла поверх непрозрачной стены с alpha blending (Porter-Duff «over»), в каком порядке нужно рисовать объекты?

Ключевые идеи

  • **Вершинный шейдер:** MVP-трансформация object→world→camera→clip space; нормали требуют отдельной матрицы (transpose(inverse(model))).
  • **Растеризация:** edge functions + барицентрические координаты для интерполяции; perspective-correct interpolation обязательна для текстур; Early-Z отсекает скрытые фрагменты до шейдера.
  • **Фрагментный шейдер:** Cook-Torrance PBR BRDF (D×F×G); metallic определяет F0, roughness ширит distribution; mip-map LOD через производные dFdx/dFdy.
  • **Output Merger:** z-буфер для скрытых поверхностей, alpha blending (Porter-Duff «over») для прозрачности, stencil для масок; прозрачные объекты требуют сортировки от дальнего к ближнему.

Связанные темы

GPU-конвейер объединяет линейную алгебру, вычислительную геометрию и физику света:

  • Тени и глобальное освещение — Shadow maps строятся отдельным проходом через тот же конвейер; ray tracing (DXR/Vulkan RT) добавляет стадию после растеризации
  • Polygon clipping (Sutherland-Hodgman) — Clipping треугольников по frustum в clip space - применение алгоритма Sutherland-Hodgman внутри GPU-конвейера

Вопросы для размышления

  • Early-Z отключается, если фрагментный шейдер использует discard или записывает в gl_FragDepth. Почему это архитектурное ограничение неустранимо - почему GPU не может «угадать» результат discard заранее?
  • Deferred shading переносит освещение на отдельный проход, читая данные из G-buffer. При этом MSAA (multisampling antialiasing) работает плохо с deferred. Почему геометрическая информация (нормали, глубина) несовместима с усреднением по сэмплам MSAA?
  • PBR-материал описывается набором текстур (albedo, metallic, roughness, normal map). Какие физические ограничения накладывает принцип сохранения энергии на BRDF - и почему некоторые самодельные шейдеры без PBR выглядят «нереально»?

Связанные уроки

  • arch-04-cpu
GPU Graphics Pipeline

0

1

Войти