Urho3D: Материалы
Графическая подсистема является, вероятно, самой сложной и запутанной частью движка. И вместе с тем это именно та часть, в которой нужно очень хорошо ориентироваться. Вы можете легко обрабатывать ввод, воспроизводить звуки и даже не задумываться о том, как оно там внутри устроено. Но редкая игра обойдется без собственных красивых эффектов и тут без определенного набора знаний не обойтись. В одной статье невозможно охватить весь объем информации по данной теме, но я надеюсь, что смогу предоставить вам базу, опираясь на которую вы гораздо легче освоите все нюансы и тонкости рендера Urho3D.
Для нетерпеливых
Результат экспериментов находится здесь: github.com/1vanK/Urho3DHabrahabr05. В папке Demo собственно демка (двигая мышкой можно вращать модель, колесико поворачивает источника света). Все добавленные ресурсы отделены от файлов движка и находятся в папке Demo/MyData.
Рассматриваемая версия движка
В настоящий момент ведется интеграция в движок PBR (Physically Based Rendering, физически корректный рендеринг). Он не добавит в сам движок никаких существенных изменений, в основном это просто дополнительный набор шейдеров. К тому же он пока еще не устаканился и в финале какие-то вещи могут поменяться. Поэтому, чтобы сузить диапазон рассматриваемой информации, я буду опираться на более старую версию движка (в момент написания статьи она устарела аж на десять дней :).
:: Указываем путь к git.exe
set "PATH=c:\Program Files (x86)\Git\bin\"
:: Скачиваем репозиторий
git clone https://github.com/Urho3D/Urho3D.git
:: Переходим в папку со скачанными исходниками
cd Urho3D
:: Возвращаем состояние репозитория к определённой версии (9 апреля 2016)
git reset --hard 4c8bd3efddf442cd31b49ce2c9a2e249a1f1d082
:: Ждём нажатия ENTER для закрытия консоли
pause
Импортирование модели
Для экспериментов я решил взять модель Абаддона из игры Dota 2. Здесь есть как текстурные карты, которые Urho3D поддерживает «из коробки», так и специфичные для движка Source 2. Их мы тоже не оставим без внимания. Какая карта за что отвечает можно посмотреть в этом документе. Но прежде чем разбираться с материалами, нам нужно преобразовать модель в понятый для Urho3D формат. Сделать это можно разными способами.
В комплекте с движком поставляется утилита AssetImporter, которая позволяет импортировать модели из множества форматов. Ее можно найти в папке bin/tool. Команда «AssetImporter.exe model abaddon_econ.fbx abaddon.mdl -t» объединяет все фрагменты модели в единое целое (в fbx-файле модель разбита на части). Обязательно включайте опцию »-t», если собираетесь использовать карты нормалей (этот параметр добавляет к вершинам модели информацию о касательных). Команда «AssetImporter.exe node abaddon_econ.fbx abaddon.xml -t» сохранит модель в виде префаба, в котором части модели организованы в виде иерархии нод. Так как в данном fbx-файле для модели не назначены текстуры, то материалы придется создавать вручную.
Можно импортировать модель прямо из редактора (File > Import model…). В этом случае используется все та же утилита AssetImporter. Параметр »-t» включен по умолчанию. Остальные параметры можно указать в окне View > Editor settings.
Для любителей Блендера есть отдельный экспортер. И он великолепен. Скачайте аддон, установите его и откройте уже подготовленный файл Abaddon.blend (я оставил от модели только коня и создал ему материал). Укажите путь, куда вы хотите сохранить результат, проверьте настройки (не забудьте про тангенты) и нажмите кнопку Export.
Первое, что бросается в глаза при открытии модели в редакторе Urho3D — белые ноги у коня. Дело в том, что Блендер интерпретирует карту свечения как интенсивность (черно-белое изображение), а движок Urho3D как цвет. Вы можете исправить саму карту свечения (например в ГИМПе умножить ее на диффузную карту), но мы не ищем легких путей и будем менять шейдер Urho3D, чтобы приспособить его к набору текстур движка Source 2. Но это будет позже, а пока, чтобы посмотреть модель в более приятном виде, вы можете в параметрах материала (файл abaddon_mount.xml) изменить значение множителя свечения MatEmissiveColor на »0 0 0», либо вообще удалить эту строку (только не забудьте потом все вернуть назад, этот параметр нам еще понадобится). Кстати, когда вы модифицируете и сохраняете файл материала, редактор автоматически подхватывает новую версию и обновляет свой вьюпорт. Более того, это работает даже для шейдеров. Вы можете писать шейдер и наблюдать за результатом своих трудов в реальном времени.
Процесс рендеринга
Процесс рендеринга в Urho3D описываться с помощью набора текстовых файлов: рендерпасов (CoreData/RenderPaths/*.xml), техник (CoreData/Techniques/*.xml) и шейдеров (CoreData/Shaders/GLSL/*.glsl или CoreData/Shaders/HLSL*.hlsl). Как только вы поймете взаимосвязь между ними, в остальном разобраться не составит труда.
Итак, каждый объект сцены в большинстве случаев рендерится несколько раз с применением разных шейдеров (так называемые проходы рендера). Какой именно набор проходов (шейдеров) требуется для отрисовки каждого объекта, описывается в технике. В каком порядке эти проходы (шейдеры) будут выполнены, описано в рендерпасе. Еще раз обращаю ваше внимание: порядок строк в технике не важен.
Также есть материалы (Data/Materials/*.xml). Материал определяет какая именно техника будет использоваться для объекта, а также наполняет эту технику конкретным содержанием, то есть определяет те данные, которые будут переданы в шейдеры: текстуры, цвета и т. п.
То есть совокупность рендерпас+техника+шейдеры можно представить как алгоритм, а материал — как набор данных для этого алгоритма.
Для понимания порядка вызова шейдеров взгляните на данную сравнительную табличку:
// НЕВЕРНО // ВЕРНО
for (i = 0; i < числоОбъектов; i++) for (int i = 0; i < числоПроходов; i++)
{ {
for (j = 0; j < числоПроходов; j++) for (j = 0; j < числоОбъектов; j++)
Рендрить(объект[i], проход[j]); {
} if (объект[j] имеет проход[i])
Рендерить(объект[j], проход[i]);
}
}
Если в технике есть какой-то проход, но в рендерпасе такой проход отсутствует, то он не будет выполняться. И наоборот, если в рендерпасе есть какой-то проход, а в технике он отсутствует, то он тоже выполняться не будет. Однако существуют прописанные в самом движке проходы, которые вы не найдете в рендерпасе, но они все равно будут выполняться, если они есть в технике.
Шейдеры
Urho3D поддерживает как OpenGL, так и DirectX, поэтому он содержит два аналогичных по функционалу набора шейдеров, которые расположены в папках CoreData/Shaders/GLSL и CoreData/Shaders/HLSL. Какой именно API и, соответственно, какой набор шейдеров будет использоваться, определяется при компиляции движка. Я предпочитаю OpenGL как более универсальный, поэтому в данной статье буду ориентироваться на него.
В папке CoreData/Shaders/GLSL множество файлов, но вам стоит обратить особое внимание на один из них — LitSolid.glsl. Именно этот шейдер используется для подавляющего большинства материалов. Urho3D использует метод убершейдеров, то есть из огромного универсального шейдера LitSolid.glsl посредством дефайнов выбрасываются ненужные куски и получается маленький быстрый специализированный шейдер. Наборы дефайнов указываются в техниках, а некоторые дефайны добавляются самим движком.
Возвращаемся к нашему коню
На данный момент Urho3D поддерживает 3 метода рендерина: Forward (традиционный), Light Pre-Pass и Deferred. Разичия между ними являются темой для отдельного разговора, поэтому просто ограничимся методом Forward, который используется по умолчанию и описан в рендерпасе CoreData/RenderPaths/Forward.xml.
Так как мы будем модфицировать технику, то скопируйте ее в отдельный файл Techniques/MyTechnique.xml, чтобы не затронуть другие материалы, которые могут тоже использовать эту технику. В материале также измените название используемой техники.
Техника использует стандартный для материалов шейдер LitSolid.glsl. Так как мы будем изменять этот шейдер, то скопируйте его в Shaders/GLSL/MyLitSolid.glsl. Так же измените имя используемого шейдера в технике. Заодно эту технику можно упростить, выкинув лишнее. Так как проходы «prepass», «material», «deferred» и «depth» отсутствуют в рендерпасе Forward (они определены в других рендерпасах), то они никогда не будут использоваться. Однако оставьте проход «shadow». Хотя он и отсутствует в рендерпасе, но он является встроенным проходом и прописан в самом движке. Без него конь не будет отбрасывать тени.
Давайте теперь поработаем с нашим шейдером Shaders/GLSL/MyLitSolid.glsl и изменим способ использования карты свечения на нужный нам. Дефайн EMISSIVEMAP (как раз и сигнализирующий, что материал должен определять карту свечения) в шейдере присутствует в нескольких местах, а так как разобрать шейдер построчно в рамках статьи не получится, то мы пойдем другим путем. Дефайны DEFERRED, PREPASS и MATERIAL никогда не будут определены в шейдере, так как проходы, в которых они определяются, мы удалили из техники как неиспользуемые. Поэтому смело удаляем обрамленный ими код. Размер шейдера уменьшился на треть. Дефайн EMISSIVEMAP остался только в одном месте.
Давайте умножим карту свечения на диффузный цвет. Замените строку
finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb;
на строку
finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb * diffColor.rgb;
Теперь ноги лошади светятся правильным голубым цветом.
Для большей красоты давайте добавим блум-эффект (ореол вокруг ярких частей изображения). Он реализуется двумя строчками в скрипте:
// Включаем HDR-рендеринг.
renderer.hdrRendering = true;
// Добавляем эффект постобработки.
viewport.renderPath.Append(cache.GetResource("XMLFile", "PostProcess/BloomHDR.xml"));
Обратите внимание, что эффект постобработки добавляется в конец рендерпаса. Вы можете дописать его прямо в файл.
Rim Light
Подсветка краев модели — фейковая техника, симулирующая свет, падающий на модель сзади. В движке Source 2 используется отдельная маска для обозначения частей модели, на которые нужно накладывать данный эффект.
Для того, чтобы передать текстуру в шейдер, выберем неиспользуемый текстурный юнит. В нашем материале не используется карта окружения, поэтому будем использовать ее слот. Также в шейдере нам также понадобятся два параметра RimColor и RimPower, поэтому сразу добавим и их. Итоговый материал выглядит так. Это может иногда сбивать с толку, но имя текстурного юнита не обязательно должно соответствовать его использованию. Вы вольны передавать через текстурные юниты что угодно и применять это в своих шейдерах как угодно.
Имя юниформа в шейдере будет отличаться от имени параметра в материале на префикс «c» (const): параметр RimColor станет юниформом cRimColor, а параметр RimPower станет юниформом cRimPower.
uniform float cRimPower;
uniform vec3 cRimColor;
А так выглядит сама реализация эффекта:
// RIM LIGHT
// Направление = позиция камеры - позиция фрагмента.
vec3 viewDir = normalize(cCameraPosPS - vWorldPos.xyz);
// Скалярное произведение параллельных единичных векторов = 1.
// Скалярное произведение перпендикулярных векторов = 0.
// То есть, если представить шар, то его середина будет белой
// (так как нормаль параллельна линии взгляда), а к краям темнеть.
// Нам же наоборот нужны светлые края, поэтому скалярное произведение вычитается из единицы.
float rimFactor = 1.0 - clamp(dot(normal, viewDir), 0.0, 1.0);
// Если cRimPower > 1, то подсветка сжимается к краям.
// Если cRimPower < 1, то подсветка наоборот становится более равномерной.
// При cRimPower = 0 вся модель будет равномерно окрашена, так как любое число в степени ноль = 1.
rimFactor = pow(rimFactor, cRimPower);
// Проверяем, нужно ли использовать карту подсветки.
#ifdef RIMMAP
// Учитываем карту и цвет подсветки.
finalColor += texture2D(sEnvMap, vTexCoord.xy).rgb * cRimColor * rimFactor;
#else
finalColor += cRimColor * rimFactor;
#endif
Обратите внимание на дефайн RIMMAP. Возможно вам захочется не использовать карту подсветки, а просто наложить эффект на всю модель. В этом случае вы просто не определяете дефайн RIMMAP в технике. Кстати, не забудьте определить дефайн RIMMAP в технике:) Итоговая техника выглядит так, а шейдер — так.
Литература
urho3d.github.io/documentation/1.5/_rendering.html
urho3d.github.io/documentation/1.5/_a_p_i_differences.html
urho3d.github.io/documentation/1.5/_shaders.html
urho3d.github.io/documentation/1.5/_render_paths.html
urho3d.github.io/documentation/1.5/_materials.html
urho3d.github.io/documentation/1.5/_rendering_modes.html