Visitar URL original
model-viewer-effects@1.5.0 bundles outdated postprocessing@6.37.1, causing LuminanceFormat SyntaxError with current Three.js · Issue #5191 · google/model-viewer · GitHub
Skip to content

model-viewer-effects@1.5.0 bundles outdated postprocessing@6.37.1, causing LuminanceFormat SyntaxError with current Three.js #5191

Open

Description

@TheCGIMonkey

@google/model-viewer-effects@1.5.0 fails to load with recent Three.js versions:

Uncaught SyntaxError: The requested module 'three' does not provide an export named 'LuminanceFormat' (at model-viewer-effects.min.js:1:796)

Root cause: model-viewer-effects bundles postprocessing@^6.37.1 directly into its distributed file. LuminanceFormat was removed from Three.js's exports, and postprocessing fixed this exact issue in v6.37.3 ("Remove use of LuminanceFormat for r176 support", PR #702). Since model-viewer-effects bundles postprocessing at build time rather than resolving it live, updating the consuming project's Three.js version doesn't help — the outdated, broken code is baked into the shipped .min.js file itself.

To reproduce: Follow the official setup at https://modelviewer.dev/examples/postprocessing/ using three@^0.183.0 (or any Three.js version without LuminanceFormat) with @google/model-viewer-effects@1.5.0. Confirmed reproducible with zero CDN involvement — self-hosting all three libraries locally (downloaded directly from the npm registry) produces the identical error, ruling out any CDN/caching explanation.

Ask: Please rebuild and republish model-viewer-effects against postprocessing@>=6.37.3 (or later) to pick up the fix.

Activity

  1. smirnov-artur commented on Aug 8, 2026

    @smirnov-artur

    Confirming the diagnosis from the registry, and two corrections that matter for the fix.

    1. ^6.37.1 isn't the problem — the rebuild is. @google/model-viewer-effects@1.5.0 declares "postprocessing": "^6.37.1", and 1.5.0 is currently the latest tag. A fresh npm install of that caret range resolves to 6.39.4 today, so nothing is pinned wrong in the manifest; the broken code is simply frozen into the published .min.js from whenever 1.5.0 was built. A plain republish picks up the fix with no manifest change.

    2. >= 6.37.3 is not enough for three 0.183. Published peerDependencies.three ranges:

    postprocessing peer three published
    6.37.1 >= 0.157.0 < 0.175.0 2025-03-06
    6.37.3 >= 0.157.0 < 0.177.0 2025-04-27
    6.38.0 >= 0.157.0 < 0.182.0 2025-11-08
    6.39.0 >= 0.168.0 < 0.184.0 2026-03-20
    6.39.4 >= 0.168.0 < 0.186.0 2026-07-27

    6.37.3 clears the LuminanceFormat SyntaxError, but three 0.183.0 still falls outside its declared window — the first release that covers 0.183 is 6.39.0, and 6.39.4 is current. Worth aiming the ask there rather than at 6.37.3.


    A workaround that holds until the republish, for anyone landing here from a search.

    The substitution upstream made is exact and checkable: postprocessing@6.37.1 references LuminanceFormat in 5 places; in 6.37.3 those are 0, and RedFormat appears in 5. So the fix was a straight LuminanceFormat → RedFormat swap, and the constant that went missing on the three side is a plain integer — three@0.175.0/src/constants.js has export const LuminanceFormat = 1024;, and it is gone from 0.183.0.

    That means you can satisfy the bundled import without patching the minified file, as long as you shim it to RedFormat rather than to its old numeric value — a shim to 1024 silences the import error and then hands a format three no longer knows about to the texture upload.

    // three-shim.js
    export * from 'three-real'
    export { RedFormat as LuminanceFormat } from 'three-real'
    <script type="importmap">
    {
      "imports": {
        "three": "./three-shim.js",
        "three-real": "/node_modules/three/build/three.module.js",
        "three/": "/node_modules/three/"
      }
    }
    </script>

    Bundler equivalent — Vite: resolve.alias { 'three-real': 'three/build/three.module.js', three: '/three-shim.js' }; webpack: the same pair in resolve.alias. Both model-viewer and model-viewer-effects import three by bare specifier, so they resolve through the shim together and you still end up with one three instance, which is the thing you actually want to preserve here.

    Verified against the npm registry and unpkg on 2026-08-08; three has no default export, so export * re-exports the full surface.

  2. TheCGIMonkey commented on Aug 8, 2026

    @TheCGIMonkey
    Author

    Thanks for the reply, and the corrections above — the peerDependencies.three table is a genuinely useful catch, and confirms >=6.39.0 is the right ask, not 6.37.3.

    For anyone who needs this working now: skip @google/model-viewer-effects entirely and use model-viewer's own registerEffectComposer() API directly with the current, unaffected postprocessing package. Confirmed working end-to-end (bloom, SSAO, brightness/contrast) as of postprocessing@6.39.2 / three@0.183.0 / model-viewer@4.3.1.

    The one non-obvious gotcha: the documented interface (render(), setRenderer(), setMainScene(), setMainCamera(), setSize()) is incomplete. Model-viewer's actual tick loop calls beforeRender(time, delta), not render() directly — confirmed from source:

    js
    [$tick](time, delta) {
    this[$scene].effectRenderer?.beforeRender(time, delta);
    }

    Skip this and you'll get TypeError: i.beforeRender is not a function the moment rendering actually starts, even though registerEffectComposer() itself returns without error.

    Minimal working adapter:

    js
    import { EffectComposer, RenderPass, EffectPass, BloomEffect, SSAOEffect, BrightnessContrastEffect } from 'postprocessing';

    function createAdapter() {
    return {
    renderer: null, scene: null, camera: null, composer: null,
    rebuild() {
    if (!this.renderer || !this.scene || !this.camera) return;
    this.composer = new EffectComposer(this.renderer);
    this.composer.addPass(new RenderPass(this.scene, this.camera));
    const bloom = new BloomEffect({ intensity: 1, luminanceThreshold: 0.9 });
    const ssao = new SSAOEffect(this.camera, null, {});
    const bc = new BrightnessContrastEffect();
    this.composer.addPass(new EffectPass(this.camera, bloom, ssao, bc));
    },
    setRenderer(r) { this.renderer = r; this.rebuild(); },
    setMainScene(s) { this.scene = s; this.rebuild(); },
    setMainCamera(c) { this.camera = c; this.rebuild(); },
    setSize(w, h) { this.composer?.setSize(w, h); },
    render(dt) { this.composer?.render(dt); },
    beforeRender(time, delta) { this.render(delta); }
    };
    }

    modelViewerEl.registerEffectComposer(createAdapter());

    A couple of runtime property paths that differ from what I expected going in, worth noting since they cost real debugging time to pin down:

    Bloom's blur radius lives at bloomEffect.mipmapBlurPass.radius, not a top-level property
    SSAOEffect's radius is a direct top-level property (ssaoEffect.radius)
    Brightness/contrast needs its own dedicated BrightnessContrastEffect — it's not a sub-property of ToneMappingEffect

    Trade-off worth being upfront about: you lose whatever built-in catalog wires up for you (notably SMAA anti-aliasing) and have to construct/manage effects yourself. But postprocessing exposes plenty else beyond what's shown here (outline, glitch, pixelate, more), so it's a reasonable foundation if you're building custom effects anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions