Система материалов, часть 5
Теперь когда мы достаточно осветили теоретическую часть вопроса, можно поговорить и конкретной реализации того, как устроена система материалов в XashNT.
Описания материалов хранятся в файлах с произвольным расширением и в произвольной папке (это настраивается пользователем). Для чего это нужно станет понятно далее. Каждый файл описания материалов может содержать в себе только произвольное количество материалов, а имена самих скриптовых файлов ни на что не влияют. Но при этом вы конечно можете придерживаться практики "один материал - один файл".
Технически каждый материал представляет собой именованный блок фигурных скобок, такого плана:
"my_material_name" { }
Имя материала может быть опционально заключено в кавычки, хотя явным образом этого и не требуется. Скорее вопрос безопасности, если в имени материала попадётся какая-нибудь фигурная скобка или пробел (известная проблема ещё со времён GoldSrc).
Имя материала выступает связующим звеном между геометрией и настройками рендеринга. Для каждого типа моделей существует так же секция материала "по умолчанию". Для сцены эта секция называется "mod_static" для моделей со скелетной анимацией - "mod_studio", для спрайтов "mod_sprite" и.т.д.
Все имена вы сможете узнать из документации по материал-системе, однако полный объем документации (только по материал-системе) составляет более 100 килобайт чистого текста, поэтому я, разумеется, не буду приводить его здесь. Это просто для примера, чтобы вы могли лучше понять концепцию. Дефолтные секции существуют в единственном экземпляре на всю игру, а пользовательские - без ограничений. Таким образом, при загрузке материал формируется путём объединения двух секций - пользовательской и дефолтной.
В принципе система материалов допускает отсутствие пользовательской части - тогда материал формируется из дефолтной. Но допускается и отсутствие дефолтной части - тогда вам придётся каждый раз в каждый материал заново копировать то, что могло бы находится в дефолтной части.
Так же есть техники (techniques), которые, если проводить аналогию с языком программирования больше всего напоминают функции. Их можно переиспользовать для разных материалов.
Внутри секций можно вызывать различные встроенные команды, которые могут быть использованы в качестве подсказок как на этапе компиляции, так и на этапе рендеринга. Так например команда
addCompileFlags( C_NOLIGHTMAP );
сообщает компилятору, что мы не хотим *рассчитывать* лайтмапу для этой поверхности и следовательно - тратить на это время. Чтобы просто игнорировать уже рассчитанную лайтмапу у нас будет масса других способов.
Кроме встроенных функций вызовов, мы так же можем объявлять переменные различных типов, обычно это float, int, vec2, vec3, vec4, mat3, mat4 и image. Последний используется для объявления текстурных юнитов. Имена объявленных переменных будут автоматически доступны в шейдере, который будет связан с текущим материалом.
Примеры объявления переменных:
vec2 u_LightmapOffset = "entity->lightmapOffset"; mat4 u_ModelMatrix = "entity->transform"; mat4 u_ModelViewMatrix = "render->modelViewMatrix"; mat4 u_ModelViewProjectionMatrix = "render->modelViewProjectionMatrix"; ivec4 u_DynamicLights = "entity->dynamicLights"; vec4 u_RenderColor = "entity->renderColor"; vec3 u_ViewForward = "render->viewForward"; vec3 u_ViewOrigin = "render->viewOrigin"; vec2 u_DiffuseSize = u_ColorMap->size; vec2 u_DetailScale = vec2( 1.0f, 1.0f ); float u_RealTime = "globals->time";
Примеры объявления текстурных юнитов:
image u_ColorMap = "globals->$WhiteTexture"; // fallback texture image u_StyleMap = "entity->$StylemapTexture"; image u_FogTable = "globals->$FogTexture"; image u_LightMapA = "entity->$AddLightA"; image u_LightMapB = "entity->$AddLightB"; image u_LightMapC = "entity->$AddLightC"; image u_LightMapD = "entity->$AddLightD"; image u_DebugLightMap = "entity->$DebugLightmap"; image u_WorldLights = "globals->$WorldLights"; image u_GlobalMap = "globals->$BlackTexture"; image u_LayerMap = "globals->$BlackTexture"; image u_FlowMap = "entity->$FlowMap";