Разница между старым и обновленным приложением даже превзошла наши ожидания. Простенькие частицы дыма существенно улучшили сцену, сделали ее богаче и полнее. Клубы дыма это и дополнительные детали за которые “цепляется глаз”, а также способ сделать переход между основными объектами и фоном более плавным:

Мягкие частицы
Так что такое эти мягкие частицы? Вы можете помнить что во многих старых играх (времен Quake 3 и CS 1.6) эффекты дыма и взрывов имели очень четкие плоские границы на пересечении частиц с другой геометрией. Все современные игры уже не имеют подобных артефактов благодаря использованию мягких частиц — то есть частиц с размытыми, “мягкими” краями вокруг прилегающих объектов.
Рендеринг
Что требуется для того чтобы сделать частицы мягкими? Во-первых, нужна информация о глубине сцены для того чтобы определить места пересечения частиц с другими объектами и смягчить их. Затем нам нужно определить места пересечения геометрий сцены и частицы путем сравнения глубины сцены и частицы во фрагментном шейдере — пересечения там, где глубины одинаковые. Далее мы рассмотрим процесс рендеринга пошагово. Обе реализации сцены под Android OpenGL ES и WebGL одинаковы, основное отличие только в загрузке ресурсов. Реализация на WebGL имеет открытый исходный код и вы можете взять его тут — https://github.com/keaukraine/webgl-buddha.
Рендеринг карты глубины
Для рендеринга карты глубины сцены, сперва нам потребуется создать текстуры для карты глубины и цвета и назначить их определенному FBO. Это делается в методе initOffscreen() в файле BuddhaRenderer.js.
Сам рендеринг Actual объектов сцены в карту глубины выполняется в методе drawDepthObjects(), который отрисовывает статую Будды и плоскость пола. Однако, тут есть одна хитрость для улучшения производительности. Поскольку на данном этапе рендеринга нам не нужна информация о цвете, а лишь глубина, рендеринг в цветовой буфер отключается вызовом gl.colorMask(false, false, false, false), а затем включается снова вызовом gl.colorMask(true, true, true, true). Функция glcolorMask() может выключать и выключать запись красного, зеленого синего и альфа компонента по отдельности, так что для того чтобы полностью отключить запись в буфер цвета мы ставим всем компонентам флажок false, а затем включаем их для рендеринга на экран, выставляя их все в true. Результат рендеринга в текстуру глубины можно увидеть, раскомментировав вызов drawTestDepth() в методе drawScene(). Поскольку текстура карты глубины имеет только один канал, она воспринимается как только красная, синий и зеленый каналы равно нулю. Визуализация карты глубины нашей сцены выглядит так:

Рендеринг частиц
Код шейдера, который используется для отрисовки частиц находится в файле SoftDiffuseColoredShader.js. Давайте разберемся как он устроен.
Основная идея поиска пересечения геометрий частицы и сцены состоит в сравнении значения текущей глубины фрагмента с сохраненным значением из карты глубины.
Первый шаг в сравнении глубин это линеаризация значений глубин, поскольку оригинальные значения экспоненциальные. Это делается с помощью функции calc_depth(). Эта техника хорошо описана здесь — https://community.khronos.org/t/soft-blending-do-it-yourself-solved/58190. Для линеаризации значений нам понадобится юниформ-переменная vec2 uCameraRange, компоненты x и y которой содержат значения ближней и дальней плоскостей отсечения камеры. Затем шейдер вычисляет линейную разницу между глубиной частицы и сцены — это значение сохраняется в переменной a. Однако, если мы будем применять это значение к цвету фрагмента, мы получим слишком прозрачные частицы — цвет будет затухать линейно от любой геометрии позади частицы, и затухать довольно быстро. Вот как выглядит визуализация линейной разницы глубины (Вы можете раскомментировать соответствующую строчку кода в шейдере и увидеть ее):

Чтобы частицы были более прозрачными только возле границы пересечения (в районе a=0) мы применим функцию GLSL smoothstep() к значению переменной a со значением перехода от 0 до коэффициента, заданного в юниформе uTransitionSize, который определяет ширину прозрачного перехода. Если Вы желаете узнать больше о работе функции smoothstep() и увидеть пару интересных примеров ее использования, рекомендуем прочитать эту статью — http://www.fundza.com/rman_shaders/smoothstep/. Финальный коэффициент сохраняется в переменной b. Для режима миешивания цветов, используемого в нашей сцене, достаточно просто перемножить цвет частицы взятый из текстуры на этот коэффициент; в других реализациях частиц Вам может понадобиться изменение например только альфа канала. Если в шейдере раскомментировать строчку кода для визуализации этого коэффициента то результат будет выглядеть так:

Сравнение различных значений коэффициента “мягкости” частиц:

Оптимизация отрисовки спрайтов
В этой сцене мелкие пылинки отрисовываются как точечные спрайты (примитивы типа GL_POINTS). Этот режим удобен тем что он автоматически создает готовую квадратную геометрию частицы с текстурными координатами. Однако, они имеют и недостатки, которые делают их использование неуместным для крупных частиц клубов тумана. Прежде всего, они отсекаются плоскостями отсечения матрицы камеры по координатам центра спрайта. Это приводит к тому что они резко исчезают из вида на краях экрана. Также, квадратная форма спрайта не очень оптимальна для фрагментного шейдера так как он вызывается и в тех местах где текстура частиц пустая, что вызывает заметную излишнюю перерисовку. Мы используем оптимизированную форму частицы — с обрезанными краями в тех местах где текстура полностью прозрачна:

Такие модели для частиц обычно называются billboard. Конечно, они не могут быть отрисованы как примитивы GL_POINTS, поэтому каждая частица рисуется отдельно. Это создает не очень много вызовов drawElements, во всей сцене всего 18 частиц тумана. Они должны быть размещены в произвольных координатах, масштабированы но повернуты таким образом, чтобы всегда быть перпендикулярными к камере независимо от ее положения. Это достигается модификацией матрицы, описанной в этом ответе на StackOverflow. В файле BuddhaRenderer.js есть метод calculateMVPMatrixForSprite() который создает MVP-матрицы для billboard моделей. Он выполняет все обычные трансформации перемещения и масштабирования а затем использует resetMatrixRotations() для сброса компонента вращения матрицы model-view перед тем как она перемножается на матрицу проекции. Результирующая матрица выполняет трансформацию в результате которой модель всегда направлена ровно на камеру.
Результат
Финальный результат можно посмотреть вживую здесь.
Можете изучать и переиспользовать для своих проектов исходный код с Github.
ссылка на оригинал статьи https://habr.com/ru/post/489644/
Добавить комментарий