melonJS
    Preparing search index...

    Class ToneMappingEffect

    A shader effect that tone maps what it is applied to: values are lifted by exposure and brought back into display range by a curve, instead of clipping flat at white.

    Put it on the CAMERA, and put it LAST — after BloomEffect, so the light bloom spreads is mapped along with everything else rather than clipping where the two overlap.

    Three curves, chosen once at construction:

    mode character
    "aces" filmic: more midtone contrast, long shoulder. The default. Clamps internally, so it cannot feed hdrOutput
    "reinhard" gentle and neutral, desaturates highlights as they climb
    "exponential" channels saturate separately, so a hot core shifts toward white through its own hue

    Without hdr: true it is a grade, not a colour-managed pipeline. The default render targets are RGBA8, so the frame reaching this is already clamped to [0, 1] and exposure is what gives the curve something to work on. With the hdr application setting on, the targets are half-float and the values arriving are genuinely above 1, which is what the curve is for. Either way it does not convert linear to sRGB, because nothing linearized it.

    // the usual pairing: spread the bright parts, then map the result
    game.viewport.addPostEffect(new BloomEffect(renderer));
    game.viewport.addPostEffect(new ToneMappingEffect(renderer, { exposure: 1.6 }));
    // a hot core that shifts through its own colour on the way to white
    const grade = new ToneMappingEffect(renderer, {
    mode: "exponential",
    exposure: 2.2,
    });
    game.viewport.addPostEffect(grade);
    // and dip it for a moment when the player is hit
    grade.setExposure(0.7);

    Hierarchy (View Summary)

    Index
    • Parameters

      • renderer: Renderer

        the current renderer instance

      • Optionaloptions: { exposure?: number; mode?: string; white?: number } = {}

        effect options

        • Optionalexposure?: number

          multiplier applied before the curve; above 1 lifts the image into the curve's shoulder

        • Optionalmode?: string

          curve: "aces", "reinhard" or "exponential"

        • Optionalwhite?: number

          the post-exposure value that should come out as display white. Omitted, the curve keeps its own shoulder and nothing reaches 1 exactly

      Returns ToneMappingEffect

      on an unknown mode, or a non-finite exposure or white

    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).

    exposure: number | undefined
    mode: string

    which curve this instance baked, for reading back

    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
    
    white: number

    The post-exposure value that comes out as display white, or 0 when the curve is left with its own shoulder.

    • 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.addPostEffect(loader.getShader("flash"));
      // the boss needs its own intensity — clone a private, caller-owned copy
      const bossFlash = loader.getShader("flash").clone();
      boss.addPostEffect(bossFlash);
      bossFlash.setUniform("uIntensity", 0.9);
    • set the exposure. Cheap, and safe to drive per frame: it is a uniform, so changing it does not rebuild the program the way mode would.

      Parameters

      • exposure: number

        multiplier applied before the curve; 0 maps everything to black

      Returns void

      if exposure is not a finite number

    • 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: HTMLCanvasElement | HTMLImageElement | OffscreenCanvas | ImageBitmap | 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.addPostEffect(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.addPostEffect(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: number | boolean | object | number[] | Float32Array<ArrayBufferLike>

        the value to assign to that uniform. Scalars (float, int, bool) take a number or a boolean; vectors and matrices take an array, a Float32Array, or any object exposing toArray() — which is every Vector2d, Vector3d, Color and Matrix3d.

      Returns void

      // a scalar the body declares as `uniform float uStrength;`
      fx.setUniform("uStrength", 0.5);
      // a vec3 — an array, or anything with toArray()
      fx.setUniform("uTint", [1.0, 0.82, 0.55]);
      fx.setUniform("uOrigin", new me.Vector2d(0.5, 0.5));
    • Set the white point: the value that should come out as display white.

      This is the knob a "brightness" or "peak" slider in a settings screen drives. Without it a tone curve only approaches 1 asymptotically, so nothing in the frame is ever quite white and a player who wants a brighter picture can only raise exposure, which lifts the midtones with it. A white point moves where the shoulder lands instead.

      Implemented as a scale rather than a reshaped curve: the value is run through the same curve on the CPU and the shader multiplies by its reciprocal, so white maps to exactly 1 and everything under it keeps the curve's shape. Values above it clip, which is what "white point" means.

      Parameters

      • white: number

        the value to map to white; 0 or less restores the curve's own shoulder

      Returns void

      if white is not a finite number