Первый 2D shader: цвет, прозрачность и проявление
Рабочий canvas_item-пример: Sprite2D, отдельные материалы, modulation, alpha и управляемая C#-анимация.
Запустить готовый пример
Скачать проект. Нужны Reckfell 1.0.0 с C# SDK. Откройте project.reckfell, соберите C# и запустите Main.rscene. Пример проверен в Compatibility/OpenGL и Forward+/Vulkan.
Это 2D-обработка сохранённого изображения: входная текстура взята из 3D-примера, а не рисуется заново как 3D-мир. Все четыре карточки — Sprite2D с общим Shader, но отдельными ShaderMaterial.
| Карточка | Параметр | Результат |
|---|---|---|
| 01 | reveal=1 | исходное изображение |
| 02 | desaturate=1 | обесцвечивание |
| 03 | tint | цветовой множитель |
| 04 | reveal=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
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 четвёртая карточка полностью восстановлена:
Если нужен эффект, зависящий от игровой паузы или скорости времени, задайте эту политику в управляющем коде и проверьте её отдельно. Пример не утверждает такое же поведение для встроенного 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 при паузе требуют отдельных проверок.