melonJS
    Preparing search index...

    Class ShaderEffect

    A simplified shader class for applying custom fragment effects to renderables. Only requires a fragment apply() function — the vertex shader, uniforms, and texture sampling boilerplate are handled automatically.

    An effect body is written in the active renderer's shading language: GLSL on the WebGL renderer, WGSL on the WebGPU renderer. Pass a plain string for a GLSL-only effect (the historical form), or one body per language for an effect that runs on both backends:

    new ShaderEffect(renderer, { glsl: glslBody, wgsl: wgslBody });
    

    The renderer compiles the body matching its Renderer#shaderLanguage. When no matching body exists — a GLSL-only effect on the WebGPU renderer, any effect on the Canvas renderer — the effect warns once and stays disabled (enabled === false, every method a safe no-op): the scene renders without the effect, it never breaks.

    A WGSL body mirrors the GLSL one — declarations plus an apply function, compiled verbatim inside engine boilerplate:

    • fn apply(color : vec4f, uv : vec2f) -> vec4f — required; receives the sampled, tinted pixel and its UV, returns the modified color.
    • Uniforms are the members of ONE struct bound as @group(3) @binding(0) var<uniform> fx : MyUniforms; — member names are the ShaderEffect#setUniform names, so a dual-language effect uses the same uniform names in both bodies and one setUniform call serves both. Supported member types: f32, i32, u32, vec2f, vec3f, vec4f, mat3x3f, mat4x4f, array<vec4f, N>.
    • Extra ShaderEffect#setTexture samplers are texture/sampler pairs at explicit consecutive group-3 bindings (from 1): @group(3) @binding(1) var uNoise : texture_2d<f32>; @group(3) @binding(2) var uNoiseSampler : sampler;
    • The source texture is available as uTexture with uSampler (textureSample(uTexture, uSampler, uv) — the WGSL spelling of GLSL's texture2D(uSampler, uv)), and the interpolated tint as vColor, under the same names as the GLSL side.
    • The shader builtins keep their names: screen_uv, noise_uv, and screen_texture — sampled through screen_sampler (clamped) or screen_sampler_repeat (wrapping), replacing the GLSL : screen_texture(repeat) annotation.
    • Porting note: a texture sampled after a non-uniform return or inside a varying branch must use textureSampleLevel(uTexture, uSampler, uv, 0.0) (a WGSL uniform-control-flow rule; identical output for sprite textures).

    The vertical orientation of apply()'s UV space depends on the draw path: sampling the sprite directly, uv.y grows downward, but the WebGL multi-effect (pooled) path composites through capture FBOs whose rows are bottom-up — there uv.y grows upward. A body that offsets its sampling coordinate vertically (a drop shadow, a directional smear) would render mirrored on that path. Declare a float uUVYDir uniform (WGSL: uUVYDir : f32 in the uniform struct) and multiply vertical UV offsets by it: the renderer feeds +1 where uv.y grows downward — including every WebGPU path — and -1 on the WebGL pooled path, so "down" stays down everywhere. Initialize it to 1.0 with setUniform; bodies that don't declare it are unaffected. The built-in DropShadowEffect is the reference use.

    // one effect, both backends: dual-language body
    mySprite.shader = new ShaderEffect(renderer, {
    glsl: `
    uniform float uStrength;
    vec4 apply(vec4 color, vec2 uv) {
    return vec4(color.rgb * uStrength, color.a);
    }
    `,
    wgsl: `
    struct Fx { uStrength : f32, };
    @group(3) @binding(0) var<uniform> fx : Fx;
    fn apply(color : vec4f, uv : vec2f) -> vec4f {
    return vec4f(color.rgb * fx.uStrength, color.a);
    }
    `,
    });
    mySprite.shader.setUniform("uStrength", 0.5); // sets either backend
    // create a grayscale effect
    mySprite.shader = new ShaderEffect(renderer, `
    vec4 apply(vec4 color, vec2 uv) {
    float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
    return vec4(vec3(gray), color.a);
    }
    `);
    // create an effect with a custom uniform
    const pulse = new ShaderEffect(renderer, `
    uniform float uTime;
    vec4 apply(vec4 color, vec2 uv) {
    float brightness = 0.8 + 0.2 * sin(uTime * 3.0);
    return vec4(color.rgb * brightness, color.a);
    }
    `);
    mySprite.shader = pulse;
    // update the uniform each frame
    pulse.setUniform("uTime", time);
    // Shader builtins — no JS plumbing, no UV math:
    // `: screen_texture` keeps the sampler filled with everything drawn so
    // far (a back-buffer copy; one screen copy per draw of the effect),
    // `screen_uv` is this fragment's 0..1 screen position, and `noise_uv`
    // runs 0..1 across the sprite regardless of its atlas frame.
    const water = new ShaderEffect(renderer, `
    uniform sampler2D uNoise;
    uniform sampler2D screenTex : screen_texture;
    uniform float uTime;
    vec4 apply(vec4 color, vec2 uv) {
    vec2 flow = texture2D(uNoise, noise_uv + uTime * 0.25).rg;
    vec4 refracted = texture2D(screenTex, screen_uv + flow * 0.005);
    return refracted * texture2D(uSampler, uv + flow * 0.005);
    }
    `);
    water.setTexture("uNoise", noiseTexture.getTexture(), "repeat");

    Hierarchy (View Summary)

    Index
    • Parameters

      • renderer: any

        the current renderer instance

      • body: string | { glsl?: string; wgsl?: string }

        the effect body: a GLSL string (containing a vec4 apply(vec4 color, vec2 uv) function — unchanged from previous versions), or an object carrying one body per shading language (glsl and/or wgsl, the WGSL body defining fn apply(color : vec4f, uv : vec2f) -> vec4f). The renderer picks the body matching its Renderer#shaderLanguage; when no matching body exists the effect warns once and stays disabled (enabled === false), exactly like the Canvas renderer.

      • Optionalprecision: string

        float precision ('lowp', 'mediump' or 'highp'), GLSL only

      Returns ShaderEffect

    _enabledBeforeSuspend: boolean | undefined
    _textureOverflowWarned: boolean | undefined
    _uvYDir: any
    destroyed: boolean = false

    true once destroy has been called. Distinct from enabled — which also toggles transiently across a context lost / restored cycle — to give callers a stable signal for "this effect has been explicitly released."

    enabled: boolean = false

    whether this effect is active (false in Canvas mode, false after destroy, and false while the WebGL context is suspended between an ONCONTEXT_LOST and the matching ONCONTEXT_RESTORED event).

    shared: boolean = false

    When true, a renderable will NOT auto-destroy this effect when it is removed from its postEffects (via the shader setter, Renderable#removePostEffect, Renderable#clearPostEffects) or when the renderable itself is destroyed. Set this on an effect shared across several renderables so one of them going away doesn't free the GL program still used by the others — you then own its lifecycle and call destroy yourself.

    false
    
    • Create an independent copy of this effect, compiled as its own GL program. Use it when several renderables need the same effect with different uniform values — a single instance has a single set of uniforms, shared by everything it is assigned to.

      The clone copies the recipe: the fragment source, float precision, every uniform value set so far, and any extra textures bound via setTexture (the clone uploads and owns its own GL copies). It does NOT copy ownership or lifecycle state — in particular the clone's shared flag is always reset to false, even when cloning a shared shader (such as one returned by loader.getShader()): the clone is caller-owned and will be auto-destroyed by the renderable it is assigned to, exactly like a hand-constructed effect. Set shared = true on the clone yourself if you intend to reuse it across several renderables.

      Returns ShaderEffect

      a new, caller-owned effect (shared === false)

      // the loader's shader is ONE shared program — one uniform state for all
      sprite.shader = loader.getShader("flash");
      // the boss needs its own intensity — clone a private, caller-owned copy
      boss.shader = loader.getShader("flash").clone();
      boss.shader.setUniform("uIntensity", 0.9);
    • destroy this shader effect. Idempotent — calling destroy twice is safe. Unsubscribes from the renderer's context-lost / restored events so a destroyed effect is not auto-reactivated.

      Returns void

    • Bind an extra texture to a named sampler2D uniform in this shader, so a custom effect can read a second texture — a noise map, mask, gradient, flow/lookup table — besides the sprite/target it post-processes (uSampler). The engine uploads, caches, and re-binds it to a reserved texture unit each time the effect draws, and points the sampler uniform at it — no raw WebGL texture-unit juggling.

      Declare the sampler in your fragment (uniform sampler2D <name>;) and pass that name here. Any engine texture works — a Texture2d asset (NoiseTexture2d, TextureAtlas, …) can be passed directly, or a raw drawable source. No-op in Canvas mode.

      Parameters

      • name: string

        the sampler2D uniform name declared in the fragment

      • image: ImageBitmap | HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | Texture2d

        the texture: an engine texture asset, or a raw drawable source

      • Optionalrepeat: "repeat" | "no-repeat" | "repeat-x" | "repeat-y" = "no-repeat"

        wrap mode; use "repeat" for a tiled/scrolled texture

      Returns ShaderEffect

      this effect for chaining

      // "water": distort the sprite by a static noise texture scrolled over time
      const noise = new me.NoiseTexture2d({ width: 256, height: 256, seamless: true });
      const water = new me.ShaderEffect(renderer, `
      uniform sampler2D uNoise;
      uniform float uTime;
      vec4 apply(vec4 color, vec2 uv) {
      vec2 flow = texture2D(uNoise, uv + uTime * 0.03).rg - 0.5;
      return texture2D(uSampler, uv + flow * 0.02);
      }`);
      water.setTexture("uNoise", noise, "repeat");
      waterSprite.shader = water;
      // each frame, in your Stage's update(dt):
      water.setTime(me.timer.getTime() / 1000);
    • Set the shader's uTime uniform (elapsed time, in seconds). A convenience over setUniform("uTime", ...); call it once per frame from your update loop to animate a shader that declares uniform float uTime (e.g. scrolling a static noise texture's UVs, pulsing, waving). Drive it with whatever clock you like — real time, a paused/scaled/scrubbed one.

      No-op if the shader does not declare a uTime uniform (nothing to update), or in Canvas mode. The engine does NOT call this for you — animation is opt-in, exactly like re-baking a NoiseTexture2d with update(dt).

      Parameters

      • seconds: number

        elapsed time in seconds

      Returns ShaderEffect

      this effect for chaining

      // a shader that scrolls a static seamless noise texture over time
      const flow = new me.ShaderEffect(renderer, `
      uniform float uTime;
      vec4 apply(vec4 color, vec2 uv) {
      return texture2D(uSampler, uv + vec2(uTime * 0.05, 0.0));
      }`);
      mySprite.shader = flow;
      // then in your Stage's update(dt):
      flow.setTime(me.timer.getTime() / 1000);
    • Set the uniform to the given value

      Parameters

      • name: string

        the uniform name

      • value: object | Float32Array<ArrayBufferLike>

        the value to assign to that uniform

      Returns void