Как GPU рисует 3D-картинку в играх: конвейер рендеринга без магии (vertex → raster → fragment)
Игровая картинка выглядит как «фотореализм», но внутри — строгий конвейер вычислений, который GPU прогоняет заново на каждом кадре. Если понимать базовые этапы, проще объяснить себе, почему проседает FPS: где упираетесь в геометрию, где — в пиксели, где — в освещение, а где начинаются отдельные тяжёлые блоки вроде ray tracing и DLSS.
Что именно GPU «переваривает» в сцене
Для ориентира по масштабу: пример детализированной сцены можно разложить в цифры — 2,1 млн вершин собираются в 3,5 млн треугольников, поверх назначены 976 цветов и текстур.
И важный практический момент: камера не рендерит «весь мир». В сцене может быть 1 100 моделей, но поле зрения камеры отсекает лишнее — к рендерингу попадают 600 объектов.
Отдельный объект тоже «обманчиво гладкий». Например, локомотив визуально круглый, но геометрически это 762 тыс. плоских треугольников, 382 тыс. вершин и 9 материалов/цветов на поверхностях.
Конвейер рендеринга: 3 шага, которые стоят за каждым кадром
1) Vertex shading: как 3D превращается в 2D «вид на экран»
Смысл этапа — перенести все вершины из 3D-пространства в координаты 2D-экрана. Это делается тремя преобразованиями координат:
- из model space в world space
- из world space в camera space
- из перспективы поля зрения на view screen
Во входных данных — координаты вершины X/Y/Z в model space, положение/масштаб/поворот модели в world space, координаты/поворот камеры и её поле зрения. Всё это собирается в матрицы преобразований и перемножается.
На выходе у каждой вершины появляются:
- X и Y на view screen (то есть «где это на экране»)
- Z (depth) — глубина, которая дальше решает, кто кого перекрывает
И так — для каждой вершины. В итоге на 2D-плоскость проецируются те же 3,5 млн треугольников (просто уже в экранных координатах).
2) Rasterization: как треугольники превращаются в пиксели и фрагменты
Дальше нужно понять: какие пиксели экрана закрывает каждый треугольник.
Для масштаба: 4K — это 3840×2160, то есть около 8,3 млн пикселей. GPU берёт экранные координаты вершин треугольника (X/Y), «накладывает» его на сетку пикселей и определяет, какие пиксели внутри.
Тут появляется базовый термин:
Фрагменты (fragments) — группы пикселей, пришедшие от одного растеризованного треугольника и разделяющие одну текстуру/цвет.
Так GPU проходит по всем треугольникам и формирует 4K-кадр в frame buffer, который уходит на дисплей.
2.1) Проблема видимости: Z-buffer (depth buffer)
В 3D объекты перекрывают друг друга, а ещё у модели есть треугольники «сзади», которые математически проходят конвейер, но в финальном кадре не должны появиться.
Это решает Z-buffer: у каждого пикселя (из тех самых ~8,3 млн) хранится дополнительное значение глубины относительно камеры.
Правило простое:
- если новый треугольник ближе (его depth меньше) — пиксель перерисовывается, depth обновляется
- если дальше — вклад отбрасывается
Визуализация depth buffer обычно трактуется так: чёрный — близко, белый — далеко.
Важный нюанс: у треугольника в 3D часто три разные Z на вершинах, поэтому depth приходится вычислять для каждого пикселя треугольника по координатам вершин. Это же позволяет корректно рисовать пересечения треугольников пиксель-за-пикселем.
2.2) «Лесенка» на краях и SSAA
Почему края «ступеньками»? Если треугольник проходит через пиксель под углом, пиксель часто закрашивается целиком, даже если треугольник покрывает его частично.
Один из способов уменьшить эффект — SSAA (Super Sampling Anti-Aliasing). В описанном варианте в один пиксель кладут 16 точек выборки. Если треугольник покрывает только часть из этих 16 точек, пиксель получает дробный оттенок — края становятся мягче, пикселизация заметно падает.
3) Fragment shading: как появляется свет, объём и материал
После растеризации «просто покрасить» недостаточно — реализм дают освещение и материал. Fragment shading учитывает:
- направление и силу света
- положение камеры
- отражения и тени от объектов
На примере металлического объекта это очевидно: если дать всем пикселям один цвет, получится плоская фигура. Но если добавить корректный шейдинг (низ темнее, верх светлее) и specular highlights (блики), «чёрный металл» начинает выглядеть как металл.
Динамика тоже туда же: при движении «солнца» меняется распределение света, ночью материалы темнеют и подсвечиваются огнём.
И да — даже Super Mario 64 (почти 30 лет) использует простую идею: цвета поверхностей меняются из-за света и теней.
Математика освещения: нормали, cos(theta), ambient и несколько источников
Упрощённо логика такая: поверхность, направленная на источник света, ярче; повернутая в сторону — темнее.
Для расчёта нужны:
- направление света
- нормаль поверхности (surface normal) — вектор, перпендикулярный плоскости треугольника
Яркость берут через cos(theta) между направлением света и нормалью:
- cos(theta)=1, если поверхность «смотрит» на свет
- cos(theta)=0, если поверхность перпендикулярна свету
Дальше:
- cos(theta) умножается на интенсивность света
- и на цвет материала
Чтобы поверхность не становилась «провалом в чёрное», отрицательные значения не используют: минимум фиксируется как 0, а сверху добавляют ambient light (окружающий свет). В дневных сценах ambient выше, ночью — ближе к 0.
Если источников света несколько, расчёт делают несколько раз и суммируют вклад. Но это дорого: большое число источников вычислительно тяжело, поэтому сцены ограничивают их количество и иногда ограничивают радиус влияния, чтобы треугольники игнорировали дальние источники.
Техническая деталь: на практике cos(theta) считают через векторную математику — dot product, делённый на norm векторов.
Условная форма записи логики выглядит так:
shaded = (max(0, cos_theta) * light_intensity + ambient_intensity) * material_color
Flat vs Smooth: почему «гладкий объект» может выглядеть гранёным
Проблема flat shading: у каждого треугольника одна нормаль — значит, цвет по треугольнику одинаковый, и кривые поверхности выглядят «фасеточно».
Для smooth shading:
- берут нормаль на каждую вершину (усреднение нормалей соседних треугольников)
- внутри треугольника нормаль интерполируют через barycentric coordinates
Для конкретного фрагмента берут центр пикселя и по координатам треугольника и vertex normals вычисляют barycentric normal этого пикселя. Итог — плавные градиенты освещения «пиксель за пикселем».
Где в этой схеме ray tracing и DLSS
Ray tracing
Трассировка лучей традиционно даёт очень точные свет и отражения (типично для кино/TV), и один кадр может рендериться десятки минут или больше.
В играх обычно делают гибрид:
- основная видимость и базовый шейдинг — через классический конвейер
- ray tracing в отдельных проектах добавляют для теней, отражений и улучшенного освещения
DLSS
DLSS берёт кадр в низком разрешении и апскейлит до 4K с помощью convolution neural network. По описанной схеме DLSS выполняется после того, как ray tracing и базовый конвейер сформировали low-resolution кадр.
Почему современные GPU «вывозят» RT+DLSS лучше: три типа ядер
Отдельный практический факт: у последних поколений GPU выделены разные вычислительные блоки:
- CUDA / shading cores — классический конвейер (vertex/raster/fragment)
- ray tracing cores — трассировка
- tensor cores — DLSS
Когда включены RT и DLSS, GPU использует все ресурсы параллельно. Отсюда цифры по времени кадра:
- с RT+DLSS можно рендерить 4K-кадры менее чем за 10 мс
- если полагаться только на CUDA/shading cores, кадр занял бы около 50 мс
Производительность в цифрах: почему 120 FPS — это «бюджет 8,3 мс»
GPU оптимизированы под треугольные сетки и математику шейдеров. Пример порядка величин: около 10 000ish ядер могут выполнять до 35 трлн операций 32-битного умножения и сложения в секунду. За счёт параллелизма GPU распределяет вершины и данные трансформаций по ядрам и тянет сцену до 120+ FPS.
Перевод в понятный ограничитель: 120 FPS = 8,3 мс на кадр. И этот бюджет должен вместить vertex shading, rasterization и fragment shading заново — каждый кадр.
Нюансы и подводные камни
- 4K — это 3840×2160 и около 8,3 млн пикселей: цена «пиксельных» этапов растёт резко.
- Геометрия не исчезает сама: в сцене может быть 1 100 моделей, и только поле зрения камеры сокращает это до 600 объектов для рендера.
- Даже «круглое» — это плоские треугольники: пример локомотива — 762 тыс. треугольников, 382 тыс. вершин, 9 материалов.
- Z-buffer работает на уровне каждого пикселя (те же ~8,3 млн) и требует считать глубину внутри треугольника, потому что у вершин разные Z.
- SSAA уменьшает «лесенку», но использует 16 точек выборки на пиксель — визуально эффективно, вычислительно не бесплатно.
- Много источников света — дорого, поэтому сцены режут их число и/или радиус влияния.
- Flat shading даёт «гранёность»; smooth shading требует vertex normals и интерполяции через barycentric coordinates.
- Ray tracing и DLSS — не «настройки качества», а отдельные вычислительные блоки: RT обычно добавляет тени/отражения/свет, DLSS апскейлит low-res кадр до 4K нейросетью и выполняется после формирования low-res кадра.
Итог: кому разбираться, кому хватит пресетов
Если вы настраиваете графику под стабильный FPS или оцениваете «что даст новая видеокарта», держите в голове разделение по нагрузкам:
- геометрия (вершины/треугольники) давит на ранние этапы
- разрешение (пиксели) давит на растеризацию и шейдинг
- освещение усложняет fragment shading
- ray tracing и DLSS живут отдельными блоками и сильно меняют картину по времени кадра
Чек-лист настройки без гадания
- Зафиксируйте цель по времени кадра: 120 FPS = 8,3 мс на весь конвейер.
- Определитесь с разрешением: 4K = 3840×2160 ≈ 8,3 млн пикселей на кадр.
- Помните, что видимость решается глубиной: Z-buffer хранит глубину для каждого пикселя и оставляет ближайшее.
- Если включаете сглаживание SSAA, держите в голове механику 16 выборок на пиксель.
- Если включаете ray tracing, воспринимайте его как отдельный блок, который часто считают для теней/отражений/света поверх классики.
- Если используете DLSS, это апскейл low-res кадра до 4K через convolution neural network, выполняемый после формирования low-res кадра.
- Если сравниваете GPU под RT+DLSS, учитывайте, что задействуются shading cores + RT cores + tensor cores, а не только шейдерные ядра.
Mini-FAQ
Почему у сцены «миллионы треугольников», но она всё равно в реальном времени?
Потому что GPU — параллельная машина под треугольные сетки и математику шейдеров: пример порядка величин — 10 000ish ядер и до 35 трлн 32-битных операций умножения/сложения в секунду.
Почему 4K так больно по производительности?
Потому что это около 8,3 млн пикселей на кадр, и дальше многие вычисления идут «по пикселям».
Зачем Z-buffer, если уже есть 3D-координаты?
Потому что на выходе растеризации нужно решить видимость для каждого пикселя: Z-buffer хранит глубину и оставляет ближайший треугольник.
DLSS — это «после рендера»?
По описанной схеме — да: DLSS апскейлит low-res кадр до 4K и выполняется после того, как классический конвейер и ray tracing сформировали low-res кадр.
Почему RT+DLSS могут быть быстрее, чем «только шейдеры»?
Потому что в современных GPU есть отдельные блоки: ray tracing cores и tensor cores, которые работают параллельно с CUDA/shading cores. В приведённых цифрах это даёт <10 мс на кадр против порядка 50 мс, если бы всё считалось только на шейдерных ядрах.
Эту же статью но под другим углом + схемы я написал на нашем портале:
https://tehnari.ru/ams/kak-rabotayet-videokarta-v-igrakh-rendering-sheidery-rasterizatsiya-i-z-buffer.30/