Рендер в демке теней от полупрозрачных объектов.
Автор: Вячеслав Е.
Несколько человек пожелало, чтобы я написал, как всё сделано в этой демке. Свободное время есть, так что решил их порадовать. И так, пнём дохлую лошадь ещё раз.
Хотелось сделать как лучше, и чтобы все эффекты работали на картах с поддержкой SM2.0. В основном, разработка производилась на ноутбуке с интегрированной видеокартой ATI M200, которая в процессе жарила мне колени.
Для начала, отдельно о разных эффектах.
HDR решил сделать фейковый, с использованием RGBA8 текстур. Недостаток один – потеря качества, но вот плюсов оказалось больше: быстрее отрисовка, меньше занимаемой памяти, поддержка альфаблендинга и АА на всём спектре железа, и отсутствие хардварного бага на R3** (discard() при рендере в fp текстуры не отменяет запись в буфер глубины).
Средняя яркость картинки для адаптации рассчитывается опытным путём. В рендер таргет размером 256х256 рисуется финальная картинка. При этом включен шейдер, который отбрасывает фрагменты, яркость в которых ниже определённой величины. Во время этого рендера используется occlusion query для нахождения количества фрагментов, которые прошли тест. После того, как результат OQ готов и может быть получен, разрешается провести данный рендер с другой тестовой величиной. Имеется два значения для сравнения: 1/3 и 2/3. Таким образом находятся процентная величина для разных диапазонов яркости: [0; 1/3], [1/3; 2/3] и [2/3; 1]. Исходя из преобладания того или иного диапазона, меняется параметр средней яркости изображения. Если много фрагментов с яркостью выше 2/3, средняя яркость увеличивается, если много фрагментов с яркостью ниже 1/3, средняя яркость уменьшается.
Значение средней яркости передаётся в вертексный шейдер, где оно изменяет цвет источника света. Результат схож с тем, который можно получить пост-процессом над fp текстурой, но так как я использую RGBA8, то делать это нужно в процессе расчёта цвета фрагмента.
Также имеется Bloom. Для начала производится Bright Pass. Рендер в текстуру 256х256 с шейдером, который уберёт цвета с яркостью ниже определённой (я выбрал значение 0.8). После первого прохода производиться downsampling в текстуры размером 128х128 и 64х64. Каждая текстура методом Гаусса размывается по горизонтали, а затем по вертикали. Размер результирующего размытия - 26х26.
Исходное изображение:
Размытая текстура 256х256 с результатом Bright Pass, а также размытые текстуры 128х128 и 64х64:
Сумма этих текстур:
Значение из этих текстур будет прибавлено к финальному изображению:
Объёмное освещение (Volumetric Lightning, Light Shafts). Красивый эффект, возникающий от того, что воздух не везде чистый, но местами содержит мелкие частицы, отражающие свет.
Для реализации необходимо подготовить геометрию в виде набора квадов (у меня 32), заполняющих единичный куб. В дальнейшем, этот набор будет рисоваться в позиции источника света, с матрицей трансформации, которая увеличит единичный куб до радиуса источника света, а также повернёт его к камере, как Billboard.
Используя шейдер, который рассчитает упрощённое освещение для каждого фрагмента с учётом теней, получим нужный результат. Используется два варианта шейдера:
Первый вариант для точки в тени возвращает чёрный цвет, а для освещённой - цвет источника света с учётом ослабления от расстояния и умноженный на константу, значение которой чуть больше чем 1.0/quadCount.
Второй вариант возвращает результат отличный от 0.0 для точек, которые находятся за плоскостью стекла, если, конечно, они не находятся в тени от непрозрачных объектов.
С учётом блендинга рисовать это сразу в кадр очень затратно. Также, результат следует размыть, чтобы скрыть ограниченное количество плоскостей. Поэтому отрисовка происходит в отдельную текстуру небольшого размера (256х256). Потом эта текстура в два прохода размывается по горизонтали и по вертикали. Размытие такое же, как описано выше в тексте про Bloom. После этого получаем более мягкий результат:
Эта текстура добавляется вместе с Bloom’ом к финальному изображению.
Объёмный туман[/b]. Хотелось добавить туман, который бы стелился по полу, сгущаясь в углах и у плинтуса. Простой метод, где величина тумана зависит от расстояния до камеры тут уже не подходит. В моём случае, объём, где находится туман, задан произвольным мешем, сделанным вручную. Чтобы получить величину тумана в точке, необходимо узнать, глубину, которую проходит луч от геометрии уровня через объём тумана. Для этого понадобиться узнать глубину сцены, и задних фейсов объёма тумана.
Линейная глубина сцены (float) пакуется в три канала RGBA текстуры:
Во время отрисовки передних граней тумана, глубина распаковывается, и рассчитывается разница между расстоянием до фрагмента тумана и расстоянием из текстуры. В зависимости от расстояния производится переход от цвета сцены к цвету тумана.
При такой реализации, при отрисовке невыпуклых объёмов можно получить небольшие ошибки, в случаях, когда луч пересекает меш в двух разных местах, а также туман пропадает, когда камера находиться внутри объёма. Эти проблемы поправимы, первая довольно тяжёлым методом, а вторая довольно просто: можно например, рисовать задние грани объёма тумана с другим шейдером, который вместо разницы в расстояниях возьмёт само расстояние до задней грани.
Преломление изображения у стекла. Полных и честных отражений окружения у меня нет, только блик от источника света. Поэтому эффект реализуется простым пост-процессом. Берётся текстура с содержимым кадра, и проецируется на стекло с небольшим сдвигом по .xy компонентам нормали. Также цвет из текстуры кадра перемножается на цвет стекла.
Огонь сделан системой частиц. В качестве текстуры использована анимация взрыва, упакованная в volume texture. Частицы анимируются в вершинном шейдере. Для этого у вершины имеется начальная позиция в плоскости XZ (у меня Y – вверх), время жизни (rand()) и дополнительное случайное число (ещё раз rand()). Частицы рисуются в отдельный рендер таргет с аддитивным блендингом. Чтобы не было резких пересечений с геометрией уровня, цвет частицы плавно интерполируется к чёрному, когда разница между глубиной фрагмента и глубиной геометрии уровня приближается к нулю. В общем, один из обычных подходов для реализации Soft Particles.
В финальном проходе, где совмещается Bloom, Volumetric Light и содержимое кадра, также добавляется цвет из текстуры с частицами. Для того чтобы добавить преломление от тепла (Heat Haze), находится яркость текущего текселя текстуры огня, а также двух текселей справа и снизу от него. Используя разницу в яркостях, находиться сдвиг текстуры кадра. Ниже представлено абсолютное значение сдвига, увеличенное в несколько раз для видимости.
#bloom, #fog, #HDR, #render, #тени, #soft particles, #volumetric
13 июля 2008 (Обновление: 18 июля 2008)