reckfellENGINE

Свет и тени 2D: рабочий пример и маски

PointLight2D, заслонитель и проверяемые причины отсутствия света или тени.

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

Что получится

Светлое пятно освещает 2D-меш, а вертикальная преграда отбрасывает тень вправо. Текстура света и геометрия создаются кодом. В примере нет физических тел: картинка преграды и заслонитель света не создают коллизию.

Нужен C#-проект Reckfell.NET.Sdk 1.0.0, net10.0 и рендерер Compatibility. Сохраните три файла ниже, выполните dotnet build и прикрепите CanvasLightingExample.cs к корню Entity2D уровня .rscene. Запустите уровень. Для окна игры пример задаёт логический Viewport 320×240 во время запуска; это не меняет сохранённые настройки проекта.

CanvasMeshes.cs
C#
using System;
using Reckfell;

public static class CanvasMeshes
{
    public static ArrayMesh Quad(Vector2 size)
    {
        if (!size.IsFinite() || size.X <= 0 || size.Y <= 0)
            throw new ArgumentOutOfRangeException(nameof(size));
        Vector2 half = size * 0.5f;
        return Build(
            new[] { new Vector2(-half.X, -half.Y), new Vector2(half.X, -half.Y), half, new Vector2(-half.X, half.Y) },
            new[] { Vector2.Zero, Vector2.Right, Vector2.One, Vector2.Down },
            new[] { 0, 1, 2, 0, 2, 3 });
    }

    public static ArrayMesh Triangle() => Build(
        new[] { new Vector2(-45, -40), new Vector2(50, -17), new Vector2(-20, 45) },
        new[] { Vector2.Zero, Vector2.Right, Vector2.Down },
        new[] { 0, 1, 2 });

    private static ArrayMesh Build(Vector2[] vertices, Vector2[] uv, int[] indices)
    {
        var arrays = new Reckfell.Collections.Array();
        arrays.Resize((int)Mesh.ArrayType.Max);
        arrays[(int)Mesh.ArrayType.Vertex] = vertices;
        arrays[(int)Mesh.ArrayType.TexUV] = uv;
        arrays[(int)Mesh.ArrayType.Index] = indices;
        var mesh = new ArrayMesh();
        mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays);
        return mesh;
    }
}
CanvasLampTexture.cs
C#
using Reckfell;

public static class CanvasLampTexture
{
    public static ImageTexture Create()
    {
        using var image = Image.CreateEmpty(256, 256, false, Image.Format.Rgba8);
        for (int y = 0; y < 256; y++)
        for (int x = 0; x < 256; x++)
        {
            float radius = new Vector2(x - 127.5f, y - 127.5f).Length() / 128;
            float strength = Mathf.Max(1 - radius, 0);
            image.SetPixel(x, y, new Color(strength, strength, strength, 1));
        }
        return ImageTexture.CreateFromImage(image);
    }
}
CanvasLightingExample.cs
C#
using Reckfell;

public partial class CanvasLightingExample : Entity2D
{
    [Export] public Viewport.Msaa Antialiasing { get; set; } = Viewport.Msaa.Disabled;
    [Export] public bool FollowPointer { get; set; }
    public MeshInstance2D Ground { get; private set; } = null!;
    public MeshInstance2D Block { get; private set; } = null!;
    public PointLight2D Lamp { get; private set; } = null!;
    public LightOccluder2D Occluder { get; private set; } = null!;
    public CanvasModulate Ambient { get; private set; } = null!;

    public override void _Ready()
    {
        if (GetViewport() is Window window)
        {
            window.ContentScaleSize = new Vector2I(320, 240);
            window.ContentScaleMode = Window.ContentScaleModeEnum.Viewport;
        }
        GetViewport().Msaa2D = Antialiasing;
        Ambient = new CanvasModulate { Color = new Color(0.06f, 0.07f, 0.09f) };
        AddChild(Ambient);
        Ground = new MeshInstance2D
        {
            Mesh = CanvasMeshes.Quad(new Vector2(320, 240)),
            Position = new Vector2(160, 120),
            LightMask = 1
        };
        AddChild(Ground);
        Block = new MeshInstance2D
        {
            Mesh = CanvasMeshes.Quad(new Vector2(12, 140)),
            Position = new Vector2(170, 120),
            Modulate = new Color(0.3f, 0.35f, 0.4f),
            ZIndex = 1
        };
        AddChild(Block);
        Occluder = new LightOccluder2D
        {
            Position = Block.Position,
            OccluderLightMask = 1,
            Occluder = new OccluderPolygon2D
            {
                Closed = true,
                CullMode = OccluderPolygon2D.CullModeEnum.Disabled,
                Polygon = new[] { new Vector2(-6, -70), new Vector2(6, -70), new Vector2(6, 70), new Vector2(-6, 70) }
            }
        };
        AddChild(Occluder);
        Lamp = new PointLight2D
        {
            Position = new Vector2(100, 120),
            Texture = CanvasLampTexture.Create(),
            TextureScale = 1.5f,
            Energy = 1,
            RangeItemCullMask = 1,
            ShadowItemCullMask = 1,
            ShadowEnabled = true
        };
        AddChild(Lamp);
    }

    public override void _Process(double delta)
    {
        if (FollowPointer) Lamp.Position = GetGlobalMousePosition();
    }
}

Реальный кадр: PointLight2D и тень от контура

За что отвечает каждый объект

ОбъектНазначение
Ground и Block, MeshInstance2DВидимая поверхность и преграда
CanvasModulateЗатемняет canvas, чтобы вклад света был заметен
PointLight2DСвет с текстурой формы, Energy и масштабом TextureScale
LightOccluder2DПоложение заслонителя света
OccluderPolygon2DЗамкнутый контур заслонителя; не рисует преграду сам

ShadowEnabled должен быть включён на источнике. В примере Polygon задаётся относительно Occluder.Position=(170,120), а не в мировых координатах. Контур совпадает с видимой преградой размером 12×140. CullMode=Disabled делает его двусторонним и убирает зависимость этого примера от направления обхода вершин.

CanvasModulate не создаёт источник света. У PointLight2D должна быть Texture: чёрный край нашей радиальной текстуры даёт слабый вклад, светлый центр — сильный. Замена Energy не исправляет неверные маски, пустой контур или материал Unshaded.

Разделить маски освещения и тени

Все значения в примере равны 1. Это битовые маски, а не номера слоёв: 1 включает первый бит, 2 — второй, 3 — оба. Совпадение означает ненулевое пересечение битов, не равенство целых чисел.

Проверка для PointLight2DСопоставляемые значения
Получатель принимает светRangeItemCullMask источника и CanvasItem.LightMask получателя
Заслонитель участвует в тениShadowItemCullMask источника и OccluderLightMask заслонителя
Получатель принимает теньShadowItemCullMask источника и CanvasItem.LightMask получателя

Поэтому смена только маски заслонителя не всегда даёт ожидаемую тень. Например, при RangeItemCullMask=1, ShadowItemCullMask=2 и OccluderLightMask=2 получателю нужен LightMask=3, чтобы получить свет по первому биту и тень по второму. Это поведение подтверждено кадрами движка. Не подменяйте OccluderLightMask унаследованным LightMask самой Entity-заслонителя.

Маски света не заменяют физические collision layer/mask. ZIndex, диапазон света по Z/canvas-слоям, Visible и принадлежность Viewport проверяются отдельно.

Настроить поведение и материал

FollowPointer=true включает перемещение лампы вслед за курсором в координатах canvas. Это настройка примера; физический ввод в автоматическом прогоне не проверялся. Для фиксированного света оставьте false.

Материал CanvasItemMaterial с LightMode=Unshaded исключает поверхность из обычной реакции на PointLight2D. Если ваш объект не темнеет или не реагирует на свет, проверьте этот режим и пользовательский shader. Для normal map изучите CanvasTexture; контур теней по-прежнему задаётся отдельно. DirectionalLight2D имеет другой охват и ограничения: не переносите правила PointLight2D на него без проверки.

Если результат неверный

  • Нет света: проверьте Enabled, Texture, Energy, материал и совпадение маски получателя.
  • Нет тени: проверьте ShadowEnabled, непустой OccluderPolygon2D, обе проверки масок теней и положение контура между лампой и поверхностью.
  • Тень есть, преграды не видно: LightOccluder2D не рисует изображение; нужен отдельный Sprite2D, Polygon2D или MeshInstance2D.
  • Игрок проходит через преграду: добавьте физическое тело и Shape; контур света не является физической формой.
  • Всё слишком яркое: проверьте фоновую модуляцию и сумму вкладов нескольких источников, затем меняйте Energy.

Геометрия 2D · Сглаживание и ограничения рендерера

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

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

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

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