reckfellENGINE

Shading language: types, stages and uniform scopes

Complete example, language rules, material/global/instance data and actual limitations.

On this page

The engine shading language describes work for a particular rendering stage. It resembles GLSL, but a regular Shader is not a raw GLSL program: choose shader_type and the stage functions supported by that type. For imported code, use GLSL conversion; for raw GPU compute, use RenderingDevice compute.

Start with a complete example

The following canvas_item shader demonstrates typed functions, overloads, arrays, a struct, loops, a matrix, a varying and three uniform scopes. It draws a diagnostic gradient; it is not a performance or beauty-scene example.

Before opening the shader in the editor, define the global float rf_docs_gain with value 1.0. Its project.reckfell representation is the entry rf_docs_gain={"type":"float","value":1.0} inside the [shader_globals] section. Reload the project after editing that file externally. Do not add a duplicate runtime registration for a name already defined by the project.

For a runtime-created setup without that project definition, call RenderingServer.GlobalShaderParameterAdd("rf_docs_gain", RenderingServer.GlobalShaderParameterType.Float, 1f) before loading the shader. Both startup paths are tested. Global registration and a shader declaration must use the same name and type.

Save this as LanguageFeatures.reckfellshader, load it with ResourceLoader.Load<Shader>, assign it to a ShaderMaterial and apply the material to a white 128×128 ColorRect:

Shader
shader_type canvas_item;
render_mode unshaded;

const float LEVELS[3] = float[3](0.125, 0.25, 0.5);

group_uniforms Surface;
uniform float gain : hint_range(0.0, 1.0) = 1.0;
uniform int palette : hint_enum("Base", "High") = 0;
uniform bool visible_effect = true;
group_uniforms;

global uniform float rf_docs_gain;
instance uniform float item_gain = 1.0;
varying vec2 local_uv;

struct SurfaceSample {
    vec3 rgb;
    float alpha;
};

float evaluate(float value) {
    return value * 0.5;
}

vec2 evaluate(vec2 value) {
    return value * 0.5;
}

void add_red(inout vec3 rgb) {
    rgb.r += 0.125;
}

SurfaceSample sample_surface(vec2 uv) {
    float samples[2] = float[2](LEVELS[0], LEVELS[1]);
    float total = 0.0;
    for (int i = 0; i < samples.length(); i++) {
        total += samples[i];
    }
    int step = 0;
    while (step < 2) {
        step++;
        if (step == 2) {
            break;
        }
        total += 0.125;
    }
    switch (palette) {
        case 0:
            break;
        case 1:
            total = LEVELS[2] + 0.25;
            break;
        default:
            total = LEVELS[0];
            break;
    }
    mat2 axes = mat2(vec2(0.0, 1.0), vec2(1.0, 0.0));
    SurfaceSample result;
    result.rgb = vec3(evaluate(axes * uv), evaluate(total));
    result.alpha = 1.0;
    return result;
}

void vertex() {
    local_uv = UV;
}

void fragment() {
    if (!visible_effect) {
        discard;
    }
    SurfaceSample result = sample_surface(local_uv);
    add_red(result.rgb);
    result.rgb *= gain * rf_docs_gain * item_gain;
    COLOR = vec4(result.rgb, result.alpha);
}

At pixel (32, 32), the checked output is approximately (0.252, 0.127, 0.25) before display quantization. The matrix swaps the two UV axes, so green grows to the right and red downward. Palette 1 changes blue to approximately 0.375. Two cards can share the same material; an item_gain override changes only the chosen card.

Types, constructors and operators

FamilyUse and constraints
bool, bvec2/3/4Logical values; use a scalar boolean condition for control flow.
int / ivec2/3/4, uint / uvec2/3/4Signed/unsigned integer values; use uint literals such as 1u when that type is required.
float, vec2/3/4Floating-point values and vectors. A precision qualifier is not a request for C# double.
mat2/3/4Square column-major matrices. The example constructs columns explicitly before multiplying a vector.
sampler familiesReferences to texture resources, not pixel arrays embedded in the shader. Match dimensionality and integer/float sampling to the resource.
voidFunction return with no value.

Use explicit constructors when a conversion matters: float(index), vec3(value), or vec4(rgb, alpha). A scalar vector constructor fills components; a vector is not a C# collection. Overload selection depends on argument types, not only the desired return type. Keep integer and floating-point literals deliberate: integer division is not fractional division.

Vector arithmetic is component-wise; use dot/cross for their mathematical operations and matrix multiplication for coordinate transforms. Do not confuse matrix multiplication with the special C# Transform2D inverse operators. Arithmetic, comparisons, logical operators, integer bitwise/shift operations, assignments and the conditional expression are available with compatible types.

Swizzles select/reorder components: xyzw for geometry, rgba for color, stpq where appropriate. Do not mix naming families in one swizzle or assign overlapping destination components such as xx. Normalize directions only when needed; a zero vector is not a valid direction to normalize.

lowp/mediump/highp are per-variable precision choices. Availability, actual precision and performance depend on the backend/device; use sufficient precision for coordinates and test lower precision on the target instead of assuming a universal speedup.

Arrays, constants and structs

Declare a known array size and initialize every element read by your algorithm. The example uses a global constant array and a local initialized array. .length() supplies its declared length; valid indices are 0 through length−1. A dynamic index still needs a bounds guarantee. Do not treat a shader array as a resizable .NET List.

const values cannot be assigned later. Ordinary writable variables are not a replacement for persistent cross-frame storage: changing a local/global shader variable does not save gameplay state for the next invocation or frame.

Define struct types at global scope before use, then initialize their fields. They group related values and can be passed/returned by functions, as SurfaceSample demonstrates. Declaring a new struct inside another struct is rejected; a struct uniform is also rejected. Do not infer support for arbitrary C# object graphs.

Functions and control flow

Define helper functions before their callers. Overloads may have different argument types; recursion is rejected. in is the normal input direction, out supplies an output, inout reads and writes the caller's value, and const input parameters prevent modification. Initialize every output on every reachable path.

if/else, for, while, do/while and switch express runtime flow; break, continue and return alter that flow. Bound loops intentionally. A shader invocation does not run a sequential loop over the entire rendered image, and neighboring pixels are not ordinary shared variables.

discard terminates the current fragment without writing its color/depth contribution. It is not equivalent to writing black and is not a general early return for every shader stage. Test its interaction with blending/depth and avoid assuming it makes expensive earlier work free.

Varyings and built-ins

A varying transfers stage data; in the example vertex writes local_uv and fragment receives interpolation across the primitive. It is not a uniform and should not be used as writable scratch storage in arbitrary helpers. smooth provides interpolation; flat preserves a primitive value. Integer vertex-to-fragment varyings require flat in this implementation.

Built-ins and permitted writes depend on shader_type and stage. UV, COLOR, VERTEX, TIME and FRAGCOORD do not have one universal space or mutability rule. In particular, VERTEX is not a direct replacement for GLSL gl_Position. Check the relevant stage reference before copying an expression.

Math, vector/matrix and texture functions are provided by the language, not the C# API. Match overloads and sampler types; derivative, texture LOD and stage-restricted operations need their specific rules. This example verifies its used operations, not every built-in function on every renderer.

Choose a uniform scope

DeclarationOwnership and update
uniformValue belongs to the ShaderMaterial. SetShaderParameter affects every item sharing that material.
global uniformValue belongs to the rendering context's global registry. Declare/register its name and type before use; update with GlobalShaderParameterSet.
instance uniformValue belongs to the rendered item. For CanvasItem, use SetInstanceShaderParameter to vary one item without duplicating the material.

In the example, SetShaderParameter("gain", 0.5f) darkens both sharing cards; setting the right card's item_gain to 0.5f changes only the right. Passing Nil to the material or CanvasItem setter restores the relevant declared default in the checked case. Uniform names are case-sensitive: a cached misspelled parameter is not proof the shader uses it.

hint_range and hint_enum describe Inspector controls; they do not enforce runtime validation. A programmatic gain=1.5 is accepted despite hint_range(0,1). Clamp or reject values where the algorithm requires a bound. source_color identifies color input for the appropriate color-space conversion; do not apply it blindly to normals/masks/numerical data. Texture hints control the relevant sampling path, not the texture's semantic meaning. group_uniforms organizes Inspector metadata without renaming the actual uniform.

Keep current global values in application-side state. In this version, runtime calls to GlobalShaderParameterGet, GetList and GetType are rejected by the rendering implementations outside the editor context; do not poll them in gameplay. A global override takes precedence over the base value: Set updates the stored base while the override remains active, and SetOverride with Nil clears it and reveals the updated base. Remove globals only after dependent materials/shaders are no longer in use.

Per-instance uniforms have a 16-index allocation limit here; use simple scalar/vector parameters and coordinate explicit instance_index hints when materials share an instance layout. Sampler and matrix instance types are rejected. Array/material combinations and multi-surface layouts need separate validation. Total material-uniform bytes, alignment, sampler count and varying budgets also depend on the device/backend: count real layout costs instead of treating a platform name as a capacity guarantee.

Diagnose a valid shader that looks wrong

Check the shader type/stage, coordinate domain, exact uniform name, correct scope and intended defaults first. Then isolate sampling/color space, alpha/depth and the algorithm. Use the first compiler diagnostic before chasing downstream messages. Treat shader compilation, visible behavior and target-device performance as separate checks.

The native example verifies the complete shader and metadata, range bypass, material/global/instance updates, override reset and fragment discard on Windows Forward+/Vulkan. Invalid-language diagnostics, every built-in/type/precision/interpolation form, editor UI and other renderers/exports remain separate validation. See shader style for a smaller production-shaped example.

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.