Система материалов, часть 7
У всех, кто осилил хотя бы просто дочитать предыдущую часть, даже не особо вникая в написанное, неизбежно возникнет закономерный вопрос - как же так, нам обещали простейшую систему, с которой сможет разобраться любой художник, а вместо этого по сути представили чуть ли не полноценный язык программирования с какой-то своей логикой и особенностями. Тут не то что художник, не каждый программист сходу разберётся.
Это действительно так. Столь большая и обширная система описания материалов нужна, как вы понимаете, для полного контроля над процессом рендеринга и сборки. Упростить её возможным не представляется, однако есть достаточно способов упростить методы взаимодействия между художником и системой.
Одна из них - это мощный препроцессор, инструменты автозамены. Которая тоже является частью языка системы материалов и о которой я ещё ничего не говорил. Язык препроцессора содержит в себе условия #if, #ifdef, а так же ключевые слова объявления макросов, такие как #define, #keydef и #block. Механизм автозамены отличается от принятого в С\С++ и адаптирован под нужды системы материалов.
Так вы можете использовать рекурсивную автозамену, формировать имена и строки, используя содержание переменных, а так же использовать перегрузку макросов. Приведу пример макроса, для превращения выбранного полигона в невидимый:
#keydef nodraw\ addCompileFlags( C_NODRAW );
Это один из самых простых случаев. Вместо того чтобы каждый раз писать в материале addCompileFlags( С_NODRAW ); вы просто пишете nodraw, что гораздо проще запомнить. Разумеется имя макроса может быть любым по вашему усмотрению.
Более сложный пример - указание явного пути к текстуре
#keydef texture <path>\ image c_ImplicitImage = "textures/<path>";\ image u_ColorMap = "textures/<path>";\ ignorePaths = 1;
Тут требуются некоторые пояснения. Между пользовательской и дефолтной секцией существуют временные глобальные переменные типа enum, которые хранят свои значения во время чтения обоих секций. Таким образом осуществляется передача данных между секциями, если мы хотим запретить какое-то поведение по умолчанию.
Если зайти в секцию mod_static, то мы можем увидеть там следующий код:
#if ignorePaths == 0 addImageLocation( u_ColorMap, "textures/<matname>" ); addImageLocation( u_ColorMap, "models/<matname>" ); addImageLocation( u_ColorMap, "<matname>" ); #endif
Это подсказки системе материалов, где следует искать текстуры для текущего. Переменная <matname> содержит имя текущего материала (но есть и другие переменные), таким образом подсказка для "my_material_name" в задании хинтов для поиска раскроется в "textures/my_material_name", "models/my_material_name" и просто в "my_material_name". Чем ниже вызов - тем больше у него приоритет.
Однако в случае явного указания текстуры, мы попросту не хотим чтобы текстура искалась и грузилась по дефолтным путям поиска. Для чего и ставим переменную ignorePaths в состояние 1. Так же следует сказать немного слов о текстурном юните
image c_ImplicitImage = "textures/<path>";
Это встроенный текстурный юнит, который используется компилятором уровней по собственному усмотрению. Ну например для того чтобы запечь тени от решёток, в случае если проставлен соответствующий режим работы, а текстура имеет альфа-канал.
Аналогично под макрос прячутся и директивы для шейдера, чтобы сформировать убер-шейдер. Но конечно никто не запрещает вместо убершейдеров использовать их раздельные копии, если кому-то не нравится когда код шейдера пестрит бесчисленными #ifdef.
Пример реализации эффекта конвейера:
#keydef conveyor\ float u_ConveyorPos = "entity->conveyorMovement";\ addShaderDefine( SURF_CONVEYOR );
вызов addShaderDefine - встроенный вызов системы материалов, который на этапе компиляции шейдера развернёт переданный параметр в #define SURF_CONVEYOR и под условие вы сможете спрятать непосредственно код скролинга текстуры. Ну а объявленная скалярная переменная передаёт позицию скролла текстуры из энтити в юниформ.
Ещё пример - включение альфа-теста на объектах:
#keydef alphtest\ addCompileFlags( C_DETAIL|C_TRANSLUCENT|C_ALPHASHADOW );\ // GoldSrc rules: alpha-test on world polygons is not allowed // if(( entity->objectID != 0 ) == 1 ) beginStateCondition( "entity->objectID != 0", 1 );\ alphaFunc( GL_GREATER, 0.25f );\ endStateCondition();\ noRenderMode = 1;
здесь как мы видим используется условие - включать альфатест только на объектах не являющихся частью статичной сцены, у которой objectID равен нулю. И одновременно переменная noRenderMode = 1; запрещает использование глобальной системы рендермодов, код реализации которой я приводил в конце прошлой части.
Я бы мог бы привести и гораздо более сложные примеры, но для понимания общих принципов, думаю, вполне достаточно. Таким образом для художника описание нового материала (при условии, что он вообще требует каких-то особенных режимов отображения сводится к крайне простому блоку:
// включенный альфатест: "halflife/{rail1b" { alphtest }
// эмиссивная текстура: "halflife/~lab_crt9a" { light 225 150 150 100 }
// анимированная текстура: "halflife/+0~generic78d" { addBaseAnim( "halflife/+0~generic78d" ); addBaseAnim( "halflife/+1~generic78d" ); finishBaseAnim(); addAltAnim( "halflife/+a~generic78d" ); finishAltAnim(); }
Обращаю ваше внимание, что функции addBaseAnim и addAltAnim уже не являются встроенными функциями системы материалов, а точно такими же макросами.
#keydef addBaseAnim( <path> );\ #if frameNum == 0\ image u_ColorMap = "globals->$WhiteTexture";\ #endif\ addUnitFrame( u_ColorMap, "textures/<path>", <frameNum>, 0 );\ ignorePaths = 1;\ frameNum++;
Исходя из вышесказанного, сценарий взаимодействия с системой материалов предполагается следующий:
- создаются все необходимые шейдеры для рендеринга
- создаются все типовые настройки режимов отображения и прячутся под макро-блок
- создаётся дефолтный блок описания материала с необходимыми условиями
- при необходимости система масштабируется без опаски сломать уже существующие описания
В итоге художник остаётся в заданных рамках и оперирует либо исключительно текстурами, либо прописывает минимальные блоки, по типу представленных выше.
Это - на данный момент, пока у XashNT ещё нет собственного редактора. Когда он появится, то пользовательские макросы будут доступны в меню, например в качестве выпадающего списка, чтобы применить ту или иную настройку можно было в редакторе настройки материалов.
И разумеется сразу же увидеть результат. Но можно будет проэмулировать и аналог системы нодов, если кто-то считает подобную систему более удобной.
Про эмуляцию следует сказать отдельно. Дело в том, что макроязык у данной системы материалов настолько мощный и гибкий, что он позволяет эмулировать уже существующие системы описания материалов. Единственное условие - чтобы исходные материалы тоже выглядели как именованный блок фигурных скобок, хотя есть средства обойти даже это ограничение. Таким образом можно например полностью проэмулировать встроенный язык шейдеров Quake III Arena или описания материалов Half-Life 2, и действовать в привычном вам пространстве ограничений того или иного рендерера.
Примеры реализации подобных эмуляций так же будут доступны в готовящемся к выходу бета-билде.
Ну вот теперь пожалуй действительно всё. Спасибо всем кто дочитал, жду ваших вопросов и мыслей в комментариях на канале https://t.me/xashnt