Текстуры: UV, сэмплирование, фильтрация

Текстура — это картинка, которую «натягивают» на поверхность модели по координатам UV.

Текстура — изображение, накладываемое на поверхность; UV-координаты сопоставляют каждой вершине точку на этом изображении.

Зачем это знать

Без текстур всё было бы однотонным. Текстуры дают дереву кору, кирпичу — рисунок, лицу — кожу. UV-координаты — это «выкройка»: как развернуть 3D-поверхность в 2D-картинку. А фильтрация и mipmap решают, как картинка выглядит вблизи и вдали, без «мыла» и шума.

UV-координаты

UV — это координаты на текстуре, обычно от 0 до 1 по обеим осям независимо от размера картинки. (0,0) — один угол текстуры, (1,1) — противоположный. Каждой вершине меша назначают её UV, а растеризатор интерполирует UV по треугольнику.

UV-пространство текстуры (0..1):

 (0,1) +-----------+ (1,1)
       |           |
       |  картинка |
       |           |
 (0,0) +-----------+ (1,0)

Сэмплирование: из UV в пиксель текстуры

Чтобы получить цвет, UV переводят в координаты текселя (пикселя текстуры): умножают на размер. Покажем перевод и почему возникает дробная позиция.

tex_w, tex_h = 4, 4  # текстура 4x4 текселя

def uv_to_texel(u, v, w, h):
    return (u * (w - 1), v * (h - 1))

print("UV (0.0, 0.0):", uv_to_texel(0.0, 0.0, tex_w, tex_h))
print("UV (1.0, 1.0):", uv_to_texel(1.0, 1.0, tex_w, tex_h))
print("UV (0.5, 0.5):", uv_to_texel(0.5, 0.5, tex_w, tex_h))
print("UV (0.3, 0.7):", uv_to_texel(0.3, 0.7, tex_w, tex_h))

Вывод:

UV (0.0, 0.0): (0.0, 0.0)
UV (1.0, 1.0): (3.0, 3.0)
UV (0.5, 0.5): (1.5, 1.5)
UV (0.3, 0.7): (0.8999999999999999, 2.0999999999999996)

UV (0.5,0.5) попадает между текселями — в позицию (1.5, 1.5). Что делать с дробной координатой? Здесь и нужна фильтрация.

Билинейная фильтрация

Вместо ближайшего текселя (nearest, даёт чёткие квадраты) берут четыре соседних и смешивают пропорционально расстоянию — это билинейная фильтрация, дающая гладкий результат. Смоделируем смешивание четырёх текселей.

def bilinear(tl, tr, bl, br, fx, fy):
    top = tl + (tr - tl) * fx
    bot = bl + (br - bl) * fx
    return round(top + (bot - top) * fy, 2)

# 4 соседних текселя (яркости) и дробные доли fx, fy
print("Центр (0.5,0.5):", bilinear(0, 100, 0, 100, 0.5, 0.5))
print("Ближе к левому (0.1):", bilinear(0, 100, 0, 100, 0.1, 0.5))

Вывод:

Центр (0.5,0.5): 50.0
Ближе к левому (0.1): 10.0

Дробная позиция даёт плавно смешанный цвет, а не резкий скачок между текселями.

Mipmap: текстура вдали

Когда объект далеко, один пиксель экрана накрывает много текселей. Если брать только один — получится мерцающий шум (алиасинг). Решение — mipmap: заранее уменьшенные копии текстуры (1/2, 1/4, 1/8...). GPU выбирает подходящий уровень по расстоянию, и далёкие поверхности выглядят чисто.

Mipmap-пирамида:

уровень 0: 256x256 (оригинал)
уровень 1: 128x128
уровень 2:  64x64
уровень 3:  32x32  ... до 1x1

Как работает под капотом

В GLSL цвет берут функцией texture(sampler, uv). GPU сам выбирает уровень mipmap, исходя из того, как быстро меняется UV между соседними пикселями (производные). Трилинейная фильтрация смешивает два соседних уровня mipmap, а анизотропная улучшает резкость на наклонных поверхностях (дорога, уходящая вдаль).

Частые ошибки

  • Не сгенерировать mipmap — далёкие текстуры мерцают и шумят.
  • UV вне диапазона [0,1] без понимания режима wrap (repeat/clamp) — текстура повторится или растянется неожиданно.
  • Использовать nearest-фильтрацию там, где нужна гладкость — увидите ступенчатые квадраты.

Итоги

  • UV-координаты (0..1) сопоставляют точкам меша точки текстуры.
  • Сэмплирование переводит UV в тексели; дробные позиции требуют фильтрации.
  • Билинейная фильтрация смешивает 4 соседних текселя — гладкость вблизи.
  • Mipmap — уменьшенные копии для далёких поверхностей, убирает мерцание.
Проверьте себя
1. В каком диапазоне обычно лежат UV-координаты?
AОт 0 до 255
BОт 0 до 1 по каждой оси
CОт -1 до 1
DОт 0 до размера текстуры в пикселях
2. Зачем нужны mipmap?
AЧтобы хранить текстуру в RAM
BУменьшенные копии убирают мерцание далёких поверхностей
CЧтобы ускорить вершинный шейдер
DДля прозрачности
3. Что делает билинейная фильтрация при дробной координате текселя?
AОкругляет до ближайшего текселя
BСмешивает 4 соседних текселя пропорционально расстоянию
CБерёт чёрный цвет
DГенерирует mipmap