reckfellENGINE

Свой блок Visual Shader на C#

Порты, генерация кода, программный граф и сохранение C#-блока.

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

Собственный блок Visual Shader — C#-ресурс, который объявляет порты и возвращает фрагмент шейдерного кода. Это не игровая Entity: слово «узел» здесь означает элемент графа вычислений. В примере BlendTint смешивает исходный RGB с оттенком по весу amount от 0 до 1.

Нужен C#-редактор Reckfell 1.0.0 и собирающийся проект. Для одного такого блока EditorPlugin и plugin.cfg не нужны: редактор обнаруживает класс через [GlobalClass], а [Tool] позволяет выполнять его C#-методы в редакторе.

1. Добавьте блок

Создайте asset://addons/docs_visual/BlendTint.cs. Имя файла должно совпадать с именем класса:

C#
using Reckfell;
using Reckfell.Collections;

[Tool, GlobalClass]
public partial class BlendTint : VisualShaderNodeCustom
{
    public override string _GetName() => "BlendTint";
    public override string _GetCategory() => "Reckfell/Color";
    public override string _GetDescription() => "Blend RGB toward a tint; amount is clamped to 0..1.";
    public override PortType _GetReturnIconType() => PortType.Vector3D;
    public override int _GetInputPortCount() => 3;
    public override string _GetInputPortName(int port) => port switch
    {
        0 => "source", 1 => "tint", 2 => "amount", _ => ""
    };
    public override PortType _GetInputPortType(int port) =>
        port == 2 ? PortType.Scalar : PortType.Vector3D;
    public override Variant _GetInputPortDefaultValue(int port) => port switch
    {
        0 => Vector3.One,
        1 => new Vector3(0.1f, 0.35f, 1.0f),
        2 => 0.5f,
        _ => default
    };
    public override int _GetOutputPortCount() => 1;
    public override string _GetOutputPortName(int port) => "rgb";
    public override PortType _GetOutputPortType(int port) => PortType.Vector3D;
    public override bool _IsAvailable(Shader.Mode mode, VisualShader.Type type) =>
        (mode == Shader.Mode.CanvasItem || mode == Shader.Mode.Spatial) &&
        type == VisualShader.Type.Fragment;
    public override string _GetGlobalCode(Shader.Mode mode) => """
        vec3 reckfell_docs_blend_tint(vec3 source, vec3 tint, float amount) {
            return mix(source, tint, clamp(amount, 0.0, 1.0));
        }
        """;
    public override string _GetCode(Array<string> inputVars, Array<string> outputVars,
        Shader.Mode mode, VisualShader.Type type) =>
        $"{outputVars[0]} = reckfell_docs_blend_tint({inputVars[0]}, {inputVars[1]}, {inputVars[2]});";
}

Соберите проект. Если класс ещё не появился в списке добавления блоков, дождитесь сканирования файлов и заново откройте граф; при необходимости перезапустите редактор. Не добавляйте фиктивный plugin.cfg: он не исправит ошибку C#-сборки или отсутствие [GlobalClass].

2. Соберите граф в редакторе

  1. Выберите Sprite2D с текстурой либо ColorRect с ненулевым размером. Создайте у него ресурс Material → ShaderMaterial.
  2. В свойстве Shader создайте VisualShader и откройте его. Установите режим CanvasItem, этап Fragment.
  3. В окне добавления найдите BlendTint. Категория — Addons → Reckfell → Color. _GetCategory() возвращает вложенный путь Reckfell/Color; префикс Addons добавляет редактор.
  4. Добавьте блок. Соедините выход rgb с входом Color блока Output. Порт Alpha этот пример не изменяет.
  5. Оставьте значения source = (1,1,1), tint = (0.1,0.35,1), amount = 0.5. Формула даёт RGB (0.55,0.675,1). Это результат вычисления в шейдере, а не обещание идентичного экранного пикселя при любых настройках вывода.
  6. Измените amount: при 0 получается source, при 1 — tint. Значения вне диапазона ограничиваются функцией clamp().
  7. Сохраните ресурс VisualShader в проекте, например как asset://tint-graph.rdata.

Для 3D используйте Spatial → Fragment и соедините rgb с Albedo. Это тот же RGB-расчёт, но освещение материала влияет на изображение. Самостоятельный BlendTint не читает текстуру спрайта: чтобы сохранить её рисунок, подключите цвет из графа к source вместо постоянного белого значения. Обработка альфа-канала остаётся отдельной связью.

Порты и генерируемый код

ПортТипЗначение без соединения
sourceVector3D → vec3(1,1,1)
tintVector3D → vec3(0.1,0.35,1)
amountScalar → float0.5
rgbVector3D → vec3Выход вычисления

_GetInputPortName() задаёт подпись порта. Не подставляйте её вместо inputVars[i]: генератор передаёт готовые выражения и имена временных переменных, которые зависят от графа. _GetCode() должен присвоить результат соответствующему outputVars[i] и закончить инструкцию точкой с запятой.

Строка, возвращаемая C#-методом, написана на шейдерном языке Reckfell, а не на C#. Поэтому внутри неё используются vec3, mix() и clamp(). C#-сборка проверяет обёртку, но сама по себе не доказывает корректность GPU-кода.

_GetGlobalCode() объявляет общую функцию. Генератор добавляет её один раз для использованного типа C#-блока, даже если в графе несколько экземпляров. Дайте функции уникальное имя: два разных скрипта с одинаковым именем глобальной функции могут конфликтовать. Не помещайте туда состояние, уникальное для отдельного экземпляра.

_IsAvailable() ограничивает список добавления режимами CanvasItem/Spatial и этапом Fragment. Это фильтр доступности, а не механизм перевода кода из одного шейдерного режима в другой.

3. Тот же граф из C#

Для процедурного создания графа сохраните TintGraph.cs рядом с проектными скриптами:

C#
using System;
using Reckfell;

public static class TintGraph
{
    public static VisualShader Create()
    {
        var shader = new VisualShader();
        shader.SetMode(Shader.Mode.CanvasItem);
        var tint = new BlendTint();
        int id = shader.GetValidNodeId(VisualShader.Type.Fragment);
        shader.AddNode(VisualShader.Type.Fragment, tint, new Vector2(100, 100), id);
        // The editor applies these callbacks when inserting a node through its UI.
        // AddNode alone does not initialize the default input values.
        for (int port = 0; port < tint._GetInputPortCount(); port++)
            tint.SetInputPortDefaultValue(port, tint._GetInputPortDefaultValue(port));
        var error = shader.ConnectNodes(VisualShader.Type.Fragment, id, 0,
            (int)VisualShader.NodeIdOutput, 0);
        if (error != Error.Ok)
        {
            shader.Dispose();
            throw new InvalidOperationException("Cannot connect tint output: " + error);
        }
        return shader;
    }
}

TintGraph.Create() возвращает ресурс VisualShader; назначьте его свойству ShaderMaterial.Shader. Результат принадлежит вызывающему коду и материалу, которому он назначен. Не вызывайте Dispose() у ресурса или его C#-блока, пока их ещё использует материал/граф.

Важное различие 1.0.0: AddNode() обновляет описание портов, но не применяет _GetInputPortDefaultValue() автоматически. Редактор вызывает эти методы при добавлении через UI. Поэтому фабрика явно записывает исходные значения после AddNode().

Нельзя заключать new BlendTint() внутри фабрики в using: при выходе из метода Dispose() отсоединяет C#-поведение от ещё используемого native-ресурса. В графе останется объект, но нужные переопределения уже не будут работать. Это проверено на установленной версии; для живого блока оставляйте управление временем жизни графу и C#-мосту.

Для связей используйте один и тот же этап VisualShader.Type.Fragment. ID 0 принадлежит Output; свободный ID запрашивайте через GetValidNodeId(). Перед интерактивным соединением полезен CanConnectNodes(): он отклоняет отсутствующие блоки, неверные порты, повторные связи и циклы. Всё равно проверяйте результат ConnectNodes().

Ошибки и исправления

СимптомПричина и действие
Нет BlendTint в спискеПроверьте сборку, [Tool, GlobalClass], наследование VisualShaderNodeCustom, совпадение имени файла и класса. Перепроверьте режим и этап графа.
Ошибка имени блока_GetName() должен быть допустимым ASCII-идентификатором без пробелов; используйте BlendTint. Категория, в отличие от имени, допускает путь через /.
Порт выдаёт чёрный цветПроверьте значения неподключённых портов, выходную связь и отсутствие преждевременного Dispose(). При программном создании явно назначьте значения по умолчанию.
Ошибка компиляции шейдераОткройте сгенерированный код и сообщение ошибки. Проверьте типы vec3/float, объявления вспомогательной функции, имена и ;. Успешная C#-сборка этого не проверяет.
Несколько блоков вызывают повторное объявлениеОбщая функция должна жить в _GetGlobalCode(), иметь уникальное имя и не повторяться вручную в _GetCode().
После удаления блока пропал эффектВходящие и исходящие связи удаляются вместе с ним. Соедините оставшийся граф с Output заново.
После перезапуска ресурс не работаетСохраните и сам граф, и исходный BlendTint.cs; не переносите один .rdata без его C#-зависимости. Пересоберите проект.

Границы проверки

В headless-редакторе 1.0.0 проверены регистрация глобального C#-класса, типы и значения портов, построение и связи графа, генерация текста CanvasItem Fragment, сохранение/загрузка со скриптом, два экземпляра с одной общей функцией и удаление связей. Пример не подтверждает GPU-компиляцию, экранный цвет, физическое управление графом, картинку Spatial, экспорт или работу на других платформах. Не используйте этот пример как иллюстрацию качества рендера.

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

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

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

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