reckfellENGINE

Первый 2D shader: цвет, прозрачность и проявление

Рабочий canvas_item-пример: Sprite2D, отдельные материалы, modulation, alpha и управляемая C#-анимация.

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

Запустить готовый пример

Скачать проект. Нужны Reckfell 1.0.0 с C# SDK. Откройте project.reckfell, соберите C# и запустите Main.rscene. Пример проверен в Compatibility/OpenGL и Forward+/Vulkan.

Четыре Sprite2D: исходное изображение, grayscale, tint и частичное проявление

Это 2D-обработка сохранённого изображения: входная текстура взята из 3D-примера, а не рисуется заново как 3D-мир. Все четыре карточки — Sprite2D с общим Shader, но отдельными ShaderMaterial.

КарточкаПараметрРезультат
01reveal=1исходное изображение
02desaturate=1обесцвечивание
03tintцветовой множитель
04reveal=0.5частичное проявление с мягкой границей

У корня Animate=true запускает анимацию четвёртой карточки. Без анимации вызывайте SetReveal(value) после _Ready. Метод ограничивает значение 0…1 и обновляет подпись.

Подключить canvas_item shader

Для 2D используйте shader_type canvas_item. Файл в текущем движке имеет расширение .reckfellshader. Создайте ShaderMaterial с этой программой и назначьте его Material у Sprite2D. Spatial shader из 3D-урока сюда не подходит.

TEXTURE — текстура Sprite2D, UV — координаты её выборки. fragment() рассчитывает COLOR. Само объявление uniform ещё не даёт эффект: переменная должна участвовать в формуле.

В vertex() пример сохраняет COLOR в item_color. Это цвет элемента с modulation до выборки текстуры. В fragment() текстура выбирается один раз и умножается на этот varying. Если заново присвоить COLOR только выбранную текстуру, можно потерять нужный цветовой множитель; если повторно умножить уже текстурированный COLOR на TEXTURE, можно затемнить изображение двойной выборкой.

Card.reckfellshader
Шейдер
shader_type canvas_item;
render_mode blend_mix;

uniform vec4 tint : source_color = vec4(1.0);
uniform float desaturate : hint_range(0.0, 1.0) = 0.0;
uniform float reveal : hint_range(0.0, 1.0) = 1.0;
uniform float opacity : hint_range(0.0, 1.0) = 1.0;
uniform vec4 edge_color : source_color = vec4(0.22, 0.55, 1.0, 1.0);
varying vec4 item_color;

void vertex() {
    item_color = COLOR; // Includes the CanvasItem modulation, before texture sampling.
}

void fragment() {
    vec4 source = texture(TEXTURE, UV) * item_color;
    float gray = dot(source.rgb, vec3(0.2126, 0.7152, 0.0722));
    vec3 color = mix(source.rgb, vec3(gray), desaturate) * tint.rgb;
    float wave = 0.04 * sin(UV.y * 18.0) + 0.018 * sin(UV.y * 47.0);
    float field = 0.05 + 0.9 * clamp(UV.x + wave, 0.0, 1.0);
    float visible = smoothstep(field - 0.012, field + 0.012, reveal);
    float edge = 1.0 - smoothstep(0.0, 0.025, abs(reveal - field));
    edge *= step(0.001, reveal) * (1.0 - step(0.999, reveal));
    color += edge_color.rgb * edge * 0.65;
    COLOR = vec4(color, source.a * tint.a * visible * opacity);
}

Разобрать результат

desaturate смешивает исходный RGB с его взвешенным серым значением. tint умножает RGB; tint.a и opacity участвуют в выходной alpha. Исходная alpha текстуры также сохраняется — полупрозрачный пиксель смешивается с уже нарисованным фоном.

reveal сравнивается с полем из UV.x и плавных волн по UV.y. При 0 карточка скрыта полностью, при 1 видна полностью; промежуточное значение оставляет часть изображения и подсвечивает границу. Это прозрачность визуального элемента, не изменение коллизии или состояния игры.

source_color помечает цветовые uniform. hint_range задаёт диапазон управления в редакторе, а не универсальную проверку любого значения из C#. В примере SetReveal выполняет clamp явно.

Управлять из C#

В архиве Gallery.rdata содержит готовую ImageTexture с mipmaps. Сцена загружает её как ресурс и уменьшает карточки линейной фильтрацией с mipmaps. Это отдельная задача от сглаживания 3D-геометрии: MSAA здесь не заменяет фильтрацию текстуры.

Для своей картинки импортируйте подходящую Texture2D или подготовьте ресурс заранее. Не считайте прямое чтение исходного asset:// PNG через Image.LoadFromFile гарантированно доступным после экспорта. Генератор примера читает PNG при подготовке и упаковывает результат как Gallery.rdata; работающая сцена использует ресурс. Макет рассчитан на изображение 16:9.

ShaderCards2D.cs
C#
using Reckfell;

public partial class ShaderCards2D : Entity2D
{
    [Export] public bool Animate { get; set; }
    public Sprite2D[] Cards { get; private set; } = null!;
    public ShaderMaterial[] Materials { get; private set; } = null!;
    public ImageTexture Picture { get; private set; } = null!;
    private double elapsed;
    private Label revealCaption = null!;

    public override void _Ready()
    {
        TextureFilter = TextureFilterEnum.LinearWithMipmaps;
        Picture = ResourceLoader.Load<ImageTexture>("asset://Gallery.rdata");
        if (Picture == null) throw new System.InvalidOperationException("Gallery.rdata could not be loaded.");
        var shader = ResourceLoader.Load<Shader>("asset://Card.reckfellshader");
        if (shader == null) throw new System.InvalidOperationException("Card.reckfellshader could not be loaded.");
        var background = new ColorRect { Size = new Vector2(2560, 1440), Color = new Color(0.025f, 0.03f, 0.04f) };
        AddChild(background);
        AddLabel("ShaderMaterial / canvas_item", new Vector2(240, 52), 50, Colors.White);
        Cards = new Sprite2D[4]; Materials = new ShaderMaterial[4];
        string[] captions = { "01 / reveal=1", "02 / desaturate=1", "03 / tint", "04 / reveal=0.5" };
        for (int i = 0; i < 4; i++)
        {
            Vector2 at = new(240 + (i % 2) * 1120, 180 + (i / 2) * 645);
            var material = new ShaderMaterial { Shader = shader };
            var card = new Sprite2D { Texture = Picture, Centered = false,
                Position = at, Scale = Vector2.One * (960f / Picture.GetWidth()), Material = material };
            Cards[i] = card; Materials[i] = material; AddChild(card);
            var caption = AddLabel(captions[i], at + new Vector2(0, 548), 28, new Color(0.7f, 0.74f, 0.8f));
            if (i == 3) revealCaption = caption;
        }
        Materials[1].SetShaderParameter("desaturate", 1f);
        Materials[2].SetShaderParameter("tint", new Color(0.45f, 0.7f, 1));
        SetReveal(0.5f);
    }

    private Label AddLabel(string text, Vector2 at, int size, Color color)
    {
        var label = new Label { Position = at, Text = text,
            LabelSettings = new LabelSettings { FontSize = size, FontColor = color } };
        AddChild(label); return label;
    }

    public void SetReveal(float value)
    {
        value = Mathf.Clamp(value, 0, 1);
        Materials[3].SetShaderParameter("reveal", value);
        revealCaption.Text = "04 / reveal=" + value.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
    }

    public override void _Process(double delta)
    {
        if (!Animate) return;
        elapsed += delta;
        SetReveal((float)(0.5 - 0.5 * System.Math.Cos(elapsed)));
    }
}

Изменять нужный материал

Materials[1].SetShaderParameter("desaturate", 0f) возвращает цвет второй карточке. Другие карточки не меняются, потому что ShaderMaterial разные. Общий Shader при этом менять не нужно.

Если назначить один ShaderMaterial нескольким объектам, его обычные uniform станут общими. Перед правкой проверьте ресурс, регистр имени и UseParentMaterial. При UseParentMaterial=true дочерний элемент рисуется родительским материалом, хотя getter Material продолжает возвращать собственное назначение. В нативной проверке родительский tint перекрыл grayscale, а false вернул детский shader.

Modulate у CanvasItem продолжает влиять на пример: его значение передано через vertex varying. Opacity=0 в shader скрывает изображение, но сама Entity не удаляется и _Process не останавливается.

Анимировать управляемо

_Process(double delta) накапливает время C# и передаёт reveal. Animate=false прекращает обновления параметра; SetProcess(false) останавливает обработку этого экземпляра. Возобновление SetProcess(true) продолжает анимацию — это проверено в runtime.

При reveal=1 четвёртая карточка полностью восстановлена:

Тот же 2D-материал после полного проявления четвёртой карточки

Если нужен эффект, зависящий от игровой паузы или скорости времени, задайте эту политику в управляющем коде и проверьте её отдельно. Пример не утверждает такое же поведение для встроенного shader TIME.

Диагностика

СимптомПроверка
Карточка пропалаreveal, opacity, tint.a, Modulate.a и alpha исходной текстуры
Картинка неожиданно тёмнаяНет ли двойного умножения на текстуру или повторного цветового множителя
Правка материала не виднаНазначенный ресурс, регистр uniform и UseParentMaterial
Изменились все карточкиОни используют общий ShaderMaterial
После уменьшения появились ступениФильтр и наличие mipmaps; pixel art может требовать Nearest
Анимация стоитAnimate, активность _Process и SetProcess
После экспорта текстура не найденаУпакованный ресурс и зависимости вместо предположения о доступном исходном PNG

Иллюстрации — реальные кадры canvas_item-примера 2560×1440 из актуального локального runtime. Это не снимки интерфейса редактора. Проверены перечисленные операции в двух renderer; HDR 2D, custom light(), все виды CanvasItem, импорт анимированных текстур и поведение TIME при паузе требуют отдельных проверок.

CanvasItem API · ShaderMaterial API · Перейти к 3D shader

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

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

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

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