Формат эффектов и runtime шейдеров
FOnline использует файлы .fofx для авторских GPU-эффектов. Один исходный файл
объединяет состояние рендеринга, один или несколько проходов вершинного и
фрагментного шейдера, принадлежащие Engine шейдерные ресурсы и данные для
запекания backend-специфичных артефактов шейдера.
Используйте это руководство для авторинга и понимания runtime-поведения. Точный контракт текущей ревизии приведён в сгенерированном справочнике формата эффектов, его отдельных страницах о состоянии рендеринга, ресурсах, запекании и runtime, а также в канонической JSON-модели.
Область и источник истины
Поведение определяют:
Source/Tools/EffectBaker.cpp: разбор.fofx, компиляция шейдеров, reflection, backend-варианты и диагностика;Source/Frontend/Rendering.hиSource/Frontend/Rendering.cpp: форматы вершин, встроенные buffer-ы, состояние проходов/рендеринга и загрузка отражённых ресурсов;Source/Client/EffectManager.cpp: кеширование по пути, эффекты по умолчанию,ScriptValueBufи обновления buffer-ов каждого кадра;Source/Client/Client.cppиSource/Scripting/ClientGlobalScriptMethods.cpp: выбираемые скриптами эффекты и API script value;Source/Frontend/Rendering-*.cpp: загрузка шейдеров backend-ов, pipeline-ы, привязка дескрипторов и отрисовка;Source/Tests/Test_EffectBaker.cpp: исполняемые примеры запекания и ошибок.
Эта страница является переиспользуемой документацией Engine. Подключаемый
проект владеет каталогом эффектов, порядком переопределения resource pack,
профилем качества шейдеров, художественным направлением, конкретными
назначениями EffectType и всей семантикой, присвоенной ScriptValueBuf.
BuildTools/EffectFormatInterface.json является структурированным контрактом,
закреплённым за исходниками. BuildTools/docs_effect_format.py проверяет его
source anchors, выводит значения compile-time ограничений из CMake-интерфейса
проекта и формирует сгенерированный справочник. Изменение parser-а, baker-а,
ресурсов или runtime должно обновлять эту модель в той же правке.
Минимальный эффект
Минимальный однопроходный эффект не текстурированного quad может выглядеть так:
[Effect]
[VertexShader]
layout(set = 0, binding = 0, std140) uniform ProjBuf
{
mat4 ProjMatrix;
};
layout(location = 0) in vec3 InPosition;
void main(void)
{
gl_Position = ProjMatrix * vec4(InPosition, 1.0);
}
[FragmentShader]
layout(location = 0) out vec4 FragColor;
void main(void)
{
FragColor = vec4(1.0);
}
Файл обязан содержать [Effect] и пригодный исходный код вершинного и
фрагментного шейдера для каждого объявленного прохода. Baker сам добавляет
директиву версии, квалификатор точности и compile-time define-ы Engine.
Структура файла
.fofx использует parser Engine ConfigFile в режиме сбора содержимого. Ключи
конфигурации находятся в [Effect]; тела секций шейдеров собираются как
необработанный текст.
[Effect]
Эта обязательная секция содержит:
Version;Passes;ShadowPassв 3D-сборках;BlendFunc;BlendEquation;DepthWrite;DepthFunc;DepthVariants;CullVariants;- варианты ключей смешивания и глубины с суффиксом
_PassN.
Runtime повторно разбирает эту секцию из запечённой копии исходного файла
.fofx. Поэтому состояние рендеринга хранится не только в бинарных файлах
шейдера или метаданных reflection.
[ShaderCommon]
Эта необязательная секция необработанного текста добавляется перед обеими стадиями каждого прохода. Используйте её для констант и вспомогательных функций, общих для стадий или проходов эффекта.
В .fofx нет директивы include. Храните переиспользуемый код в
[ShaderCommon], осознанно дублируйте его между разными ресурсами эффектов либо
генерируйте проектные эффекты вне формата Engine, если такой процесс принадлежит
проекту.
Стадии шейдера и fallback прохода
Для прохода N baker ищет стадии в следующем порядке:
[VertexShader PassN], затем[VertexShader];[FragmentShader PassN], затем[FragmentShader].
Пустая секция конкретного прохода считается отсутствующей и использует общую секцию. Если исходный текст стадии не найден ни в одной из них, запекание завершается ошибкой.
Нумерация проходов начинается с единицы. Не смешивайте два варианта записи:
- секция шейдера:
[FragmentShader Pass2]; - ключ состояния:
BlendFunc_Pass2.
Состояние рендеринга
Число проходов и версия шейдера
Значение Passes по умолчанию равно 1 и должно находиться в диапазоне
1..FO_EFFECT_MAX_PASSES (значение Engine по умолчанию 6). Оно определяет
компиляцию каждой стадии, артефакты метаданных и backend-объекты проходов.
Значение Version по умолчанию равно 310. Baker формирует:
#version 310 es
precision highp float;
Не добавляйте вторую директиву #version в авторский текст шейдера.
Состояние смешивания
Значение BlendFunc по умолчанию:
BlendFunc = SrcAlpha InvSrcAlpha
Оно обязано содержать ровно два фактора, первым идёт исходный:
Zero, One, SrcColor, InvSrcColor, DstColor, InvDstColor,
SrcAlpha, InvSrcAlpha, DstAlpha, InvDstAlpha, ConstantColor,
InvConstantColor или SrcAlphaSaturate.
Значение BlendEquation по умолчанию равно FuncAdd. Допустимы FuncAdd,
FuncSubtract, FuncReverseSubtract, Max и Min.
Для переопределений конкретного прохода используйте BlendFunc_PassN и
BlendEquation_PassN. Неизвестные значения приводят к ошибке создания эффекта.
Состояние глубины
Значение DepthWrite по умолчанию равно True. Значение DepthFunc по
умолчанию равно Always; допустимы сравнения Always, Never, Less,
LessEqual, Equal, GreaterEqual, Greater и NotEqual.
Для переопределений конкретного прохода используйте DepthWrite_PassN и
DepthFunc_PassN.
Состояние глубины действует для EffectUsage::QuadSprite и, в 3D-сборках,
EffectUsage::Model, если у цели есть depth attachment. UI, примитивы, свет и
финальные blit-проходы могут рисоваться в цели, где состояние глубины не влияет
на результат.
Значение DepthVariants по умолчанию равно False. Включайте его, только если
runtime-вызывающий код должен выбирать проверку/запись глубины для каждого
вызова отрисовки, как это делают узлы emitter-ов Effekseer. Эффект с этой опцией
строит четыре состояния: test/write, test/no-write, no-test/write и
no-test/no-write. Варианты с проверкой сохраняют авторский DepthFunc, а
варианты без проверки используют Always. Без этой опции отрисовка может
использовать только состояние, полученное из авторских DepthWrite и
DepthFunc.
Порядок глубины карты и backend-специфичное поведение описаны в Frontend и рендеринг.
Culling для отдельного вызова отрисовки
Значение CullVariants по умолчанию равно False. По умолчанию допустим только
CullModeType::None. Включайте опцию, когда модели или частицы должны выбирать
None, Back или Front для каждого вызова отрисовки. Backend-ы, запекающие
culling в состояние устройства или pipeline, создадут дополнительные варианты;
запрос не объявленного эффектом варианта завершится ошибкой, а не скрытой
отрисовкой с неверным состоянием.
Теневой проход
В 3D-сборке значение ShadowPass по умолчанию равно -1. Укажите начинающийся
с единицы индекс прохода, чтобы пометить его как теневой проход модели. Индекс
проверяется относительно compile-time лимита проходов. Runtime-отрисовка модели
может отключить отмеченные теневые проходы, не отключая остальные.
Окружение компилятора шейдеров
Каждый проход разбирается и связывается glslang как GLSL для Vulkan 1.0 и
SPIR-V 1.0. Перед [ShaderCommon] и телом стадии baker добавляет:
#version <Version> es
precision highp float;
#define MAX_SCRIPT_VALUES <FO_EFFECT_SCRIPT_VALUES>
При активном FO_ENABLE_3D он также добавляет:
#define MAX_BONES <FO_MODEL_MAX_BONES>
#define MAX_TEXTURES <FO_MODEL_MAX_TEXTURES>
Связанная программа обязана успешно построить reflection. Поэтому выходы вершинной стадии и входы фрагментной должны иметь совместимые locations/types, даже если отдельный backend допускает менее строгий исходный код.
Контракт входных вершин
Effect usage фиксируется при первой загрузке пути. Входные locations шейдера должны соответствовать этому usage.
ImGui, QuadSprite и Primitive
Эти usage совместно используют Vertex2D:
| Location | Тип GLSL | Нативное поле | Значение |
|---|---|---|---|
0 |
vec3 |
PosX, PosY, PosZ |
позиция/глубина |
1 |
vec4 |
Color |
нормализованный цвет вершины |
2 |
vec2 |
TexU, TexV |
текстурные координаты |
3 |
vec2 |
EggFlags |
egg или вспомогательные данные draw path |
Шейдер может не объявлять неиспользуемые входы. Объявленные locations/types должны оставаться совместимыми с таблицей.
Model
EffectUsage::Model использует Vertex3D:
| Location | Тип GLSL | Значение |
|---|---|---|
0 |
vec3 |
позиция |
1 |
vec3 |
нормаль |
2 |
vec2 |
основные текстурные координаты |
3 |
vec2 |
базовые/вторичные текстурные координаты |
4 |
vec3 |
tangent |
5 |
vec3 |
bitangent |
6 |
vec4 |
веса смешивания |
7 |
vec4 |
индексы смешивания |
8 |
vec4 |
нормализованный цвет вершины |
Модельные эффекты существуют только в 3D-сборках. Сохраняйте
FO_MODEL_BONES_PER_VERTEX = 4; активные backend-layout-ы проверяют эту форму.
Контракт дескрипторов и привязок
Нативное соглашение
Описывайте ресурсы по нативному соглашению Vulkan:
- descriptor set
0: uniform buffer-ы; - descriptor set
1: combined image sampler-ы.
Binding-и являются явными целыми числами. Для нативного пути они не обязаны быть плотными, но должны быть уникальны внутри одной стадии шейдера и класса ресурсов.
Тесты EffectBaker содержат исходники без set = ...; glslang всё равно может
скомпилировать такие fixtures. В production-эффектах указывайте set-ы явно.
Нативный backend Vulkan использует исходный SPIR-V и не исправляет ошибочный
авторский descriptor set.
Переназначение SDL_GPU
Baker копирует нативный SPIR-V и переписывает decorations дескрипторов:
| Стадия/ресурс | Descriptor set |
|---|---|
| sampler-ы вершинной стадии | 0 |
| uniform buffer-ы вершинной стадии | 1 |
| sampler-ы фрагментной стадии | 2 |
| uniform buffer-ы фрагментной стадии | 3 |
Внутри каждого класса/стадии авторские binding-и сортируются и переназначаются
в плотные слоты 0..N-1. Каждая стадия ограничена 16 sampler-ами и 4
uniform buffer-ами. Переназначенный модуль сохраняется как spv_sdl; исходный
код SDL Metal также компилируется из этого модуля.
Отсутствующие binding-и, повторяющиеся binding-и одной стадии, storage image и мёртвые объявления дескрипторов останавливают запекание: полное и детерминированное переназначение для них невозможно.
Текстуры, предоставляемые Engine
Runtime распознаёт следующие имена sampler-ов:
| Sampler | Доступность | Producer |
|---|---|---|
MainTex |
все сборки | основная текстура текущего спрайта/render target/модели |
IndoorMaskTex |
клиентские пути карты | текущая indoor mask |
BackgroundTex |
direct-scene пути с передачей вызывающим кодом | снимок сцены перед рефракционной отрисовкой |
ModelTex0..ModelTexN-1 |
3D-сборки | слоты текстур модели |
Неизвестные имена sampler-ов попадают в reflection, но RenderEffect не имеет
для них producer-а Engine. Поэтому исходник может скомпилироваться, а runtime
оставит sampler непривязанным. Считайте таблицу authoring allowlist, пока
изменение renderer/runtime не добавит producer в той же правке.
Встроенные uniform buffer-ы
Имена uniform block-ов и их layout в байтах фиксированы. EffectBaker сравнивает отражённые размеры с нативными структурами и отклоняет неизвестные блоки.
Общие buffer-ы
| Блок | Форма GLSL | Producer/значение |
|---|---|---|
ProjBuf |
mat4 ProjMatrix |
текущая 2D- или 3D-проекция |
MainTexBuf |
vec4 MainTexSize |
ширина, высота, обратная ширина/высота |
EggBuf |
vec4 EggData[3] |
две egg mask и параметр перехода |
SpriteBorderBuf |
vec4 SpriteBorder |
UV-прямоугольник спрайта в атласе |
ParticleSamplingBuf |
vec4 ParticleSampling |
параметры выборки частиц, ограничения атласа, искажения и ориентации фона для отдельной отрисовки |
TimeBuf |
vec4 FrameTime; vec4 GameTime |
секунды в .x, отсчитываемые от сессии и оборачиваемые на 8192 |
RandomValueBuf |
vec4 RandomValue |
четыре значения кадра в диапазоне [0,1] |
ScriptValueBuf |
vec4 ScriptValue[MAX_SCRIPT_VALUES / 4] |
управляемые проектом float-слоты |
CameraBuf |
vec4 MapAnchorScreenPos; vec4 ChunkScreenAnchor |
аффинные UV-базисы мира/экрана |
Для CameraBuf вычисляйте любой базис так:
vec2 uv = Basis.xy + TexCoord * Basis.zw;
MapAnchorScreenPos привязан к миру и не зависит от zoom.
ChunkScreenAnchor привязан к экрану. Не сводите ни один из них к вычитанию:
масштабные компоненты учитывают дополненные/разбитые на chunks render target-ы.
ParticleSamplingBuf заполняется runtime-ами частиц, а не общим путём спрайтов.
Значение его компонентов принадлежит выбранной паре эффекта/runtime частиц;
стандартные эффекты Effekseer используют их для point sampling, безопасного
ограничения атласа, интенсивности искажения и вертикальной ориентации
BackgroundTex.
Buffer-ы моделей
| Блок | Форма GLSL | Значение |
|---|---|---|
ModelBuf |
vec4 LightColor; vec4 GroundPosition; mat4 WorldMatrices[MAX_BONES] |
освещение, ground anchor, матрицы skinning |
ModelTexBuf |
vec4 TexAtlasOffset[MAX_TEXTURES]; vec4 TexSize[MAX_TEXTURES] |
преобразования атласа и размеры текстур |
ModelAnimBuf |
vec4 AnimNormalizedTime; vec4 AnimAbsoluteTime |
нормализованное и зацикленное абсолютное время анимации |
Storage buffer-ы и storage image не поддерживаются.
Владение и время жизни ScriptValueBuf
Значение FO_EFFECT_SCRIPT_VALUES по умолчанию равно 16; подключаемый проект
может его переопределить. Значение должно быть положительным и делиться на
четыре. Это compile-time форма Engine, которая обязана совпадать со
сгенерированным/запечённым define-ом шейдера.
Runtime ведёт себя так:
- Первая загрузка эффекта, объявляющего
ScriptValueBuf, создаёт обнулённый buffer. - Скриптовые записи изменяют кешированный объект
RenderEffect. - Значения сохраняются до перезаписи или явной очистки.
Game.SetEffect(...)меняет выбранный объект, но не очищает значения ни старого, ни нового объекта.- Возврат к ранее загруженному пути возвращает прежнее содержимое его buffer-а.
Game.ClearEffectScriptValues(...)обнуляет весь buffer выбранного объекта.
Ключом кеша служит только путь ресурса. Если один путь выбран в нескольких
совместимых слотах, они совместно используют один buffer. Проект должен
назначить каждому диапазону слотов одного владельца и документировать коллизии.
Повторная отправка значений после смены варианта остаётся хорошей проектной
практикой, когда варианты используют разные пути или объявления buffer-а, но
SetEffect не гарантирует сброс. SetEffect does not reset cached values.
Runtime-загрузка и идентичность кеша
EffectManager::LoadEffect(usage, path) возвращает существующий кешированный
объект, если путь загружался раньше. Ключ кеша не включает EffectUsage.
Следовательно, первая загрузка фиксирует usage объекта и допущения backend-а о
pipeline и формате входных данных.
Не используйте один путь для несовместимых категорий:
ImGui;QuadSprite;Primitive;Model.
Разные ресурсы могут содержать одинаковый текст шейдера, если им нужны разные usage. Ясность путей важнее устранения небольшого дублирования исходника.
Runtime загружает:
- запечённый исходник
.fofxдля числа проходов и состояния рендеринга; .fofx-N-infoдля отражённых нативных и SDL-слотов ресурсов;- вариант шейдера, требуемый активным renderer-ом.
Отсутствующий исходник, метаданные или вариант шейдера является ошибкой загрузки.
Скриптовый API
Поверхность скриптов клиента/mapper:
Game.SetEffect(effectType, effectSubtype, effectPath);
Game.SetEffectScriptValue(effectType, effectSubtype, valueIndex, value);
Game.SetEffectScriptValues(
effectType,
effectSubtype,
valueStartIndex,
values,
valuesOffset = 0,
valuesCount = -1);
Game.ClearEffectScriptValues(effectType, effectSubtype);
При пустом пути SetEffect восстанавливает значение слота по умолчанию.
Непустой путь загружается с usage эффекта этого слота по умолчанию.
Вызовы script value разрешают выбранную сейчас цель и завершаются ошибкой, когда:
- type/subtype не поддерживается или некорректен;
- целевая entity или offscreen-слот не существует;
- эффект не загружен;
- эффект не объявляет
ScriptValueBuf; - входной или целевой диапазон выходит за границы.
Используйте ranged-метод для блоков параметров, обновляемых вместе. Он проверяет
valuesOffset, выводит оставшееся число элементов при valuesCount = -1 и
выполняет одну нативную запись.
Записи ScriptValue для отдельных шрифтов не поддерживаются;
EffectType::Font принимает только subtype -1 для общего эффекта шрифтов.
GenericSprite и CritterSprite могут выбирать общий слот с subtype 0 либо
живую entity по id. Offscreen-subtype должен быть предварительно
зарегистрирован и загружен.
Выходы запекания
Для каждого прохода и стадии EffectBaker создаёт:
| Вариант | Потребитель |
|---|---|
spv |
нативный Vulkan; источник кросс-компиляции GLSL/ES/HLSL |
spv_sdl |
путь SDL_GPU Vulkan |
glsl |
desktop OpenGL (330) |
glsl_es |
OpenGL ES/WebGL (300 es) |
hlsl |
Промежуточный HLSL Shader Model 4.0, компилируемый при запекании |
dxbc |
Bytecode Direct3D 11, собранный из HLSL через vendored vkd3d-shader |
msl_mac |
SDL_GPU Metal на macOS |
msl_ios |
SDL_GPU Metal на iOS |
При Baking.Direct3DLevel9Shaders = True baker также помещает chunk Aon9
для уровня 9.3 в эффекты, не относящиеся к моделям. Если shader не укладывается
в этот профиль, запекание завершается ошибкой; эффекты 3D-моделей его не
получают. Настройка по умолчанию выключена. Это opt-in для 2D-only Direct3D,
а не поддержка 9.1/9.2.
Схема имён:
<path>.fofx-<pass>-<vert|frag>-<flavor>
<path>.fofx-<pass>-info
[EffectInfo] хранит общие для программы отражённые binding-и и доказывает
размеры встроенных uniform buffer-ов. [EffectInfoSdl] хранит плотные слоты
отдельных стадий и числа sampler/UBO. Исходник копируется в путь запечённого
ресурса.
Resource pack и переопределения
Engine предоставляет эффекты минимального профиля в Resources/Core/Effects/
и bootstrap-эффекты в Resources/Embedded/Effects/. Подключаемый проект может
предоставить более поздний ресурс с тем же путём и перекрыть значение Engine по
умолчанию.
Переиспользуемые значения Engine по умолчанию должны оставаться консервативными. Более богатые проектные копии могут требовать более высокий аппаратный профиль, но проект обязан проверить каждый поставляемый backend и сохранить намеренный fallback. Текущая карта слотов по умолчанию и политика минимального профиля описаны в Frontend и рендеринг.
Практики авторинга
- Начинайте с ближайшего эффекта Engine с теми же
EffectUsage, входами вершин и встроенными buffer-ами. - Объявляйте descriptor set-ы и binding-и явно, даже если test fixture показывает, что glslang способен вывести set.
- Сохраняйте малое число проходов. Каждый проход умножает компиляцию стадий, метаданные, backend-объекты и работу отрисовки.
- Удаляйте неиспользуемые sampler-ы и uniform block-и. Мёртвые объявления дескрипторов отклоняются при переназначении SDL.
- Используйте только распознаваемые имена buffer-ов и точно копируйте их layout.
- Централизуйте слоты
ScriptValueBufв проектном коде и документации: один владелец на диапазон и стабильная семантика между вариантами шейдера. - Для непрерывных снимков предпочитайте
SetEffectScriptValues; когда важна семантика сброса, явно вызывайтеClearEffectScriptValues. - Не используйте один путь для несовместимых effect usage.
- Поддерживайте периодическое время анимации.
TimeBufоборачивается на8192секундах; поддерживаемые скриптом float-часы следует оборачивать до потери точности. - Проверяйте глубину и смешивание на цели, где действительно присутствуют нужные attachments и порядок отрисовки.
- Проверяйте минимальный аппаратный профиль, поддержку которого заявляет проект; успешная кросс-компиляция не гарантирует визуальный результат или поведение драйвера.
Диагностика ошибок
| Симптом | Вероятная граница |
|---|---|
| ошибка отсутствующего Effect/vertex/fragment | написание секций, Passes или fallback исходника |
| диагностика компилятора шейдера | синтаксис GLSL, версия/профиль или интерфейс стадий |
| invalid uniform buffer size | порядок/тип полей или длина массива блока отличается от RenderEffect |
| invalid uniform buffer | неизвестное имя блока |
| ошибка explicit/duplicate binding | отсутствующий или конфликтующий локальный binding стадии |
| unused resource error | объявленный sampler/UBO оптимизирован или ни разу не читается |
| ошибка лимита стадии SDL | более 16 sampler-ов или 4 UBO в одной стадии |
| эффект загружается, но текстура пуста | имя sampler-а не имеет producer-а Engine либо неверны set/binding |
| запись script value выбрасывает исключение | неверная цель, незагруженный эффект, отсутствие ScriptValueBuf или неверный диапазон |
| script value появляется в другом слоте | оба слота разрешаются в один кешированный путь |
| возврат к эффекту восстанавливает старые настройки | ожидаемое сохранение кеша пути; явно очистите или перезапишите значения |
| работает только на одном renderer-е | различие backend-варианта, профиля, дескрипторов или глубины |
Процесс проверки
После изменения исходника формата эффектов Engine, buffer-а, backend-а или встроенного эффекта выполните:
python BuildTools\docs_effect_format.py --write
python BuildTools\tests\test_docs_effect_format.py
python BuildTools\docs_effect_format.py --check
python BuildTools\docs_contract_diff.py --help
Запустите focused нативные тесты baker-а через настроенную unit-test цель
подключаемого проекта. Владеющий файл тестов:
Source/Tests/Test_EffectBaker.cpp. Изменения состояния renderer-а также
требуют проверки соответствующего backend-а и видимой сцены.
Для подключаемого проекта:
- повторно сгенерируйте/настройте сборку, если изменился compile-time лимит;
- запеките ресурсы;
- запустите проектные validator-ы путей эффектов, назначений
EffectTypeи владения ScriptValue; - запустите репрезентативные видимые сцены для каждого затронутого слота;
- проверьте каждый поставляемый renderer/backend и минимальный аппаратный профиль;
- сравните значения по умолчанию/fallback и переопределённые проектом эффекты.
Checklist изменения
При изменении контракта Engine обновляйте вместе:
BuildTools/EffectFormatInterface.json;Docs/en/how-to/content/effect-format.md;- сгенерированные
Docs/generated/effect-format.jsonи страницы справочника; BuildTools/tests/test_docs_effect_format.py;Source/Tests/Test_EffectBaker.cppили владеющий тест renderer/runtime;- Baking Pipeline, когда меняется поведение выходов/компилятора;
- Frontend и рендеринг, когда меняется поведение runtime/backend;
- GeneratedApiAndMetadata.md и управление изменениями сгенерированного контракта, когда меняется структурированный контракт или поверхность агрегированного diff;
- документацию/тесты подключаемого проекта для путей, семантики слотов, fallback и визуальной проверки.