Компьютерная графика
Текстурирование
1974: рука Катмулла меняет всё
1974 год. Университет Юты. Эд Катмулл кладёт руку на планшет-сканер и строит 3D-модель кисти из 350 полигонов. Потом берёт фотографию этой руки и проецирует её на полигоны - как обои на стену. Рендерит. Впервые в истории 3D-объект выглядит как часть реального мира - не синтетической формой, а именно рукой. Катмулл публикует метод, уходит в Lucasfilm, основывает Pixar. Texture mapping становится фундаментом всей компьютерной графики на следующие 50 лет.
Flat quad из 2 треугольников притворяется скалой с тысячью деталей. Персонаж из 50 тысяч полигонов выглядит как 5 миллионов. Это не магия - это текстурирование.
- Любой полигональный рендер: игры, фильмы, AR-фильтры в Instagram
- Photogrammetry-сканирование актёров для AAA-игр (Horizon, God of War)
- Нейральные текстуры в NeRF и Gaussian Splatting
- Виртуальные примерки одежды в e-commerce (IKEA AR, Zara AR)
- Медицинская визуализация: текстурированные 3D-модели органов из МРТ-данных
UV mapping: как разгладить 3D-поверхность в плоскость
1974 год. Эд Катмулл - аспирант Университета Юты, будущий президент Pixar - кладёт ладонь на сканер. Потом натягивает фотографию руки на полигональную 3D-модель кисти. Впервые в истории компьютерной графики объект выглядит реальным. Не закрашенным, не затенённым - именно **реальным**. Это был первый texture mapping в мире.
Идея проста до неприличия: каждой вершине 3D-меша присваиваются координаты $(u, v) \in [0, 1]^2$, которые указывают на точку в текстурном изображении. GPU при растеризации интерполирует $(u, v)$ по треугольнику и читает цвет из текстуры. Трёхмерная поверхность разворачивается в плоский лист - как кожура апельсина, которую разрезали и расплющили.
UV - это не случайные буквы. Алфавит уже занят: X, Y, Z - координаты 3D-пространства. U и V - следующие незанятые буквы. Диапазон $[0, 1]$ нормализован: независимо от разрешения текстуры (256x256 или 4096x4096), координата 0.5 всегда означает середину.
Развёртка (unwrapping) - задача с открытой сложностью. Развернуть сферу без искажений невозможно - именно поэтому все картографические проекции Земли врут в разных местах. С игровыми персонажами та же история: руки, голова, тело развёртываются отдельными "островами" (UV islands), которые художник вручную раскладывает на UV-листе. Ошибка в развёртке - и текстура растягивается, как неправильно наклеенные обои.
Stable Diffusion генерирует изображения 512x512 или 1024x1024 без знания 3D. Но pipelines вроде Zero123 берут сгенерированный 2D-арт и автоматически создают UV-развёртку для 3D-меша. Катмулл проецировал фото вручную - сейчас диффузионные модели делают это за секунды.
**Tile и Wrap**: UV может выходить за пределы $[0, 1]$. Режим `GL_REPEAT` повторяет текстуру тайлингом (трава, кирпич, асфальт - всё тайлится). `GL_CLAMP_TO_EDGE` растягивает крайние пиксели. `GL_MIRRORED_REPEAT` отражает при каждом повторении - убирает видимые швы.
UV-координаты $(u, v) = (0.5, 0.5)$ указывают на...
Фильтрация и mipmaps: когда пиксели расходятся во мнениях
Ближайший сосед (nearest neighbor) - самый дешёвый способ читать текстуру. Берётся пиксель, ближайший к $(u, v)$. Результат - пикселизация как в Minecraft. Это иногда художественный выбор, но чаще нежелательный артефакт.
Билинейная фильтрация берёт 4 ближайших пикселя и интерполирует линейно по обеим осям. Результат гладкий при увеличении (magnification). Но при уменьшении (minification) - текстура мерцает. Особенно на дальних объектах: каждый кадр GPU выбирает разные пиксели, и картинка "дрожит".
**Aliasing при minification** - это фундаментальная проблема сэмплирования. Теорема Найквиста: частота дискретизации должна быть минимум вдвое выше максимальной частоты сигнала. Далёкая текстура с мелкими деталями нарушает этот предел - и возникает алиасинг.
Lance Williams в 1983 году предложил mipmaps. Идея: заранее вычислить отфильтрованные версии текстуры с шагом 2x. Текстура 1024x1024 порождает пирамиду: 512x512, 256x256 ..., 2x2, 1x1. Общий размер - всего 4/3 от оригинала (геометрический ряд: $\sum_{k=0}^{\infty} (1/4)^k = 4/3$). GPU выбирает нужный уровень (LOD - Level of Detail) автоматически по производной UV-координат.
Трилинейная фильтрация = билинейная внутри двух соседних mip-уровней + линейная интерполяция между уровнями. Убирает "ступеньки" при смене LOD. Анизотропная фильтрация (AF) идёт дальше: учитывает угол обзора. Пол под острым углом камеры выглядит размытым при обычных mipmaps - AF читает текстуру в нескольких точках вдоль направления наклона. AF x16 - стандарт в играх с 2005 года, стоит 1-2% производительности.
**Texture compression**: BC7/BPTC (DX11+) и ASTC (мобильные) хранят 4x4 блока пикселей в 128 битах вместо 512. Коэффициент сжатия 6:1-8:1 с минимальными визуальными потерями. GPU декомпрессирует блоки прямо в кеше. Игры на PlayStation 5 используют ASTC: это экономит VRAM и уменьшает пропускную способность памяти.
Mipmaps занимают ровно в 2 раза больше памяти, чем оригинальная текстура. Верно?
Normal maps и PBR: обман геометрии, честная физика
Normal map - это текстурная ложь. Ложь, которая позволила PS2-эре выглядеть как PS4. Вместо добавления миллионов реальных полигонов для царапин и заклёпок - нормаль поверхности меняется попиксельно. GPU думает, что поверхность шероховатая в одном месте и гладкая в другом. Освещение считается по поддельной нормали. Flat quad из 2 треугольников выглядит как кирпичная стена со всеми швами и выбоинами.
Нормали в normal map хранятся в tangent space - локальной системе координат поверхности. Три вектора: tangent ($T$), bitangent ($B$), normal ($N$) образуют TBN-матрицу. Без TBN нормали работали бы только на статичных объектах - ни вращения, ни деформации. Синеватый оттенок normal maps (RGB около [128, 128, 255]) означает "нормаль смотрит прямо вверх" - это нейтральный вектор $(0, 0, 1)$ в диапазоне $[0, 1]$.
PBR (Physically Based Rendering) изменил индустрию в 2012-2014. Disney открыла свой principled shader для игровой индустрии. Unreal Engine 4 принял metallic-roughness workflow. Три параметра плюс цвет описывают физически корректный материал.
**PBR metallic-roughness**: `albedo` (базовый цвет), `metallic` (0 = диэлектрик, 1 = металл), `roughness` (0 = зеркало, 1 = матовый). Металлические поверхности не имеют diffuse-составляющей - они отражают свет тонированным цветом. Диэлектрики отражают ненасыщенным белым около 4% входящего света (Fresnel F0 = 0.04). В основе - Cook-Torrance BRDF: GGX distribution, Smith geometry term, Schlick Fresnel.
Набор текстур в PBR-пайплайне современной AAA-игры: albedo, normal, metallic, roughness, AO, height/displacement, emissive. Персонаж может требовать 7-8 текстур по 4K каждая - это 256 MB только на VRAM одного героя. Texture streaming и виртуальные текстуры (sparse textures в Vulkan/DX12) решают проблему: GPU держит в памяти только видимые mip-уровни.
NeRF (Neural Radiance Fields, 2020) полностью обходит UV mapping. Нейросеть кодирует цвет и плотность как функцию позиции и направления взгляда: $(x, y, z, \theta, \phi) \to (r, g, b, \sigma)$. Нет текстур, нет развёртки, нет ручного труда художника. Зато есть view-dependent effects - блики, которые меняются с углом обзора, - которые обычные текстуры не умеют делать корректно. Gaussian Splatting 2023 делает то же в реальном времени.
**Baking**: для real-time рендеринга high-poly меш (миллионы полигонов) "запекается" в набор PBR-текстур для low-poly версии. Normal map кодирует геометрические детали high-poly. Персонаж в игре - это обычно low-poly (10-50K треугольников) плюс запечённые текстуры от версии на 5-10 миллионов полигонов.
Normal map со значением RGB = (128, 128, 255) означает, что нормаль...
Итоги
- UV mapping разворачивает 3D-поверхность в плоский квадрат [0,1]^2 - как кожуру апельсина на стол
- Билинейная фильтрация устраняет пикселизацию при увеличении; mipmaps устраняют мерцание при уменьшении
- Mipmaps добавляют всего 33% памяти, предвычисляя LoD-пирамиду (геометрический ряд)
- Normal maps меняют нормаль поверхности попиксельно - flat quad притворяется скалой с тысячью деталей
- PBR metallic-roughness: три параметра (albedo, metallic, roughness) - физически корректный материал
- NeRF обходит UV mapping полностью, кодируя сцену как нейронную 5D-функцию
Что дальше
Текстуры - данные для следующих систем рендеринга. Normal maps идут в освещение и тени, PBR-набор питает весь шейдерный пайплайн.
- Тени и AO — Normal maps используются в shadow mapping и SSAO для корректных теней
- Шейдеры — GLSL sampler2D и texture() - практическое чтение UV в шейдерном коде
- NeRF и Neural Rendering — Нейральная альтернатива UV-текстурам без ручной разметки художника
Вопросы для размышления
- Катмулл написал texture mapping как дипломную работу - не знал, что создаёт основу Pixar и игровой индустрии. Какие из сегодняшних экспериментов с NeRF и Gaussian Splatting могут так же переписать правила через 50 лет?
Связанные уроки
- cg-05 — PBR текстуры подают данные в Cook-Torrance BRDF
- cg-07 — Normal maps используются в shadow mapping и SSAO
- cg-09 — Шейдеры читают текстуры через sampler2D
- cg-19 — NeRF учит 5D-текстуру без UV-развёртки
- la-02-dot-product — Dot product нужен для нормализации UV и TBN матриц
- arch-09-cache