reckfellENGINE

DrawableTexture2D: запись, mipmap и рисование

Инициализация, полные C#-примеры, несколько выходов и правильное смешивание.

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

DrawableTexture2D — текстура, в которую можно выполнять GPU-операции BlitRect/BlitRectMulti. Её можно показывать через TextureRect или Sprite2D, как другую Texture2D. Изменение происходит по вызову операции: назначенный при blit материал не становится постоянным эффектом текстуры.

Создайте ресурс до первой записи

Вызовите Setup с положительными размерами от 1 до 16384, форматом и явным начальным цветом. Для прозрачного холста используйте Colors.Transparent: пропущенный/null цвет Setup означает белый, а не прозрачный.

Сохраните полный помощник DrawableOps.cs. Он создаёт проверенный RGBA8-ресурс, копирует источник целиком и выполняет пример с двумя источниками/четырьмя выходами:

C#
using System;
using Reckfell;

public static class DrawableOps
{
    public static DrawableTexture2D Create(Vector2I size, Color color, bool mipmaps = false)
    {
        if (size.X < 1 || size.Y < 1 || size.X > 16384 || size.Y > 16384)
            throw new ArgumentOutOfRangeException(nameof(size));
        var target = new DrawableTexture2D();
        target.Setup(size.X, size.Y, DrawableTexture2D.DrawableFormat.Rgba8, color, mipmaps);
        return target;
    }

    public static void Copy(DrawableTexture2D target, Texture2D source, Material material)
    {
        if (target.GetRid() == source.GetRid())
            throw new ArgumentException("Source and destination must be different textures.", nameof(source));
        target.BlitRect(new Rect2I(0, 0, target.GetWidth(), target.GetHeight()), source, Colors.White, 0, material);
    }

    public static void Split(DrawableTexture2D target, DrawableTexture2D output1,
        DrawableTexture2D output2, DrawableTexture2D output3, Texture2D first,
        Texture2D second, Rect2I rect, Material material)
    {
        var outputs = new[] { target, output1, output2, output3 };
        for (int i = 0; i < outputs.Length; i++)
            for (int j = 0; j < i; j++)
                if (outputs[i].GetRid() == outputs[j].GetRid())
                    throw new ArgumentException("Output textures must be distinct.");
        foreach (var output in outputs)
        {
            if (output.GetWidth() != target.GetWidth() || output.GetHeight() != target.GetHeight())
                throw new ArgumentException("Output textures must have the same size.");
            if (output.GetRid() == first.GetRid() || output.GetRid() == second.GetRid())
                throw new ArgumentException("A source cannot also be an output.");
        }
        var sources = new Reckfell.Collections.Array<Texture2D> { first, second };
        using var sourcesOwner = (Reckfell.Collections.Array)sources;
        var extras = new Reckfell.Collections.Array<DrawableTexture2D> { output1, output2, output3 };
        using var extrasOwner = (Reckfell.Collections.Array)extras;
        target.BlitRectMulti(rect, sources, extras, Colors.White, 0, material);
    }
}

Для первого результата создайте DrawableOps.Create(new Vector2I(256, 256), Colors.Transparent), назначьте ресурс в Texture у TextureRect и вызовите target.BlitRect(new Rect2I(20, 30, 40, 40), null, Colors.Red). null-источник использует белую текстуру, поэтому цвет задаёт modulate. Эта операция рисует красный прямоугольник.

Setup повторно создаёт содержимое и сбрасывает предыдущие рисунки. В проверенном RD-пути ресурс сохраняет свой RID, но это не сохранение пикселей. SetFormat и SetUseMipmaps меняют настройки ресурса без переразмещения уже созданного хранилища; для нового формата/цепочки mipmap снова используйте Setup. GetUseMipmaps не доказывает наличие физических уровней после отдельного изменения флага.

Прямоугольник и смешивание

rect задаёт область назначения, а не вырезку из источника. В неё масштабируется весь источник; для обрезки изменяйте UV в собственном шейдере. Пиксели вне прямоугольника остаются прежними. Нельзя читать ту же текстуру, в которую идёт запись, включая дополнительные выходы. Для последовательных эффектов используйте две текстуры и меняйте их ролями.

Без явного material применяется готовый blend_mix: полупрозрачный красный поверх синего даёт смесь. Собственный texture_blit без render_mode использует blend_disabled и заменяет RGBA. MODULATE умножает результат стандартного шейдера; это не маска каналов. Например (0,0,1,0) при blend_disabled записывает синий с нулевой alpha и обнуляет R/G, а при обычном blend_mix alpha=0 не даёт видимого вклада.

BlitMaterial выбирает готовую операцию копирования с одним из проверенных режимов Mix/Add/Sub/Mul/Disabled. Он заменяет переданный материал, а не переопределяет render_mode произвольного пользовательского шейдера. Для собственной формулы используйте ShaderMaterial типа texture_blit. См. справочник шейдера.

Mipmap-уровни

Создайте цепочку через Setup(..., useMipmaps:true). Обычная запись в уровень 0 не означает автоматическую актуализацию всей цепочки. После серии операций вызовите GenerateMipmaps, если нижние уровни должны соответствовать базе.

Параметр mipmap выбирает уровень назначения. rect всё ещё задаётся относительно полного базового размера: у текстуры 16×16 прямоугольник (0,0,16,16) заполняет весь уровень 1 размером 8×8. Запись в уровень 1 оставляет базовый уровень прежним. GenerateMipmaps затем перезаписывает нижние уровни из базы, включая вручную нарисованный уровень 1. Не указывайте уровень, которого нет в выделенной цепочке.

Несколько источников и результатов

BlitRectMulti принимает 1–4 источника и до трёх дополнительных целей: вместе с текущей текстурой это 1–4 выхода. Все цели должны быть одинакового размера и иметь запрошенный mipmap-уровень. Источники не должны совпадать ни с одной целью; не используйте повторяющиеся выходные ресурсы.

Помощник Split выше показывает полный C#-вызов для двух источников и четырёх целей. Для него используйте BlitMulti.reckfellshader из справочника. COLOR0 пишет в target, COLOR1/2/3 — в output1/2/3. Источники с hint_blit_source0/1 не обязаны математически соответствовать выходам: это определяет ваш код.

Array<T> из Reckfell.Collections не реализует IDisposable напрямую. В помощнике освобождается принадлежащий ему базовый Array через явное преобразование; сами текстуры от этого не освобождаются. Не помещайте заимствованные ресурсы, которые ещё отображаются, в короткие using-области.

Полный пример рисования в TextureRect

Сохраните DrawablePaint.cs рядом с DrawableOps.cs и прикрепите его к TextureRect с ненулевым размером. Скрипт растягивает весь холст в элемент управления, переводит его локальные координаты в пиксели текстуры и рисует квадратной кистью левой кнопкой. Движение заполняет промежутки между полученными позициями.

C#
using Reckfell;

public partial class DrawablePaint : TextureRect
{
    [Export] public Vector2I CanvasSize { get; set; } = new Vector2I(256, 256);
    [Export] public int BrushSize { get; set; } = 12;
    [Export] public Color BrushColor { get; set; } = Colors.Red;
    public DrawableTexture2D Canvas { get; private set; } = null!;
    private bool drawing;
    private Vector2 last;

    public override void _Ready()
    {
        Canvas = DrawableOps.Create(CanvasSize, Colors.Transparent);
        Texture = Canvas;
        ExpandMode = ExpandModeEnum.IgnoreSize;
        StretchMode = StretchModeEnum.Scale;
    }

    public override void _GuiInput(InputEvent input)
    {
        if (Size.X <= 0 || Size.Y <= 0) return;
        if (input is InputEventMouseButton button && button.ButtonIndex == MouseButton.Left)
        {
            drawing = button.Pressed;
            if (drawing)
            {
                last = ToPixel(button.Position);
                Stamp(last);
            }
        }
        else if (input is InputEventMouseMotion motion)
        {
            if ((motion.ButtonMask & MouseButtonMask.Left) == 0) { drawing = false; return; }
            if (!drawing) return;
            Vector2 current = ToPixel(motion.Position);
            int steps = Mathf.Max(1, Mathf.CeilToInt(last.DistanceTo(current) / Mathf.Max(1, BrushSize / 2)));
            for (int i = 1; i <= steps; i++) Stamp(last.Lerp(current, (float)i / steps));
            last = current;
        }
    }

    private Vector2 ToPixel(Vector2 point) => point / Size * new Vector2(CanvasSize.X, CanvasSize.Y);
    private void Stamp(Vector2 point)
    {
        int width = Mathf.Max(1, BrushSize);
        var rect = new Rect2I(Mathf.FloorToInt(point.X - width / 2f), Mathf.FloorToInt(point.Y - width / 2f), width, width);
        Canvas.BlitRect(rect, null!, BrushColor);
    }
}

Не меняйте CanvasSize после _Ready без пересоздания холста. Пример рассчитан на StretchMode.Scale и ExpandMode.IgnoreSize; для KeepAspect, области изображения с полями, повёрнутой кисти или текстуры атласа нужен соответствующий перевод координат. BrushSize измеряется в пикселях текстуры, а не экрана.

Обновление, чтение и ограничения

Изменение uniform не переигрывает предыдущий blit. TIME берётся в момент новой операции, поэтому текстура сама по себе не анимируется. Повторяйте blit осознанно, когда меняются данные, а не просто каждый кадр без необходимости.

GetImage выполняет чтение результата с GPU и создаёт принадлежащий вызывающему Image. Освободите Image после использования; не делайте readback для каждого штриха, если данные нужны только на GPU. Rgba8, Rgba8Srgb, Rgbah и Rgbaf имеют разную интерпретацию цвета/точность. Обычные RGBA8-пиксели примера не доказывают одинаковую арифметику всех HDR/sRGB путей.

AtlasTexture как источник текущий ресурсный API не поддерживает и подставляет белый с предупреждением. Используйте сам атлас и явное преобразование UV. Для premultiplied alpha используйте проверенный ShaderMaterial с blend_premul_alpha: текущий кэш BlitMaterial не покрывает объявленное enum-значение PremultipliedAlpha, поэтому этот путь не следует выбирать у BlitMaterial данной версии.

Диагностика

СимптомЧто проверить
Пустая или белая текстураSetup, начальный цвет, корректный источник и выполненный BlitRect.
Прозрачный цвет не смешиваетсяРазличие готового blend_mix и собственного blend_disabled.
После uniform-правки ничего не меняетсяВыполните новую операцию blit.
Кисть смещена или имеет неверный размерПеревод Size элемента в CanvasSize и режим растяжения.
Рисование начинается правой кнопкой/не останавливаетсяButtonIndex, Pressed и ButtonMask; не переключайте флаг на любом клике.
Мелкий масштаб показывает старую картинкуАктуальность mipmap-цепочки.
Mipmap-уровень исчез после GenerateMipmapsГенерация заменяет вручную записанные нижние уровни.
Несколько выходов дают ошибкуЧисло, равенство размеров, наличие уровней и отсутствие совпадений с источниками.

В Windows Forward+/Vulkan проверены фактические Image-пиксели копирования, смешивания, нескольких выходов, mipmap и вызовов _GuiInput примера. Последние — программно переданные события, не проверка реального мышиного ввода. Все платформы/рендереры, HDR/sRGB, AtlasTexture и опасный путь PremultipliedAlpha у BlitMaterial здесь отдельно не запускались. Снимки этих функциональных проверок не публикуются как художественные иллюстрации.

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

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

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

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