Параллаксный мэппинг для отверстий от пуль

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

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

Более распространенная техника это наносить декали (decal, клеймо) на стену. Декаль это двухмерное изображение какого-то повреждения, типа отверстия от пули, которое каким-то способом размещается на поверхности геометрии окружающей среды. Декали также используются и для других внутреигровых изменений мира, создаваемых игроком, типа граффити на стенах и брызг крови.

ДЕКАЛИ И ПУЛЕВЫЕ ОТВЕРСТИЯ НЕ СОЧЕТАЮТСЯ

Существует несколько способов реализовать декали. Один из них создание нового квада (quad, четырехугольник), выровненного по поверхности стены, с текстурой декали наложенной на квад. В другом способе вы могли бы сделать копию секции геометрии стены и разместить декаль, регулируя ее UV координаты. Или вы могли бы нанести декаль как дополнительный проход рендеринга на оригинальной геометрии.

Каждый из этих методов имеет свои плюсы и минусы, а тот, который вы выберете, будет зависеть от того, для чего вы вообще используете декали. Мы хотим иметь возможность создания произвольного числа пулевых отверстий на меш (mesh), так как вы захотите нашпиговать пулями всю его поверхность. В этом случае, вероятно, лучше всего создать новый квад для каждого нового пулевого отверстия.

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

Параллакс мэппинг удивительно простая техника, но может быть весьма трудно мысленно представить, как именно она работает. По существу, мы храним попиксельную карту глубины (depth map) для текстуры декали, затем для каждого пикселя, участвующего в рендеринге, мы сдвигаем UV координаты в зависимости от глубины в этом пикселе и видового вектора (view vector). Лучше всего объяснить это на рабочем примере.

СОЗДАНИЕ РЕСУРСОВ

Для начала нам нужно пулевое отверстие, поэтому мы создаем детальную трехмерную текстурированную модель, как на Иллюстрации 1. В частности эта модель чрезмерно детализована и содержит более чем 1 000 многоугольников. Но это не важно, поскольку мы используем ее только для того, чтобы сделать текстуру нашей декали и карту глубины.

Из модели мы формируем дуффузную карту (diffuse map)(иллюстрация 2A) с альфа каналом, который соответствует контуру отверстия (иллюстрация 2B). Мы также формируем карту нормалей (normal map)(иллюстрация 3A), которая содержит карту глубины в альфа канале (иллюстрация 3B). Объединенная карта нормалей и карта глубины часто упоминается как карта рельефа (relief map).

Есть множество различных путей получить карты глубины и нормалей. В отдельных случаях вы могли бы нарисовать карту глубины вручную. На иллюстрации 3B вы можете видеть, что это довольно просто. Поверхность стены черная со значением глубины равным нулю. Плоское основание пулевого отверстия белое, имеющее значение глубины 255 (которое является 1.0 в шейдере; подробнее об этом ниже). Стенки пулевого отверстия гладкий переход от черного к белому. Если вы нарисовали эту карту глубины вручную, то затем вы могли бы сделать карту нормалей из карты глубины при помощи любого из нескольких бесплатных инструментов.

Однако вы получите лучшие результаты, если вы сделаете карту глубины и карту нормалей непосредственно из трехмерной модели. Я создал примеры, показывающие использование 3ds Max и функции Render to Texture для создания соответствующей пары диффузной карты и карты рельефа из высокодетализованной модели. Все ресурсы, которые я здесь использую, вместе с кодом шейдера, могут быть загружены с www.gdmag.com.

ДЕЛАЕМ МАТЕМАТИКУ

При рендеринге треугольника на пиксельном уровне рассмотрим точку (P) на этом треугольнике. Если бы рендеринг треугольника шел обычным способом, то P имела бы UV координаты связанные с ней, и мы имели бы видовой вектор (v). Данными UV координатами мы сделали бы выборку образца текстуры и затем использовали этот цвет, чтобы применить освещение и другие возможности.

Однако с параллакс мэппингом мы выполняем несколько очень простых дополнительных шагов:

1. Прочитать значение глубины в этих UV координатах
2. Преобразовать видовой вектор в тангенциальное пространство (tangent space)
3. Отмасштабировать видовой вектор значением глубины, которое мы только что считали
4. Добавить компоненты x и y к координатам U и V
5. Использовать новые UV координаты.

Математика проста, а шаг 2 кажется наиболее сложной частью. Видовой вектор это вектор от камеры к пикселю в видовом пространстве (view space). Это означает, что вращение камеры уже было применено вершинным шейдером. Тангенциальное пространство (иногда называемое пространством касательных, прим. перев.) это система координат, заданная тремя единичными базисными векторами: нормаль (normal), бинормаль (binormal), и тангент (tangent). Эти основные векторы могут попиксельно меняться. Чтобы преобразовать их в тангенциальное пространство, нужно сформировать матрицу вращения из базисных векторов и затем умножить вектор на эту матрицу.

Когда у нас есть видовые векторы в тангенциальном пространстве, мы находимся в ситуации, показанной на иллюстрации 4. Иллюстрация показывает поперечное сечение пулевого отверстия. Точка, участвующая в рендеринге P; видовой вектор в тангенциальном пространстве v. Поскольку это поперечное сечение, то вы не можете видеть компоненту y. Мы видим только компоненту x (слева направо) и компоненту z.

В точке P мы считываем глубину пулевого отверстия (d). Видовой вектор нормализуется и затем масштабируется значением d (с помощью произвольной постоянной вы можете сделать отверстие глубже или мельче). Результирующий вектор затем добавляется к UV координатам в точке P, игнорируя компоненту z. Это дает нам UV координаты для новой виртуальной точки P’. (Примечание к иллюстрации 4, v вектор, d скалярная глубина, а не вектор).

Пытаясь представить то, что происходит важно понимать, что точки, участвующие в рендеринге, не двигаются сами по себе. Мы только регулируем UV координаты точки P таким образом, что они соответствуют UV координатам P’. Рендеринг точки P идет всё еще в позиции P, но только с UV координатами P’. Думайте об этом как будто точка, участвующая в рендеринге, немного стягивает текстуру. Чем больше глубина и чем больше угол между видовым вектором и поверхностью, тем больше расстояние между точкой, принимающей участие в рендеринге, и точкой, фактически используемой в текстуре.

Листинг 1 показывает реализацию этой дополнительной обработки в пиксельном шейдере. Видовой вектор, UV координаты и базисные векторы передаются в пиксельный шейдер из вершинного шейдера. Остальной код в пиксельном шейдере точно то же самое, что и в обычном пиксельном шейдере в смысле освещения и других его особенностей. Все, что было добавлено шаги описанные выше, которые изменяют UV координаты.

ПРОСТОЕ ПЕРЕКРЫТИЕ

Слово «параллакс» относится к эффекту, при котором объекты, находящиеся ближе к зрителю, перемещаются больше чем объект, находящийся дальше, в случае, когда зритель перемещает свое положение под прямым углом к этим объектам. Эффект параллакса как таковой наилучшим образом оценивается в движении. Иллюстрация 5 показывает отверстия от пули, нанесенные на поверхность с параллакс мэппингом и без него. Иллюстрация 5A показывает отверстия от пули, отрендеренные обычным способом. Иллюстрация 5B показывает их же при использовании параллакс мэппинга. В этих статичных изображениях нет большого различия, но обратите внимание на то, как более близкие стороны отверстия сократились, и далекие стороны выросли.

Параллакс мэппинг простая техника. Это означает, что она относительно дешевая и совместима с бОльшим количеством графических карт, чем метод ray-casting. Однако, это все еще лишь приближение к тому, что вы на самом деле хотели бы видеть на экране и имеет множество проблем. Самое очевидное это то, что нет перекрытия. Вы можете всегда видеть всю текстуру, она лишь искажена. Вокруг смещающейся текстуры еще больше искажений и основание пулевого отверстия кажется частично залезшим на стенки отверстия.

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

Для начала заметьте, что основание пулевого отверстия имеет постоянное значение глубины 1.0. Мы хотим, чтобы основание отверстия было не искаженным и было должным образом перекрыто. Это может быть достигнуто следующим способом: сначала предположим, что точка P имеет глубину 1.0, а затем найдем проектируемую точку для P (то есть найдем для точки P соответствующую ей точку P’ при помощи обычного параллакс мэппинга, в предположении, что в точке P значение глубины d 1.0, прим. перев.). Если эта точка также имеет глубину 1.0, тогда мы знаем, что луч на самом деле пересекает основание отверстия, независимо от глубины в точке P. Если это не так, тогда мы повторно вычисляем сдвиг, используя настоящее значение глубины в точке P. Перекрытие теперь обеспечивается пикселями основания, скользящими под альфа маской. Относительное движение основания также становится более реалистичным.

Для реализации данной модификации мы удаляем начальную выборку глубины и заменяем вычисление смещения на Листинг 2. В результате пиксельный шейдер на GeForce 6800 GT потребляет 40 инструкций вместо 35 (включая освещение).

Хотя в результате наших усилий подобная схема будет выглядеть немного лучше, у нас все еще остается проблема с текстурой, которая «полосит» при рассматривании под большим углом, особенно около края отверстия, на самой дальней от зрителя стороне.

Это происходит в случаях, когда глубина в точке P равна 1.0, а луч все же фактически пересекает границу довольно близко к верху, где P ближе к 0.1. Но мы можем получить удивительно эффективное улучшение, просто усредняя два значения глубины, которые мы считали ранее. И опять мы получаем простую и дешевую модификацию, не требующую никаких итераций и лишь добавляющую 2 инструкции к нашему шейдеру. См. Листинг 3.

ОБЩИЕ ПРОБЛЕМЫ

Самая большая проблема, с которой я столкнулся при реализации параллакс мэппинга, это обеспечить совместимость систем координат. Системы координат, используемые 3ds Макс и DirectX правая и левая соответственно. Переход от одной к другой требует изменений в шейдере. Мне в частности пришлось изменить порядок базисных векторов, чтобы сделать вычисления тангенциального пространства правильным.

Используемая здесь карта высоты (height map) это на самом деле карта глубины, изменяющаяся от 0.0 до 1.0 в плоскости текстуры. Для того чтобы учесть детали над поверхностью текстуры различные реализации параллакса используют как интервал 0.0, 1.0 так и интервал -1.0, 1.0. Мы реализуем специальный шейдер для пулевых отверстий, так что осознавайте эти различия, когда смотрите другой код.

У вас также могли бы возникнуть проблемы со знаком в картах высоты и картах нормалей (имеются в виду положительные/отрицательные значения компонент карт, прим. перев.). И хотя вы могли бы перевернуть их в шейдере, более эффективно иметь их правильными изначально. Это вы можете быстро протестировать, инвертируя компоненты карт в Photoshop.

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

Добавить комментарий