User Pixel Shaders

EdenSpark supports custom pixel shaders authored in Daslang. A pixel shader is a GPU program that runs once per fragment and determines the final color output for that pixel, with full access to interpolated vertex attributes, material properties, and global engine state.

Quick Start

PBR shader — the surface is shaded by the scene lighting (directional light, point lights, global illumination):

require engine.render.shader_dsl

var {
    @color tint = float3(0.2, 0.8, 1.0)   // editable color picker in material inspector
    speed = 2.0
}

[pixel_shader]
def hologram_effect(inp : PbrInput) {
    let n     = noise(inp.worldPos)
    let pulse = sin(g_Time * speed) * 0.5 + 0.5

    return PbrOutput(
        albedo          = tint * n,
        emission        = tint * pulse,
        emissionStrength = pulse * 0.5,
        metalness       = 0.0,
        roughness       = 1.0,
        ao              = 1.0
    )
}

Unlit shader — no lighting, direct color output:

require engine.render.shader_dsl

var @color color = float3(1.0, 0.5, 0.0)

[pixel_shader]
def flat_color(inp : UnlitInput) {
    return UnlitOutput(
        color = color,
        alpha = 1.0
    )
}

Files and Hot-Reload

  • Create a foo.shader file anywhere in the project folder.

  • Assign the shader to a material via the Material inspector by selecting it in the shader slot — you can replace the built-in shader on any material or create a new .material file that references it.

  • Hot-reload is triggered when you save from within the editor (Ctrl+S). Saving from an external editor may not trigger a rebuild.

Material Properties

Module-level var declarations become per-material properties editable in the inspector and overridable per mesh instance.

var {
    speed         = 2.0                               // float
    offset        = float2(0.5, 0.0)                  // float2
    @color tint   = float3(1.0, 1.0, 1.0)             // float3 with color picker
    blend         = float4(1.0, 0.0, 0.0, 1.0)        // float4
    albedoTex     : Sampler2D = Sampler2D("assets/default.png")  // texture
}

Type

Notes

float

Single scalar

float2

Two-component vector; default as float2(x, y)

float3

Three-component vector; default as float3(x, y, z)

float4

Four-component vector; default as float4(x, y, z, w)

Sampler2D

Texture reference; default as Sampler2D("path/to/texture.png")

@color float3

Same storage as float3, but shown as a color picker in the inspector

Defaults must be constant literals. References to other module-level let constants are also allowed.

Note

Only variables declared in the same module as the [pixel_shader] function become material properties. Variables from other modules cannot be accessed.

Shader Types

The shader type is determined by the function signature: the parameter type selects what geometry data is available as input, and the return type selects which outputs are written.

PBR Shader (PbrInputPbrOutput)

Physically-based rendering — the surface is lit by the engine’s full lighting pipeline: directional light, point lights, spot lights, and global illumination. The lighting model applies NdotL and BRDF on top of albedo automatically. Do not pre-multiply your own lighting into albedo — use emission for self-lit additive effects instead.

PbrInput fields — access as inp.fieldName:

Field

Type

Description

uv

float2

Texture UV coordinates

worldPos

float3

World-space position of the current fragment

worldNormal

float3

World-space surface normal (normalized)

viewDir

float3

Direction from the fragment toward the camera (world-space, normalized)

localPos

float3

Object-space position of the current fragment

localNormal

float3

Object-space surface normal (normalized)

PbrOutput fields — all optional; specify only what you need:

Field

Type

Default

Description

albedo

float3

float3(0)

Base color (diffuse reflectance). Modulated by scene lighting.

alpha

float

1.0

Opacity [0, 1]

alphaCutoff

float

0.0

Alpha-test threshold. Fragments below are discarded.

metalness

float

0.0

PBR metalness [0, 1]

roughness

float

1.0

PBR roughness [0, 1]

emission

float3

float3(0)

Additive emissive color. Not affected by lighting.

emissionStrength

float

1.0

Multiplier on emission

normalMap

float3

float3(0, 0, 1)

Tangent-space normal. Decode from a texture with unpack_normal(tex2d(...)).

ao

float

1.0

Ambient occlusion [0, 1]

Unlit Shader (UnlitInputUnlitOutput)

No lighting — direct color output. Use for HUD quads, UI meshes, additive particle effects, and any surface that must be lighting-independent.

UnlitInput has the same fields as PbrInput.

UnlitOutput fields:

Field

Type

Default

Description

color

float3

float3(0)

Output color

alpha

float

1.0

Opacity [0, 1]

alphaCutoff

float

0.0

Alpha-test threshold

Global Inputs

Available directly by name in any shader, without using the input struct:

Name

Type

Description

g_Time

float

Global time in seconds since the application started

g_LightDirection

float3

Main directional light direction — pointing toward the sun, world-space, normalized

Built-in Functions

All functions are overloaded for float, float2, float3, and float4 unless noted otherwise. Operations are component-wise.

Arithmetic

Signature

Description

abs(x)

Absolute value: |x|

min(x, y)

Component-wise minimum

max(x, y)

Component-wise maximum

clamp(x, lo, hi)

Clamp each component to [lo, hi]

saturate(x)

Clamp to [0, 1]

frac(x)

Fractional part: x − floor(x)

floor(x)

Round down to the nearest integer

ceil(x)

Round up to the nearest integer

mad(a, b, c)

Multiply-add: a × b + c. Fused into a single instruction automatically.

lerp(a, b, t)

Linear interpolation: a + t × (b − a). t must be a scalar (float).

step(edge, x)

Step function: 0 if x < edge, 1 otherwise

smooth_step(lo, hi, x)

Smooth Hermite interpolation between lo and hi

one_minus(x)

1 − x

remap(x, inRange, outRange)

Remap x from inRange to outRange. inRange and outRange are float2.

Transcendentals

All component-wise:

Function

Description

sqrt(x)

Square root

pow(x, y)

Power: x^y

sin(x)

Sine (radians)

cos(x)

Cosine (radians)

atan2(y, x)

Arc-tangent of y/x

log(x)

Natural logarithm

exp(x)

Natural exponentiation: e^x

sign(x)

Sign: −1, 0, or +1

Vector Operations

Dimension-specific:

Signature

Description

dot(x, y)

Dot product (float2/3/4 × float2/3/4 → float)

cross(x, y)

Cross product (float3 × float3 → float3)

length(x)

Vector length (float2/3/4 → float)

normalize(x)

Normalize to unit length (float2/3/4 → same type)

Lighting

fresnel(power: float; worldNorm: float3; viewDir: float3): float

Schlick-style Fresnel approximation. Returns values close to 0 at the surface center and close to 1 at glancing angles. Increase power to tighten the rim. Pass inp.worldNormal and inp.viewDir directly:

let rim = fresnel(3.0, inp.worldNormal, inp.viewDir)
return PbrOutput(emission = float3(rim))

Texture Sampling

tex2d(sampler: Sampler2D; uv: float2): float4

Sample a 2D texture at the given UV coordinates. Declare the texture as a material property and pass it here:

var albedoTex : Sampler2D = Sampler2D("assets/base_color.png")

[pixel_shader]
def textured(inp : PbrInput) {
    let c = tex2d(albedoTex, inp.uv)
    return PbrOutput(albedo = c.xyz, alpha = c.w)
}

Use .rgba, .rgb, .a swizzles to extract channels from the returned float4.

Noise

noise(pos: float2): float

2D Perlin noise. Returns values in [0, 1].

noise(pos: float3): float

3D Perlin noise. Returns values in [0, 1].

Normal Map Utility

unpack_normal(sample: float4): float3

Decode a tangent-space normal from a standard RGB normal-map texture sample. Input is the float4 returned by tex2d; output is a float3 in [−1, 1]:

var normalTex : Sampler2D = Sampler2D("assets/normal.png")

[pixel_shader]
def with_normalmap(inp : PbrInput) {
    let n = unpack_normal(tex2d(normalTex, inp.uv))
    return PbrOutput(normalMap = n)
}

Language Subset

The shader DSL is a strict subset of Daslang. The [pixel_shader] macro walks the AST at compile time and rejects unsupported constructs.

Supported:

  • Arithmetic operators + - * / (unary - → Negate node)

  • Swizzle access: v.xyz, v.zyx, v.xxxx, etc.

  • Field reads on the input struct: inp.uv, inp.worldPos, etc.

  • let local declarations — type must be float/float2/float3/float4, value must be initialized

  • Float and vector literals (1.0, float3(0.2, 0.8, 1.0))

  • Integer literals — auto-promoted to float

  • Module-level var declarations → material properties

  • All built-in functions listed above

Not supported:

  • Mutable var locals — use let

  • Control flow: if, for, while — the shader graph is a pure DAG with no branches

  • Ternary operator ?:

  • Heap allocations (new)

  • Strings, arrays, tables, variants

  • Pointer types and unsafe

  • bool, int, uint locals (integer literals are auto-promoted to float)

  • Multiple [pixel_shader] functions in one module — one .shader = one entry point

See Shader and Shader Graph Compiler for details on how the compiler enforces these restrictions.