В совсем недавнем прошлом в подавляющем числе 3D-игр эффекты взрывов реализовывались с помощью простейших текстурированных полигонов, ориентированных параллельно плоскости экрана. Подобная технология весьма проста в реализации и не требует чрезмерных вычислительных ресурсов. Основной минус подобной технологии – "врезание" вспышки взрыва в поверхности. Есть еще масса других недостатков (типа плоского внешнего вида и малой натуралистичности). Это, кстати, хорошо заметно и в движках Q3A, Return To Castle Wolfenstein, Serius Sam и т.п. (Рис. 1).
Рис.1. Врезавшаяся в поверхность вспышка взрыва.
В этой статье я попытаюсь доходчиво изложить основы идеи, позволяющей отобразить эффект объемного взрыва, лишенный артефактов, указанных выше (Рис. 2).
Рис.2 Объемный взрыв.
Краткий обзор технологии объемного взрыва
Технология придания объемности работает со взрывом, как с четко определенной частью общего пространства. Я думаю, понятно, что любой видимый полигон, наблюдаемый нами непосредственно сквозь взрыв, меняет свои видимые свойства в соответствии со свойствами самого взрыва. Это касается полигонов, находящихся непосредственно в объеме взрыва (для упрощения это – сфера) и тех, что находятся позади взрыва, как показано на Рис. 3.
Рис.3. Здесь заштрихованная область – область взрыва.
Окончательный визуальный эффект достигается повторным рендерингом полигонов уже с учетом эффекта взрыва. Скелет алгоритма следующий:
For every rendered polygon in scene do //Каждый закрашиваемого полигона в сцене If polygon visible through explosion then //Если полигон виден сквозь взрыв, то Project explosion texture onto polygon //Проецируем текстуру взрыва на полигон Modulate intensity of polygon depending on distance from explosion //Задаем интенсивность полигона в //зависимости от расстояния до взрыва Render polygon //Рендеринг полигона End If End For
В общем и целом нам надо выполнить три этапа вычислений: проверка на видимость, проекция текстуры и модуляция интенсивности луча. Каждый из этих этапов можно выполнить или в экранном пространстве, или в пространстве объектов.
Проверка на видимость
Проверка на видимость – это проверка на пересечение полигона с объемом взрыва. Как выглядит объем взрыва схематично изображено на Рисунке 3. Это – конус с вершиной в виде полушария. Точные расчеты в этом тесте не нужны, т.к. текстурные карты взрыва задаются весьма приблизительно в виде размытой круговой области в квадратном битмэпе (bitmap).
Рис.4. Текстура взрыва, использованная в Рис.1 и 2.
Наши расчеты оперируют объемом, аппроксимированным усеченной пирамидой, используемой в типовых расчетах видимого объема с передней отсекающей плоскостью, расположенной в точке на сфере взрыва, лежащей ближе к камере. Задняя отсекающая плоскость взрыва совпадает с дальней отсекающей плоскостью всей сцены. Если проверка на видимость производится в мировом пространстве, тогда необходимо проверить на видимость каждый полигон, находящийся в объеме, определенном шестью отсекающими плоскостями. Проверка на видимость в экранном пространстве – значительно более простой процесс, поскольку проекция взрыва на плоскость экрана – круг (в случае использования конического объема) или квадрат (при использовании усеченной пирамиды). Необходимо заметить, что в случае проекции с соотношением количества пикселей по вертикали и горизонтали, отличным от 1:1, результатом на экранной плоскости будет эллипс или прямоугольник (в зависимости от типа используемого объема отсечения – конус или пирамида).
Проекция текстуры
Как только по расчетам полигон попадает в объем, занимаемый взрывом, то надо будет спроецировать на него текстуру взрыва. Это достигается проецированием базовой плоской текстуры по направлению вектора камеры. Если это проецирование происходит в мировом пространстве, то самый простой метод состоит в том, чтобы трансформировать вершины треугольника в пространство камеры (наблюдателя) и выполнить проекцию там. Заметим, что эффективный радиус взрыва придется масштабировать для того, чтобы можно было применить эффекты коррекции перспективы. Это масштабирование зависит от расстояния между вершиной и камерой.Опять же, производить вычисления в экранном пространстве намного проще, поскольку масштаб радиуса взрыва придется рассчитать лишь единожды.
Вот уравнения для этих расчетов:
Где u и v - координаты текстуры,
c – центр взрыва (в экранном пространстве)
V - текущая вершина (в экранном пространстве)
Rad – масштабируемый радиус взрыва
У нас получатся значения в диапазоне [0...1] для точек в объеме взрыва. Точкам за пределами объема взрыва присваиваются значения <0 и >1. Это можно выполнить отсечением полигонов по граням объема (вообще-то, этот вариант не рекомендуется, поскольку он чреват некачественным рендерингом) или подгонкой режима отсечения текстуры в соответствии с настройкой отсечения плоскостями. В этом режиме пикселю, чьи координаты текстуры выходят за пределы диапазона [0...1], присваивается величина цвета 0 или 1 соответственно. Отсечение необходимо только для тех полигонов, чьи координаты текстур лежат за пределами максимума диапазона, определяемого API (Direct3D ограничивает координаты текстуры в диапазоне [-128...+127]).Отсечению подвергаются также те полигоны, которые находятся между передней и тыльной сторонами камеры.
Модуляция интенсивности
Этот этап изменяет интенсивность взрыва в зависимости от расстояния между вершиной (vertex) и геометрическим центром взрыва. Для простоты полагают, что любое изменение интенсивности по направлениям экранных осей X и Y совпадает с интенсивностями на текстурной карте. Все, что нам нужно – это схема, где мы запишем величины интенсивности в зависимости от расстояния из вершины по направлению экранной оси Z. Это можно проделать двумя путями. В первом случае мы считаем, что любая точка, лежащая дальше центра взрыва по направлению от камеры, обладает максимальной интенсивностью. Во втором случае полагаем, что любая точка, лежащая позади объема взрыва по направлению от камеры, обладает максимальной интенсивностью. Какую схему выбрать, зависит от конкретной реализации вашего приложения. Необходимо заметить, что второй вариант более корректный, хотя первый – лучше поддается оптимизации (см. следующий раздел). В качестве результирующего вычисления можно применить простейшую линейную интерполяцию, хоть у этого варианта и страдает точность из-за того, что не учитывается эффект перспективы. Как и в первом случае, необходимо отсечь полигоны для получения корректных результатов. Полигоны отсекаются по плоскостям максимальной и минимальной интенсивности (т.е. передняя плоскость, проходящая через фронт взрыва и плоскость, проходящая через его центр или заднюю часть).Заметьте, что интенсивность вычисляется по-разному в зависимости от того, используется аддитивное или модуляционное смешивание в приложении при рендеринге по второму проходу. При аддитивном смешивании цвет вершины варьируется от белого до черного, изменять мы его можем с помощью альфа-компоненты.
Оптимизация
Существует два основных пути для оптимизации этого алгоритма. Первый позволяет быстро выполнить большое количество вычислений, а второй – существенно сократить количество вычислений.Алгоритм наиболее просто реализуется в экранном пространстве. Это значит, что вся геометрия преобразуется всего один раз и уже обработанная используется для получения эффекта взрыва. Не требуется никаких дополнительных преобразований (разве что преобразование некоторых референсных точек на объеме взрыва в экранное пространство). К сожалению, существует несколько потенциальных проблем при использовании этой методики (они разобраны в следующем разделе).
Второй путь оптимизации - использование гибридного алгоритма. Его суть состоит в размещении основания взрыва на плоскости с максимальной интенсивностью в объеме взрыва. Если мы будем считать, что центр взрыва обладает максимальной интенсивностью, то основание надо разместить в центре взрыва. Это позволяет ограничить объем нашего взрыва только пространством между передней частью взрыва и его основанием.
Проблемы при отсечении
Может стать заметным ряд артефактов в изображении после отсечения полигонов по объему взрыва. Как мы выяснили ранее, это отсечение нам необходимо для того, чтобы получить правильные результаты при вычислении интенсивности и избежать генерации слишком больших величин координат текстуры. Отсечение модифицирует экранную область, что требует ее перерисовки. Главная проблема с отсеченными полигонами – это проблема Z-буфера. Более приемлемое решение (другое, нежели отсечение) можно выполнить на основе возможностей Z-bias’а (по-русски можно сказать "Z-сечение"), входящего в API, для того, чтобы мы смогли правильно рендерить перекрывающиеся полигоны. К сожалению, Z-bias на сегодня применяется весьма ограниченно. Тот же самый эффект можно получить, применяя трансляцию координат экранного пространства после их генерации в Z. Этого можно достичь последующей простейшей модификацией матрицы проецирования. (Direct3D требует записи матрицы проецирования в формате, показанном далее). Требуемое Z-сечение зависит не только от разрешающей способности Z-буфера, но и от возможностей применяемого железа.- Матрица Проекции
- Матрица Z-преобразования
Ограничения
В процессе реализации этого алгоритма был выявлен ряд ограничений. Самое неприятное ограничение было вызвано реализацией экранного пространства в Direct3D. Метод IDirect3Ddevice::ProcessVertices используется совместно с буферами вершин для чтения координат экранного пространства. Проблема в том, что необходимо работать со всеми полигонами и вершинами всего экранного пространства, включая обработанные алгоритмами отсечения. Проще говоря, эффективная обработка экранного пространства требует, чтобы приложение само производило вычисления геометрических преобразований и освещения.Второе ограничение – для нормальной работы с мировым и экранным пространствами необходимо, чтобы область, видимая сквозь взрыв, была бы предварительно отрендерена с полигонами полностью. Причина в том, что при закраске взрыва не видно области экрана, закрашенные плоским цветом – Рис. 5.
Рис. 5. Одно из ограничений "истинного" метода.
Чего ждать в будущем
Эта техника довольно эффективно делает плоскостное проецирование текстуры на уровне приложения, т.е. программно. В принципе, вы можете переписать свое приложение, если эта техника будет поддерживаться на уровне API. Такой вариант хорош тем, что устранил бы необходимость в отсечении и, следовательно, устранил бы необходимость в Z-bias-рендеринге. Это касается и двойного использования текстур для графики и интенсивности взрыва. Карта интенсивности представляла бы собой простейшую градиентную текстуру, проецируемую вертикально - вдоль отрицательной части оси Y экранного пространства. Взрыв проецировался бы вдоль экранной Z-оси. Это позволило бы использовать эту технику в системах с W-буферизацией и избавило бы от всяких "нехорошестей", сильно ухудшающих производительность.В статье использованы идеи и мысли девелоперов, "витающие в воздухе", на различных форумах и конференциях. Реализация была опробована на видеокарте GeForce2MX 400.