Текстуры: 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 — уменьшенные копии для далёких поверхностей, убирает мерцание.