Skip to content

PostproductionRenderer: composer render targets are not multisampled, so enabling postproduction loses MSAA (aliased edges)Β #793

Description

@rihokirss

Describe the bug πŸ“

With PostproductionRenderer, the scene is drawn into the EffectComposer's ping-pong WebGLRenderTargets. The composer is created without a render target (packages/front/src/core/PostproductionRenderer/src/index.ts#L382):

this.composer = new EffectComposer(this._renderer.three);

so three.js allocates them with samples = 0. The hardware MSAA of the default framebuffer (antialias: true) therefore only applies while postproduction.enabled === false; the moment postproduction is switched on, every geometry edge is rendered without multisampling. SMAA (smaaEnabled) recovers only part of it. The visible result is that the postproduction image looks "noisier" / more jagged than the plain render, and the usual workaround people reach for β€” pixel ratio 2 β€” does not even supersample the postproduction image: setPostproductionSize() applies the DPR only in its argument-less init path, and every onResize puts the composer back to CSS size, so the composer output is merely upscaled to the backbuffer.

What I expect: enabling postproduction does not make edges worse than the plain renderer.

What actually happens: postproduction.composer.renderTarget1.samples === 0; edges are aliased.

Measurement (AR model of an office building, fixed exterior view, Chrome / AMD Radeon 8060S, 1665Γ—1123). "Hard edges" = share of neighbouring pixel pairs in a faΓ§ade region whose luma differs by more than 60 β€” a repeatable aliasing proxy; the pixel-ratio-2 frame was box-filtered 2Γ—2 to screen resolution before comparing.

hard edges render ms
postproduction on, composer samples = 0 (current) 43.5 % 12.2
postproduction on, composer samples = 4 33.7 % 12.8
postproduction off (default framebuffer, MSAA) 36.2 % 9.9
postproduction on, samples = 0, pixel ratio 2 (composer stays at CSS size, output upscaled) 33.6 % 13.2

Multisampled composer targets bring the hard-edge share to the level of the pixel-ratio-2 output β€” which, per the above, is an upscale rather than supersampling β€” for ~+0.6 ms per frame, at the composer's own resolution.

Proposed fix β€” pass a multisampled target to the composer (three β‰₯ r163 is WebGL2-only, so samples is always honoured; renderTarget2 is cloned from it and inherits samples):

const size = this._renderer.three.getSize(new THREE.Vector2());
const pixelRatio = this._renderer.three.getPixelRatio();
const target = new THREE.WebGLRenderTarget(size.x * pixelRatio, size.y * pixelRatio, {
  type: THREE.HalfFloatType, // EffectComposer's default
  samples: Math.min(4, this._renderer.three.capabilities.maxSamples),
});
this.composer = new EffectComposer(this._renderer.three, target);

The composer targets carry no depthTexture (the AO / edge-detection passes keep their own single-sample targets), so this is safe; Postproduction.setSize() already forwards to composer.setSize(), which preserves samples. Optionally expose it as postproduction.samples (default 4, 0 = current behaviour).

Workaround on the app side until then, applied once after postproduction.enabled = true:

for (const t of [pp.composer.renderTarget1, pp.composer.renderTarget2]) {
  if (t && t.samples !== 4) { t.samples = 4; t.dispose(); } // re-allocated multisampled on next use
}

Reproduction ▢️

Any PostproductionRenderer tutorial from docs.thatopen.com β€” the check is postproduction.composer.renderTarget1.samples in the console (returns 0).

Steps to reproduce πŸ”’

  1. Load a model in a world with PostproductionRenderer; leave the default COLOR style.
  2. Compare a geometry edge with postproduction.enabled = false vs true (SMAA on or off) β€” edges get visibly more jagged when enabled.
  3. postproduction.composer.renderTarget1.samples β†’ 0.
  4. Apply the workaround above β†’ edges match the non-postproduction render.

System Info πŸ’»

  Browsers: Chrome (Windows, AMD Radeon 8060S); also reproduced in headless Chromium on Linux (Mesa)
  npmPackages:
    @thatopen/components: 3.4.8
    @thatopen/components-front: 3.4.4
    @thatopen/fragments: 3.4.7
    three: 0.184.0

Used Package Manager πŸ“¦

npm

Validations βœ…

  • Read the docs.
  • Checked there isn't already an issue that reports the same bug (PostproductionRenderer missing SMAA antialiasing in style setterΒ #646 covers SMAA in the style setter, which is a different thing).
  • This is a repository issue, not a three.js issue (three honours samples; the composer is just created without them).
  • This is a concrete bug.
  • The reproduction is minimal.

Edit 2026-09-07: corrected the pixel-ratio-2 remarks. With postproduction on, the composer stays at CSS size after any resize (the DPR multiplication in setPostproductionSize() only runs in the argument-less init path), so the pixel-ratio-2 row above was an upscaled CSS-size render, not supersampling, and the earlier "4Γ— fill" remark was wrong. Pointed out by an independent reviewer on the forum; the MSAA measurements are unaffected (same resolution in every row).

Activity

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