reckfellENGINE

Препроцессор шейдеров: варианты, макросы и include

Рабочие варианты, пути include, обновление зависимостей и диагностика.

На этой странице

Препроцессор выбирает и разворачивает текст до компиляции шейдера. Он нужен для вариантов шейдера и общего кода. Для значений, которые должны меняться при работе того же скомпилированного шейдера, используйте uniform.

#define не является параметром материала. SetShaderParameter не меняет его: изменение макроса требует изменения/перезагрузки исходника и компиляции полученного варианта. Вариант с макросом не гарантирует ускорения относительно uniform-ветки; измеряйте конкретный шейдер на целевом устройстве.

Рабочий вариант с подключаемым файлом

Создайте Palette.reckfellshaderinc в корне проекта:

Шейдер
#ifndef RF_DOC_MODE
#define RF_DOC_MODE 0
#endif
#define RF_DOC_RGB(r, g, b) vec3((r), (g), (b))
#define RF_DOC_FACTOR 2
#undef RF_DOC_FACTOR
#define RF_DOC_FACTOR 1

vec3 rf_doc_color() {
#if RF_DOC_MODE == 0
    return RF_DOC_RGB(1.0, 0.0, 0.0) * float(RF_DOC_FACTOR);
#elif RF_DOC_MODE == 1
    return RF_DOC_RGB(0.0, 1.0, 0.0) * float(RF_DOC_FACTOR);
#else
    return RF_DOC_RGB(0.0, 0.0, 1.0) * float(RF_DOC_FACTOR);
#endif
}

RF_DOC_MODE получает значение 0, если включающий исходник его не определил. Макрос с аргументами заключает их в скобки. RF_DOC_FACTOR показывает обязательный #undef перед переопределением: это подстановка исходного текста, а не uniform времени выполнения.

Сохраните помощник как ShaderVariants.cs:

C#
using System;
using Reckfell;

public static class ShaderVariants
{
    public static ShaderMaterial Create(int mode)
    {
        if (mode < 0 || mode > 2)
            throw new ArgumentOutOfRangeException(nameof(mode));
        string code = "shader_type canvas_item;\nrender_mode unshaded;\n" +
            "#define RF_DOC_MODE " + mode + "\n" +
            "#include \"asset://Palette.reckfellshaderinc\"\n" +
            "#include \"asset://Palette.reckfellshaderinc\"\n" +
            "#if 0\nThis branch is excluded before shader compilation.\n#endif\n" +
            "void fragment() { COLOR = vec4(rf_doc_color(), 1.0); }\n";
        return new ShaderMaterial { Shader = new Shader { Code = code } };
    }
}

Назначьте ShaderVariants.Create(0) свойству Material видимого ColorRect или Sprite2D. Режим 0 даёт красный, 1 — зелёный, 2 — синий. Помощник намеренно подключает один ресурс дважды: в пределах одной обработки уже включённый путь пропускается, поэтому функция не определяется повторно. Текст неактивной ветки #if 0 удаляется до разбора языка шейдера.

Фабрика создаёт отдельный Shader при каждом вызове. Если нескольким материалам нужен один вариант, намеренно сохраняйте и разделяйте этот Shader; значения uniform у материалов могут различаться. Для анимации значения не пересобирайте Code каждый кадр.

Директивы и ограничения

ДирективаЧто делает
#define NAME textЗаменяет токены идентификатора после определения, а не произвольные подстроки других имён.
#define FN(a, b) expressionПодставляет обязательные аргументы. Заключайте аргументы и выражение в скобки; это не типизированный вызов функции.
#undef NAMEУдаляет пользовательский макрос перед переопределением. Отсутствующий пользовательский макрос допустим; встроенные макросы удалять нельзя.
#if / #elif / #else / #endifВыбирает ветки исходника по выражениям препроцессора. Закрывайте каждое условие.
#ifdef / #ifndefПроверяет наличие макроса, а не ненулевое значение его подстановки.
defined(NAME)Проверяет один идентификатор макроса внутри условия.
#include "path"Загружает ShaderInclude и обрабатывает его в текущем контексте макросов.
#error messageОстанавливает обработку с сообщением, например при недопустимом сочетании настроек.
#pragma disable_preprocessorОтключает дальнейшую обработку. Оставшиеся неподдерживаемые директивы от этого не становятся допустимым кодом шейдера.

Размещайте директивы на отдельных строках. Точка с запятой в конце подстановки макроса становится её частью и может сломать использование; автоматически добавлять её не нужно. Обратная косая черта в конце строки продолжает директиву. Доступно склеивание токенов через ##, но без необходимости текстовой подстановки предпочтительнее обычные функции шейдера.

Наличие и значение — разные проверки: при #define FLAG 0 условие #ifdef FLAG истинно, а #if FLAG ложно. Для пустого флага используйте #ifdef или defined(FLAG), не арифметическую проверку значения. Uniform нельзя использовать в #if: это входные данные шейдера времени выполнения.

CURRENT_RENDERER можно сравнивать с RENDERER_FORWARD_PLUS, RENDERER_MOBILE или RENDERER_COMPATIBILITY. Эти константы определяют выбранный рендерер, а не все возможности видеокарты. Выполненные примеры используют Forward+/Vulkan.

Явно задавайте основу путей include

У подключаемого файла используйте точное расширение .reckfellshaderinc. В сохранённом .reckfellshader относительный путь вычисляется от расположения файла шейдера. Для исходника, создаваемого из C#, абсолютный asset:// работает без базового файла. Чтобы использовать относительный путь у Shader без ResourcePath, вызовите SetIncludePath с полным именем базового файла шейдера до присваивания Code, а не только с папкой. Загрузка ресурса шейдера задаёт такую основу сама.

Подключаемый файл может подключать другие файлы, но циклов быть не должно. Глубина вложенных include ограничена 25. Повторное использование файла не создаёт свежую параметризованную копию для каждого #include: повторные пути пропускаются. Для нескольких реализаций создайте явные отдельные объявления или переработайте общую функцию.

Изменение ShaderInclude.Code уведомляет зависимые шейдеры: проверка в движке подтверждает изменение пикселей и их восстановление без переназначения материала. Shader.Code/GetCode возвращают исходный текст с директивами, а не развёрнутый код графического API. Изменение include в памяти не сохраняет файл автоматически.

Если обработка завершается ошибкой

СимптомЧто проверить
Повторное определение макросаПредыдущий #define; удалите его через #undef или задайте осмысленный guard значения по умолчанию.
Несопоставленная веткаПропущенный #endif, неправильный #else/#elif или условие, пересекающее границу include.
Файл include не найденРасширение, фактический путь и базовое имя файла для относительного пути.
Ошибка цикла/глубиныГраф include; повторное подключение ресурса не разрешает рекурсивные зависимости.
Макрос изменён, но материал выглядит прежнимВозможно, отредактирован другой общий Shader/ресурс либо требовался uniform времени выполнения.
#pragma не исправила компиляциюОтключение препроцессора не переводит чужой синтаксис шейдера.

По настоящим пикселям GPU проверены опубликованные варианты, макрос с аргументами, undef/переопределение, исключённый текст, повторные/относительные include и изменение зависимости. Диагностика неверных директив, автодополнение редактора, все рендереры и производительность рассмотрены по исходникам либо требуют отдельной проверки; их выполнение здесь не заявляется. Для чужого исходника см. перенос GLSL.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.