reckfellENGINE

MultiMesh: буфер, границы и цена обновления

Упаковка 3D-матрицы по строкам, 20 float на экземпляр и ограничения группового отображения.

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

Запустить bulk-запись

Добавьте GeometryStage3D.cs и MultiMeshFieldLab3D.cs, прикрепите MultiMeshFieldLab3D к Entity3D-корню C#-уровня 1.0.0 и включите UseBulkUpload. Viewport — 640×420. После сборки и F6 поле должно выглядеть так же, как при отдельных SetInstance-вызовах: 64 куба с теми же преобразованиями и цветами.

Поле MultiMesh, одинаковое для индивидуальной и bulk-записи

GeometryStage3D.cs
C#
using Reckfell;

public static class GeometryStage3D
{
    public static StandardMaterial3D Material(bool vertexColors = false, bool unshaded = false)
    {
        return new StandardMaterial3D
        {
            AlbedoColor = vertexColors ? Colors.White : new Color(0.24f, 0.44f, 1),
            VertexColorUseAsAlbedo = vertexColors,
            ShadingMode = unshaded ? BaseMaterial3D.ShadingModeEnum.Unshaded : BaseMaterial3D.ShadingModeEnum.PerPixel,
            Roughness = 0.8f
        };
    }

    public static void Setup(Entity3D root, string ru, string en)
    {
        root.AddChild(new WorldEnvironment { Environment = new Reckfell.Environment
        {
            BackgroundMode = Reckfell.Environment.BGMode.Color,
            BackgroundColor = new Color(0.035f, 0.04f, 0.05f),
            AmbientLightSource = Reckfell.Environment.AmbientSource.Color,
            AmbientLightColor = Colors.White, AmbientLightEnergy = 0.5f
        }});
        root.AddChild(new DirectionalLight3D { RotationDegrees = new Vector3(-55, -25, 0), LightEnergy = 1.2f });
        root.AddChild(new MeshInstance3D
        {
            Mesh = new BoxMesh { Size = new Vector3(8, 0.05f, 6) }, Position = new Vector3(0, -0.12f, 0),
            MaterialOverride = new StandardMaterial3D { AlbedoColor = new Color(0.10f, 0.11f, 0.13f), Roughness = 1 }
        });
        var camera = new Camera3D { Position = new Vector3(4, 4.8f, 6), Current = true, Fov = 45 };
        root.AddChild(camera); camera.LookAt(new Vector3(0, 0.35f, 0), Vector3.Up);
        var canvas = new CanvasLayer(); root.AddChild(canvas);
        var label = new Label { Position = new Vector2(24, 20), Text = TranslationServer.GetLocale().StartsWith("ru") ? ru : en };
        label.AddThemeFontSizeOverride("font_size", 22); canvas.AddChild(label);
    }
}
MultiMeshFieldLab3D.cs
C#
using System;
using Reckfell;

public partial class MultiMeshFieldLab3D : Entity3D
{
    [Export] public int Count { get; set; } = 64;
    [Export] public bool UseBulkUpload { get; set; }
    public MultiMesh Field { get; private set; } = null!;
    private MultiMeshInstance3D _visual = null!;

    public static Transform3D TransformAt(int index)
    {
        float height = 0.35f + (index % 5) * 0.12f;
        var basis = Basis.Identity.Scaled(new Vector3(0.32f, height, 0.32f));
        return new Transform3D(basis, new Vector3((index % 8 - 3.5f) * 0.5f, height / 2, (index / 8 - 3.5f) * 0.5f));
    }

    public static Color ColorAt(int index) => index % 4 == 0 ? Colors.White : new Color(0.24f, 0.44f, 1);
    public static Color DataAt(int index) => new(index / 255.0f, 0, 0, 1);

    public static float[] BuildBuffer(int count)
    {
        if (count < 1 || count > 256) throw new ArgumentOutOfRangeException(nameof(count));
        float[] buffer = new float[count * 20];
        for (int i = 0; i < count; i++)
        {
            Transform3D t = TransformAt(i); Color color = ColorAt(i), data = DataAt(i); int p = i * 20;
            buffer[p] = t.Basis.Row0.X; buffer[p + 1] = t.Basis.Row0.Y; buffer[p + 2] = t.Basis.Row0.Z; buffer[p + 3] = t.Origin.X;
            buffer[p + 4] = t.Basis.Row1.X; buffer[p + 5] = t.Basis.Row1.Y; buffer[p + 6] = t.Basis.Row1.Z; buffer[p + 7] = t.Origin.Y;
            buffer[p + 8] = t.Basis.Row2.X; buffer[p + 9] = t.Basis.Row2.Y; buffer[p + 10] = t.Basis.Row2.Z; buffer[p + 11] = t.Origin.Z;
            buffer[p + 12] = color.R; buffer[p + 13] = color.G; buffer[p + 14] = color.B; buffer[p + 15] = color.A;
            buffer[p + 16] = data.R; buffer[p + 17] = data.G; buffer[p + 18] = data.B; buffer[p + 19] = data.A;
        }
        return buffer;
    }

    public Error Rebuild(int count, bool bulk)
    {
        if (_visual is null) return Error.Unconfigured;
        if (count < 1 || count > 256) return Error.InvalidParameter;
        _visual.Multimesh = null;
        Field?.Dispose();
        Field = new MultiMesh
        {
            TransformFormat = MultiMesh.TransformFormatEnum.Transform3D,
            UseColors = true, UseCustomData = true,
            Mesh = new BoxMesh { Size = Vector3.One, Material = GeometryStage3D.Material(vertexColors: true) },
            InstanceCount = count
        };
        if (bulk) Field.Buffer = BuildBuffer(count);
        else for (int i = 0; i < count; i++)
        {
            Field.SetInstanceTransform(i, TransformAt(i));
            Field.SetInstanceColor(i, ColorAt(i));
            Field.SetInstanceCustomData(i, DataAt(i));
        }
        int rows = (count + 7) / 8;
        Field.CustomAabb = new Aabb(new Vector3(-2, -0.05f, -2), new Vector3(4, 1.5f, Math.Max(4, rows * 0.5f)));
        Field.VisibleInstanceCount = -1;
        _visual.Multimesh = Field;
        Count = count; UseBulkUpload = bulk;
        return Error.Ok;
    }

    public override void _Ready()
    {
        GeometryStage3D.Setup(this, "MultiMesh: общая геометрия, свои данные", "MultiMesh: shared geometry, per-instance data");
        _visual = new MultiMeshInstance3D(); AddChild(_visual);
        if (Rebuild(Count, UseBulkUpload) != Error.Ok) ReckfellApi.PrintErr("MultiMesh count must be 1..256");
    }
}

Формат одного элемента

В текущем API 3D-преобразование занимает 12 float. При UseColors и UseCustomData добавляются по четыре: в примере stride = 20. Размер Buffer равен InstanceCount × stride. Неверный размер отклоняется, а не автоматически преобразуется движком.

Смещение внутри элементаСодержимое
0..3Basis.Row0.X/Y/Z, Origin.X
4..7Basis.Row1.X/Y/Z, Origin.Y
8..11Basis.Row2.X/Y/Z, Origin.Z
12..15Цвет R/G/B/A, если UseColors
16..19Custom R/G/B/A, если UseCustomData

Это строки, не три подряд записанных столбца Basis. У 2D-формата преобразование занимает восемь float; не переносите его layout в этот 3D-буфер. В стенде проверены обратное чтение матрицы, цветов/custom data и совпадающее видимое поле после обоих вариантов загрузки.

Что bulk-запись меняет

BuildBuffer вычисляет данные на CPU, затем Buffer передаёт их целиком. Это сокращает число отдельных вызовов API, но всё равно выделяет массив и передаёт данные renderer. Getter Buffer может потребовать синхронизации/чтения обратно; не используйте его каждый кадр как бесплатное хранилище игры. Держите собственные CPU-данные, если они нужны постоянно.

В примере 1–256 элементов — выбранный предел стенда, не максимальная ёмкость API. Он не измеряет ускорение bulk-записи. Профилируйте одинаковое количество объектов, одинаковый кадр и целевую сборку; фиксируйте стоимость подготовки/передачи и GPU, а не только один вызов setter.

Отсечение и активные элементы

Отсечение использует общую границу MultiMesh. Одно огромное поле способно рисовать много невидимых элементов из-за попадания общей AABB в камеру. Разбивайте мир на группы подходящего размера и проверяйте результат измерениями. Количество материалов, поверхностей и проходов, включая тени, влияет на число вызовов отрисовки; обещание «всегда один draw call» неверно.

VisibleInstanceCount показывает непрерывный префикс, не произвольную маску живых объектов. Для плотного списка перемещайте активные данные в начало либо ведите несколько групп. Это не заменяет игровую идентичность Entity, физику или систему стриминга уровней.

Проверены размер/формат буфера, layout до выделения, индивидуальные и bulk-данные, CustomAabb, ёмкость и префикс видимости. Реальная отрисовка дополнительно проверяет одинаковый bulk-результат и исчезновение поля при VisibleInstanceCount = 0. Другие renderer, motion-vector/interpolation режимы и производительность пока не подтверждены этим стендом.

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

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

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

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