reckfellENGINE

First 2D shader: color, alpha and reveal

Runnable canvas_item example: Sprite2D, separate materials, modulation, alpha and C#-controlled animation.

On this page

Run the complete example

Download the project. Requires Reckfell 1.0.0 with the C# SDK. Open project.reckfell, build C# and run Main.rscene. The example is verified in Compatibility/OpenGL and Forward+/Vulkan.

Four Sprite2D cards: original image, grayscale, tint and partial reveal

This processes a saved image in 2D: the input texture comes from the 3D example rather than a live 3D world. All four cards are Sprite2D instances sharing one Shader but using separate ShaderMaterial resources.

CardParameterResult
01reveal=1original image
02desaturate=1grayscale
03tintcolor multiplier
04reveal=0.5partial reveal with a soft boundary

Set root Animate=true to animate the fourth card. Without animation, call SetReveal(value) after _Ready. It clamps the value to 0…1 and updates the caption.

Connect a canvas_item shader

Use shader_type canvas_item for 2D. In the current engine, the file extension is .reckfellshader. Create a ShaderMaterial with the program and assign it to Sprite2D.Material. The spatial shader from the 3D lesson is unsuitable here.

TEXTURE is the Sprite2D texture; UV provides sampling coordinates. fragment() computes COLOR. Declaring a uniform alone does not create an effect: the formula must use it.

The example saves vertex-stage COLOR into item_color: item color with modulation before texture sampling. fragment() samples the texture once and multiplies it by this varying. Reassigning COLOR to only the sampled texture can discard the intended modulation; multiplying already textured COLOR by TEXTURE again can darken the image through double sampling.

Card.reckfellshader
Shader
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);
}

Understand the output

desaturate blends source RGB with its weighted grayscale value. tint multiplies RGB; tint.a and opacity contribute to output alpha. Texture alpha is retained too: a translucent pixel blends with the existing background.

reveal is compared against a field built from UV.x and smooth UV.y waves. At 0 the card is fully hidden; at 1 it is fully visible. Intermediate values retain part of the image and highlight its boundary. This changes visual transparency rather than collision or game state.

source_color marks color uniforms. hint_range supplies an editor-control range rather than validating every value supplied from C#. SetReveal explicitly clamps its argument.

Control it from C#

The archive Gallery.rdata contains an ImageTexture with prepared mipmaps. The scene loads it as a resource and downscales cards with linear mipmap filtering. This is separate from 3D geometry antialiasing: MSAA does not replace texture filtering here.

For your own image, import a suitable Texture2D or prepare a resource beforehand. Do not assume Image.LoadFromFile can read the original asset:// PNG after export. Example preparation reads PNG and packages the result as Gallery.rdata; the running scene uses the resource. Layout assumes a 16:9 image.

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)));
    }
}

Edit the intended material

Materials[1].SetShaderParameter("desaturate", 0f) restores color to the second card. Others retain their settings because their ShaderMaterial resources are separate. The shared Shader does not need modification.

Assigning one ShaderMaterial to several objects shares its regular uniforms. Before editing, check resource identity, name case and UseParentMaterial. With UseParentMaterial=true the child draws with the parent material, although its Material getter still returns its own assignment. Native checks show parent tint overriding grayscale and false restoring the child shader.

CanvasItem.Modulate still affects the example through the vertex varying. Shader opacity=0 hides the image rather than removing the Entity or stopping _Process.

Animate under explicit control

_Process(double delta) accumulates C# time and sends reveal. Animate=false stops updating that parameter; SetProcess(false) stops this instance’s processing. SetProcess(true) resumes the animation, as verified in the runtime.

At reveal=1, the fourth card is fully restored:

Same 2D material after fully revealing the fourth card

If an effect must follow game pause or time scaling, define and verify that policy in its controller. This example does not establish equivalent behavior for built-in shader TIME.

Diagnosis

SymptomCheck
Card disappearedreveal, opacity, tint.a, Modulate.a and texture alpha
Image is unexpectedly darkDouble texture multiplication or repeated color modulation
Material edit has no effectAssigned resource, uniform name case and UseParentMaterial
Every card changedThey share one ShaderMaterial
Downscaling produces jagged detailFilter and mipmap availability; pixel art may need Nearest
Animation stoppedAnimate, _Process activity and SetProcess
Exported texture is missingPackaged resource/dependencies rather than assuming the original PNG remains accessible

Figures are actual 2560×1440 canvas_item runtime frames from the current local build, not editor screenshots. Verification covers the stated operations in both renderers; HDR 2D, custom light(), every CanvasItem subtype, animated-texture import and TIME pause behavior need separate validation.

CanvasItem API · ShaderMaterial API · Continue to a 3D shader

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.