Kris

KTX2 на практике: правильный подход к сжатию текстур

3D-сжатиеСжатие текстурKTX2Basis UniversalglTF

В предыдущей статье мы разобрали отношения между форматами текстур GPU, Basis Universal и KTX2. Теперь, когда теория ясна, переходим к практике: как выбрать между ETC1S и UASTC, какие инструменты использовать, какие команды вводить и как загружать результат в движках.

Статья готова к копированию — можно читать и сразу применять.

Сначала самое важное: ETC1S или UASTC

Basis Universal предлагает два промежуточных кодирования. Ошибка в выборе — это не просто «недостаточно хорошо», а полное смазывание карт нормалей. Запомните эту таблицу:

ETC1SUASTC
Степень сжатияОчень высокая (как JPG)Средняя (как качественный PNG)
КачествоДостаточно для цветных картБлизко к оригиналу
Память GPU (после трансформации)Обычно 4 бита на пиксель (примерно 1/8 от оригинала)Обычно 8 бит на пиксель (примерно 1/4 от оригинала)
Скорость кодированияМедленная (можно регулировать)Быстрая
ПрименениеAlbedo/диффузные, emissiveNormal, metalness-roughness, текстурные карты данных
Не подходитNormal, карты с точными числовыми значениямиЦветные карты (из пушки по воробьям — объём больше)

Почему карты нормалей нельзя использовать с ETC1S? Потому что карты нормалей хранят векторы направления, где каждый пиксель имеет три канала RGB, взаимно ограниченные (длина вектора ≈ 1). ETC1S — это блочное сжатие, оптимизированное для «визуально правильного цвета». Оно нечувствительно к точности отдельных каналов, поэтому после сжатия направление вектора искажается, и освещение сразу становится некорректным — особенно в областях бликов и высокочастотных деталей. UASTC лучше сохраняет числовые значения и выдерживает такие требования к точности.

Практическое правило:

  • Цветные карты (albedo, emissive) → ETC1S
  • Карты данных (normal, roughness, metallic, AO, толщина) → UASTC
  • Если сомневаетесь и хотите сэкономить объём → сначала попробуйте ETC1S, если при приближении видно размытие — переходите на UASTC

Сравнение четырёх форматов на одной текстуре

Возьмём для примера карту albedo размером 2048×2048 (данные — типичные значения из сообщества, ориентировочные):

ФорматРазмер на дискеПамять GPU (включая мипмапы)Загрузка на GPUКроссплатформенность
PNG~5 МБ~22 МБМедленно
WebP~1 МБ~22 МБМедленно
KTX2 (ETC1S)~0.5–0.8 МБ~2.8 МББыстро
KTX2 (UASTC)~3–4 МБ~5.6 МББыстро

Обратите внимание: WebP занимает столько же памяти GPU, сколько PNG — он просто меньше на диске, но при загрузке в видеопамять всё равно распаковывается в исходные пиксели. KTX2 с ETC1S одновременно уменьшает и размер на диске, и в памяти GPU — в этом его главная ценность.

Инструментарий: три пути

Сжать текстуры в KTX2 можно не одним инструментом. Расположим по «удобству использования»:

1. toktx (официальный, самый мощный)

Официальный инструмент Khronos, полный набор параметров, подходит для обработки отдельных текстур.

# Преобразовать PNG в KTX2 с кодированием ETC1S
toktx --bcmp --uastc 0 albedo.ktx2 albedo.png

# Преобразовать PNG в KTX2 с кодированием UASTC
toktx --uastc 1 normal.ktx2 normal.png

Популярные параметры:

# ETC1S + уровень качества (1–255, по умолчанию 128, чем выше, тем лучше качество и больше объём)
toktx --bcmp --uastc 0 --qlevel 200 albedo.ktx2 albedo.png

# UASTC + суперсжатие (Zstandard, дополнительно уменьшает размер на диске)
toktx --uastc 1 --zcmp 19 normal.ktx2 normal.png

# Автоматическая генерация мипмапов (настоятельно рекомендуется)
toktx --bcmp --genmipmap albedo.ktx2 albedo.png

# Задать цветовое пространство sRGB (обязательно для цветных карт)
toktx --bcmp --srgb albedo.ktx2 albedo.png

--bcmp — это флаг режима ETC1S (базовый режим Basis Universal), --uastc 1 — режим UASTC. Они взаимоисключающие.

2. gltf-transform (самый удобный, настоятельно рекомендуется)

Если у вас целая модель glTF/GLB, используйте gltf-transform одной командой, чтобы заменить все текстуры на KTX2, автоматически выбирая ETC1S/UASTC в зависимости от назначения.

# Установка
npm install -g @gltf-transform/cli

# Однокомандное сжатие всей модели
gltf-transform optimize model.glb model-optimized.glb \
  --texture-compress basisu

Он сам определяет тип текстуры: для цветных использует ETC1S, для карт данных — UASTC, и автоматически прописывает расширение KHR_texture_basisu. Это лучший выбор для 90% сценариев — не нужно вручную вбивать toktx для каждой карты.

3. Онлайн-инструменты (самый быстрый старт)

Когда нет возможности устанавливать окружение, используйте браузерные инструменты: gltf.report (онлайн-версия gltf-transform), KTX2 Converter и т.п. Загрузил, скачал — готово. Подходит для ранних экспериментов или одноразовых задач.

Полный пайплайн: от исходника до продакшена

Стандартный процесс (схема):

Исходники (PNG/JPG/PSD/TGA)
        │
        ├── [Целая модель] gltf-transform optimize model.glb → автоматическое определение типа текстуры
        │                    └─ Цветные карты → ETC1S
        │                    └─ Карты данных → UASTC
        │                    └─ Запись расширения KHR_texture_basisu
        │
        └── [Отдельные текстуры] toktx → ручное указание ETC1S/UASTC + цветовое пространство + мипмапы
        │
        ▼
Сжатые KTX2 / GLB
        │
        ▼
Загрузка в движке (Three.js / Babylon.js) ── трансформация на лету → нативный формат GPU

Загрузка KTX2 в Three.js

Three.js поддерживает KTX2 нативно с версии r129: подключите KTX2Loader (укажите путь к wasm-файлам transcoder, определите возможности GPU) к GLTFLoader, после чего загрузка glTF с текстурами KTX2 будет автоматически декодироваться.

const ktx2Loader = new KTX2Loader().setTranscoderPath("/basis/").detectSupport(renderer);
gltfLoader.setKTX2Loader(ktx2Loader); // Если модель также использует Draco/MeshOpt, не забудьте подключить DRACOLoader / MeshoptDecoder

Обязательно вызывайте detectSupport(renderer) — он определяет, в какой нативный формат делать трансформацию. Также для цветных текстур нужно установить texture.colorSpace = THREE.SRGBColorSpace. Пропуск этого шага — классическая ошибка, когда «вся картинка серая». Для карт данных (normal, roughness и т.д.) цветовое пространство остаётся линейным.

Загрузка KTX2 в Babylon.js

В Babylon.js всё проще — GLTFFileLoader по умолчанию поддерживает расширение KHR_texture_basisu и автоматически скачивает transcoder с CDN. Загрузка glTF с текстурами KTX2 работает из коробки. В офлайн-среде или внутренней сети нужно вручную настроить BASISFileLoader.TranscoderModule.

Настройка параметров сжатия: баланс качества и объёма

Ключевой параметр ETC1S — --qlevel (1–255). Как он влияет на результат:

qlevelОбъёмКачествоВремя кодированияПрименение
128 (по ум.)МаленькийДостаточноСреднееБольшинство случаев
200–255БольшеБлизко к без потерьДолгое (в разы)Высокие требования к качеству
60–100Очень маленькийВидимые блокиБыстроДальний план / маленькие текстуры

Объём UASTC относительно фиксирован, в основном регулируется параметром --zcmp (суперсжатие Zstandard), который влияет только на размер на диске, не на память GPU (после распаковки всё равно 8 бит на пиксель).

Рекомендуемый порядок настройки:

  1. Сначала сожмите с параметрами по умолчанию, посмотрите на объём и качество
  2. Если не устраивает → меняйте --qlevel (ETC1S) или добавляйте --zcmp (UASTC)
  3. Если карта нормалей стала размытой → убедитесь, что используется UASTC, а не ETC1S
  4. Если цвета стали тёмными → проверьте настройки цветового пространства (флаг sRGB и параметр colorSpace в движке)

Часто задаваемые вопросы и решение проблем

Ошибка трансформации / ошибка загрузки

  • Проверьте, правильный ли путь к wasm-файлам transcoder (в Three.js нужен setTranscoderPath)
  • Проверьте, поддерживает ли версия движка текущую версию KTX2 (старые кодировки Basis могут не поддерживаться новым transcoder)
  • В консоли обычно есть конкретное сообщение об ошибке — ищите по ключевым словам

Цвета тёмные / светлые

  • Цветная карта (albedo) не настроена на sRGB, или настроена наоборот
  • При использовании toktx забыли флаг --srgb (для цветных карт нужно)
  • На карту данных (normal) ошибочно добавлен sRGB

Отсутствие мипмапов, мерцание вдали

  • При сжатии не добавлен --genmipmap
  • В движке не включён texture.generateMipmaps (в Three.js для KTX2 по умолчанию берётся из файла, но всё равно нужно, чтобы у материала minFilter был установлен в режим мипмапов)

Приближение показывает неправильное направление нормалей

  • Карта нормалей использует ETC1S — замените на UASTC
  • Убедитесь, что карта нормалей в стиле OpenGL (зелёный канал направлен вверх); в некоторых движках для стиля DirectX нужно инвертировать канал G

Файл стал больше

  • Для маленьких текстур (меньше 128×128) KTX2 невыгодно — блочное сжатие имеет фиксированные накладные расходы, которые занимают весь блок
  • Одноцветные текстуры не стоит сжимать в KTX2 — проще задать цвет через материал

Что дальше

Сжатие текстур на этом почти освоено. Но «уметь пользоваться инструментом» не равно «уметь выбирать правильный инструмент». В следующей статье мы сведём все знания из предыдущих четырёх статей в единую систему выбора: для десктопа, мобильных устройств, VR и мини-приложений — какие комбинации использовать в каждом сценарии.

Поддержите нас