Компьютерная графика
Координатные пространства
2011 год. Battlefield 3 - первый AAA-шутер на Frostbite 2. Художники жалуются: персонажи деформируются при масштабировании. Баг занял неделю дебаггинга. Причина - нормали трансформировались не той матрицей. Не Model Matrix, а её обратно-транспонированной. Пять пространств, четыре матрицы, и одна перепутанная - сцена рассыпается.
- **Vertex Shader в GPU:** первая стадия graphics pipeline. Его единственная обязательная задача - трансформировать вершину из Model Space в Clip Space через MVP
- **Camera systems в играх:** FPS-камера, orbit camera, cinematic camera - всё это разные способы построить View Matrix
- **VR-рендеринг:** два глаза = две View Matrix + две Projection Matrix с разным offset. Удвоение vertex pipeline
Катмулл, SGI и рождение графического pipeline
В 1974 году Эдвин Катмулл описал концепцию последовательного преобразования вершин через пространства в своей PhD-диссертации. SGI (Silicon Graphics) в 1982 аппаратно реализовала этот pipeline в рабочих станциях IRIS. В 1992 OpenGL стандартизировал именно этот порядок: Model -> World -> View -> Clip -> NDC. Каждый современный GPU - прямой наследник той архитектуры.
Предварительные знания
Model Space (локальные координаты)
3D-художник лепит персонажа. Голова - в центре, руки - по бокам, ноги - внизу. Все координаты вершин заданы **относительно центра модели**. Это **Model Space** - собственная система координат объекта. Паспорт формы.
**Model Space** (Object Space, Local Space) - координатная система объекта. Центр (0,0,0) обычно в геометрическом центре или у основания. Все вершины 3D-модели хранятся в model space.
Разделение формы (model space) и размещения (model matrix) - выразительная абстракция. Один и тот же куб можно разместить в сцене 100 раз с разными позициями, поворотами и масштабами - меняется только model matrix, вершины остаются теми же. На этом работает GPU instancing в Unreal и Unity.
| Свойство | Model Space | Зачем нужно |
|---|---|---|
| Центр (origin) | Центр объекта | Вращение происходит вокруг origin |
| Масштаб | «Естественный» размер | S = (1,1,1) = оригинальный размер |
| Общие вершины | Один буфер на все экземпляры | GPU instancing - тысячи деревьев из одной модели |
| Bounding box | В model space | Для collision detection до трансформации |
100 деревьев в сцене используют одну и ту же 3D-модель. Что различается для каждого дерева?
World Space (мировые координаты)
Все объекты сцены живут в **World Space** - единой глобальной системе координат. Дом стоит на (10, 0, 5), дерево - на (15, 0, 8), персонаж - на (12, 0, 6). World Space - «карта мира» сцены. Здесь считают физику, AI и освещение.
**World Space** - глобальная система координат сцены. Вершина переходит из Model Space в World Space через Model Matrix: world_pos = ModelMatrix × model_pos. В World Space вычисляется освещение, физика, AI.
В World Space источник света и поверхность объекта существуют в одной системе координат. Это принципиально: нельзя посчитать dot(normal, light_dir), если normal в Model Space объекта, а light_dir - глобальный вектор в World Space. Нужно общее пространство.
| Операция в World Space | Зачем | Пример |
|---|---|---|
| Расстояние между объектами | Физика, AI | Враг в радиусе 10 метров? |
| Направление на источник света | Освещение | dot(normal, light_dir) = яркость |
| Raycasting | Выбор объектов мышью | Луч из камеры через пиксель в мир |
| Bounding volume проверки | Broad phase collision | AABB overlap в world space |
Освещение обычно вычисляется в World Space. Почему не в Model Space?
View Space и матрица LookAt
Камера стоит в мире и смотрит в определённом направлении. **View Space** - это мир, «увиденный глазами камеры»: камера находится в (0,0,0), смотрит вдоль -Z (OpenGL). Весь мир пересчитывается в координаты камеры. Не камера летит к сцене - сцена летит к камере.
**View Matrix** (Camera Matrix) переводит координаты из World Space в View Space. Строится через **LookAt**: задаём позицию камеры (eye), точку, на которую смотрим (target), и вектор «вверх» (up). View Matrix = (Model Matrix камеры)^(-1).
**Почему не оставить World Space?** Проекция - следующий шаг - предполагает, что камера в начале координат и смотрит вдоль оси Z. View Matrix «перемещает весь мир» так, чтобы камера оказалась в origin. Математически это инверсия: camera_to_world^(-1) = world_to_camera.
| Параметр LookAt | Что задаёт | Типичное значение |
|---|---|---|
| eye | Позиция камеры в мире | (0, 5, 10) - сзади и сверху |
| target | Куда смотрим | (0, 0, 0) - центр сцены |
| up | Где «верх» | (0, 1, 0) - Y вверх |
| Результат | View Matrix 4x4 | Обратная матрица позиции камеры |
View Matrix - это обратная матрица трансформации камеры. Почему обратная?
Projection: Perspective vs Orthographic
Мир - трёхмерный. Экран - двумерный. **Проекция** сжимает 3D в 2D. Два типа: **perspective** (как видит глаз - далёкое меньше) и **orthographic** (без перспективы - инженерные чертежи). В играх - perspective. В CAD, 2D-играх, UI - orthographic.
**Perspective Projection** - имитирует человеческое зрение. Определяется FOV (field of view), aspect ratio, near plane и far plane. Далёкие объекты уменьшаются. **Orthographic** - параллельная проекция, размер не зависит от расстояния.
**Frustum** (усечённая пирамида) - объём пространства, видимый камерой при perspective projection. Всё за пределами frustum **отсекается** (clipping) - GPU не тратит ресурсы на невидимые объекты. Это происходит ещё в Clip Space, до perspective divide.
| Параметр | Perspective | Orthographic |
|---|---|---|
| FOV (field of view) | Определяет ширину обзора | Не используется |
| Near plane | Минимальная дистанция рендеринга | Минимальная дистанция |
| Far plane | Максимальная дистанция | Максимальная дистанция |
| Эффект глубины | Далёкое уменьшается | Нет эффекта глубины |
| Последняя строка матрицы | [0, 0, -1, 0] | [0, 0, 0, 1] |
**Near plane** нельзя ставить близко к 0! Z-buffer имеет конечную точность, и при near близком к 0 далёкие объекты «сливаются» (Z-fighting). Рекомендация: near >= 0.1 для игр.
Perspective projection matrix имеет -1 в позиции [3][2]. Зачем?
NDC: Normalized Device Coordinates
После projection вершина в **Clip Space** с координатами (x, y, z, w). GPU выполняет **perspective divide**: делит x, y, z на w. Результат - **NDC** (Normalized Device Coordinates). Все видимые координаты попадают в [-1, 1]. Вне - отсекается.
**NDC** (Normalized Device Coordinates) - нормализованное пространство после perspective divide. В OpenGL: x ∈ [-1, 1], y ∈ [-1, 1], z ∈ [-1, 1]. В DirectX/Vulkan: z ∈ [0, 1]. Всё за пределами - отсечено.
Последний шаг: **Viewport Transform** - из NDC [-1,1] в пиксельные координаты экрана [0, width] × [0, height]. Формулы: screen_x = (ndc_x + 1) / 2 * width, screen_y = (ndc_y + 1) / 2 * height.
| Пространство | Матрица перехода | Диапазон координат | Что происходит |
|---|---|---|---|
| Model -> World | Model Matrix (TRS) | Произвольный | Размещение в сцене |
| World -> View | View Matrix (LookAt) | Произвольный | Камера -> origin |
| View -> Clip | Projection Matrix | Произвольный (с w) | Перспектива |
| Clip -> NDC | Perspective divide (÷w) | [-1, 1]^3 | Нормализация |
| NDC -> Screen | Viewport transform | [0,W] × [0,H] | Пиксели экрана |
От локальных координат модели до пикселя на экране - 5 пространств и 4 матричных перехода. GPU вычисляет MVP = P × V × M один раз и применяет к миллионам вершин параллельно. Это и есть vertex shader - первый этап graphics pipeline.
Perspective projection - это просто деление x и y на z
Полная perspective projection matrix включает FOV (угол обзора), aspect ratio, near plane и far plane. Деление на z - только часть процесса. Матрица масштабирует x и y с учётом FOV/aspect, преобразует z для корректного Z-buffer, и копирует -z в w для perspective divide
Простое x/z не учитывает: 1) FOV - насколько широко видит камера, 2) aspect ratio - экран не квадратный, 3) near/far - нужно отсечение по глубине и правильное распределение точности Z-buffer. Матрица проекции решает все задачи одним умножением.
Вершина после projection имеет clip-координаты (3, 2, -8, -10). Каковы NDC-координаты?
Ключевые идеи
- **Model Space** - локальные координаты объекта. Model Matrix (TRS) размещает его в мире
- **World Space** - общая сцена. View Matrix (LookAt^-1) переносит мир в координаты камеры
- **Projection** (perspective/orthographic) сжимает 3D в frustum. Perspective divide (÷w) создаёт перспективу
- **NDC** [-1,1]^3 -> Viewport Transform -> пиксели экрана. Полный pipeline: MVP = P × V × M
Связанные темы
Координатные пространства - основа для понимания всего rendering pipeline:
- Линейная алгебра для графики — Матрицы 4x4, однородные координаты, TRS - математический фундамент всех трансформаций
- Растеризация — После NDC->Screen вершины поступают на растеризатор, который заполняет пиксели между ними
- Z-Buffer — NDC z-координата используется для определения, какой объект ближе к камере (depth testing)
Вопросы для размышления
- Почему GPU вычисляет MVP = P × V × M заранее, а не применяет три матрицы последовательно к каждой вершине?
- При VR-рендеринге две камеры (левый и правый глаз) имеют разные View Matrix. Какие части pipeline можно переиспользовать?
- Что произойдёт с перспективой, если FOV увеличить до 170°? А уменьшить до 5°?
Связанные уроки
- cg-02 — Матрицы 4x4 и однородные координаты - математика всех переходов
- cg-04 — NDC z-координата идёт прямо в depth buffer для Z-fighting
- cg-01 — Растеризатор получает вершины уже в Screen Space
- geo-01 — Аффинные преобразования - формальный базис TRS-матриц
- arvr-01 — VR: два frustum, две View Matrix, stereo rendering
- cg-05 — Shading требует нормалей в правильном пространстве
- la-06-transformations