Как я упоминал в предыдущем блог-посте, я планировал реализовать сжатие текстур рельефа, чтобы снизить требования к памяти. Все видеокарты поддерживают S3TC - алгоритм сжатия текстур с потерями, который уменьшает размер, занимаемый текстурой в видеопамяти, в 2-4 раза, и «разжимает» её на лету во время рендеринга. Декомпрессия реализована на аппаратном уровне, поэтому она не влияет на производительность (она даже может увеличиться в сценариях, где важна пропускная способность контроллера памяти, поскольку поток данных между VRAM и GPU уменьшается в 2-4 раза).

На скриншотах в этом посте можно перемещать ползунок для сравнения вида с применением сжатия текстур и без него. Также можно щелкнуть правой кнопкой мыши и выбрать «Открыть в новой вкладке», чтобы рассмотреть скриншоты в полном разрешении (сделайте это два раза - в левой и правой части изображения).

Planet from space: Before Planet from space: After

Планета из космоса - разница почти не заметна

Так что я реализовал сжатие текстур в форматы DXT1, DXT5, LATC1 и LATC2 (или их DirectX-эквиваленты BC1, BC3, BC4 и BC5). Сжатие выполняется на лету специальным шейдером, после генерации/загрузки текстуры рельефа. В нижеследующей таблице представлена ​​сводка форматов и их использование в SE. Для тех, кто интересуется деталями, вот отличная статья об алгоритмах сжатия, применяемых в видеокартах.

ФорматКаналыКоэф. сжатияКачествоИспользование в SE
DXT1 (BC1)3 (RGB)1:4среднеене используется
DXT5 (BC3)4 (RGBA)1:4среднеене используется
DXT5 (BC3) YCoCg3 (RGB)1:4высокоекарта цвета (альбедо), карта свечения (огни городов)
LATC1 (BC4)1 (ч/б)1:2высокоепрозрачность облаков, маска воды/льда, шероховатость, высоты детальных текстур
LATC2 (BC5)2 (2x ч/б)1:2высокоекарта нормалей
16 бит Без сжатия1 (ч/б)1:1ультракарта высот, карта температуры

Surface view 1: Before Surface view 1: After

Здесь можно заметить некоторую разницу

До реализации сжатия, текстуры в SE были разложены так:

Высоты16 бит ч/б
Цвет + маска воды/льда8 бит RGBA
Нормали8 бит RGBAиспользовались только 2 канала, в 0.980 другие 2 канала хранили 16-битное значение высоты
Свечение8 бит RGBAальфа-канал задавал режим: огни городов, постоянные огни, или тепловое свечение; RGB часть задавала или цвет огней, или температуру поверхности
Детальные цвет + шероховатость8 бит RGBA
Детальные нормали + высоты8 бит RGBA

В 0.990 я переключился на отдельную 16-битную текстуру для карт высот, чтобы улучшить точность и качество. Эта карта является базовой для ландшафтного движка: она используется для смещения вершин сетки и генерации всех других карт (нормалей, цвета, свечения и т.д.). К сожалению, для 16-битных карт не существует сжатых форматов S3TC. Но карта высот всё равно должна храниться без потерь, в максимально возможном качестве.

При реализации сжатия мне пришлось отделить альфа-канал некоторых карт в отдельные текстуры, потому что выбранные форматы сжатия (YCoCg DXT5 и LATC2) не поддерживают альфу. Я выбрал DXT5 в цветовом пространстве YCoCg, потому что он имеет лучшее качество, чем стандартные DXT5 или DXT1. Формат LATC2 - лучший выбор для карт нормалей (DXT-сжатие приводит к слишком большим артефактам в освещении). В этом формате хранятся только две компоненты вектора нормали, третья вычисляется шейдером планеты на лету.

Surface view 2: Before Surface view 2: After

Ранее в карте теплового свечения хранилась температура поверхности в виде 24-битного значения, закодированного в RGB-каналах. Такая большая точность необходима, чтобы избежать бандинга (появления полос - тепловое излучение очень чувствительно к изменениям температуры, поэтому температура должна храниться как можно точнее). Но сжатие с потерями закодированных данных полностью разрушает их: на вулканах и поверхностях звёзд появляются крупные и очень яркие артефакты. Поэтому я отделил тепловую карту в отдельную текстуру, хранящую температуру в несжатом 16-битном формате. Планеты теперь имеют две карты: старая GlowMap используется для огней городов или постоянных огней (видимых даже в дневное время, например, так сделана лава на Ио) и новую TempMap («температурная карта»), которая просто задаёт температуру поверхности в Кельвинах. 16 бит могут хранить значения от 0 до 65535 - вполне достаточно для описания температуры планеты или звезды (65 тысяч Кельвинов - это ослепительное синеватое сияние, более высокие температуры дадут почти такой же оттенок синего цвета). Новую карту TempMap можно задавать в скриптах точно так же, как и GlowMap; планета может иметь обе карты одновременно.

Ещё один бонус выделенных 16-битных тепловых карт: поверхности звёзд теперь гладкие, без бросающихся в глаза квадратов!

Новая раскладка текстур теперь такая:

Высоты16 бит без сжатия
ЦветYCoCg DXT5 или LATC1LATC1 используется для ч/б текстур (например, облака Земли)
Маска воды/льда или прозрачность облаковLATC1
НормалиLATC2Используются только 2 канала
СвечениеYCoCg DXT5 или LATC1LATC1 используется для ч/б текстур (например, огни городов Земли)
Температура16 бит без сжатиятемпература в Кельвинах
Детальный цветYCoCg DXT5
Детальная шероховатостьLATC1
Детальные нормалиLATC2
Детальные AOLATC1пока не реализовано, но легко может быть добавлено
Детальные высотыLATC1пока не реализовано, понадобится для Parallax Occlusion Mapping

Так много текстур, но они используют только 4 разных формата: 16 бит без сжатия, DXT5, LATC1 и LATC2. Существуют и более продвинутые аппаратные форматы сжатия - BC6, BC7, ASTC и др., но их проблематично использовать в SE. Некоторые слишком медленно сжимаются (например, BC7 - около одной секунды на сжатие текстуры 512x512 с использованием CUDA на GTX680, по сравнению с 10 мс для DXT5), другие не имеют широкой поддержки в видеокартах для настольных систем (ASTC чаще встречается на мобильных устройствах).

Close up 1: Before Close up 1: After

Крупный план - заметна разница в тонких деталях

Движок сжимает текстуры ландшафта сразу после их генерации или загрузки с диска. Это не бесплатный процесс, он занимает 2-5 миллисекунд на каждую текстуру, поэтому время загрузки увеличивается на 20-50%. Но сжатие экономит кучу памяти! Трудно сказать точно, сколько именно, потому что планеты генерируют разные наборы текстур, в зависимости от типа планеты и высоты камеры. Без сжатия кеш для 4000 нодов рельефа потреблял 1200-1500 Мбайт видеопамяти (на LOD 0); с использованием сжатия это число падает до 500-600 Мбайт. Поэтому можно считать, что средняя экономия - в 2,5 раза. Неплохо, учитывая, что SE по-прежнему использует карты высот и температуры в несжатом формате.

Я также сжал библиотеку материалов (изображения скал/гальки/травы/песка, используемые для генерации детальных текстур). Это было легко, потому что SE уже поддерживал S3TC текстуры в виде файлов в формате *.dds (они используются для некоторых текстур кораблей и галактик). Для этого я воспользовался инструментами nvcompress и ImageMagic (просто написал несколько bat-файлов для автоматизации процесса). До этого 49 материалов занимали 522 МБ видеопамяти, теперь они занимают всего 130 МБ (в 4 раза меньше). Это также ускорило загрузку библиотеки материалов при запуске SE на порядок! (1 секунда вместо 10).

Close up 2: Before Close up 2: After

Ещё один крупный план - тут уже существенная разница. Распределение детальных текстур строится на основе глобальных текстур планеты. Если глобальные текстуры сжимаются с потерями, особенно карты нормалей, это может привести к существенно другому результату. Обойти это можно, не сжимая те уровни, которые используются для генерации детальных текстур.

Теперь в настройках графики есть опция «Сжатие текстур». Она переключает режим только для вновь создаваемых текстур, т.е. старые текстуры, которые уже находятся в видеопамяти, не меняют свой формат. В любом случае, они будут удалены ландшафтным движком, когда потребуется освободить место для новых текстур.

Normals closeup: Before Normals closeup: After

На рассвете легко видна тонкая разница в освещении. Это связано с тем, что небольшая разница между сжатыми и не сжатыми нормалями приводит к значительному изменению направления света/тени.

Есть небольшая проблема с Землей и другими реальными планетами. В течение многих лет они использовали необычный размер текстуры - 258x258. Видеокарты не любят размеры, не являющиеся степенью двойки, поэтому разрешение текстур процедурных планет составляет 256х256. Раньше это не было серьезной проблемой, но теперь всё изменилась. Сжатие S3TC основано на блоках размером 4x4 пикселя, поэтому размер текстуры должен быть кратным 4 (а 258 не делится на 4). Для не-кратных текстур, код сжатия добавляет бордюры к текстуре и ее мип-уровням, чтобы сохранить их размер кратным 4. Это приводит к некоторому "сползанию" текстур на рельефе. Оно может быть исправлено путем применения обратного сдвига в шейдере, но я думаю, что лучше просто переработать все текстуры реальных планет, чтобы сделать их размер 256x256. Это даст два преимущества. Во-первых, позволит использовать фиксированный кеш текстур в видеопамяти: просто выделить по 1000 текстур для каждого из 4 форматов и повторно использовать их, а не выделять новую текстуру каждый раз. Я использовал динамическое выделение текстур с самого начала работы над SE, потому что движок не может знать, какое разрешение тайлов будет у каждой следующей планеты. Выделение памяти - медленная операция, поэтому повторное использование фиксированного набора текстур сократит время генерации рельефа. Второе преимущество заключается в том, что текстуры реальных планет будут переработаны так, чтобы иметь бордюры шириной в 2 пикселя, что позволит устранить швы в картах нормалей, которые они в настоящее время имеют.

Я экспериментировал с сохранением текстур Земли в dds формат, но в результате получается pak-файл гораздо большего размера, чем с используемыми сейчас форматами jpg/png (или png без потерь в аддонах HD и Ultra). Так что SE по-прежнему будет использовать форматы jpg/png для хранения текстур на диске, и пережимать их в S3TC на лету во время загрузки. Зато это даст возможность отключить сжатие в настройках, чтобы обеспечить максимальное качество текстур реальных планет.

Обсудить этот пост можно на форуме.