Skip to content

fix: ensure opacity dither fully hides at alpha 0 and fully shows at alpha 1#8767

Merged
mvaligursky merged 1 commit into
playcanvas:mainfrom
lucaheft:fix/opacity-dithering
May 22, 2026
Merged

fix: ensure opacity dither fully hides at alpha 0 and fully shows at alpha 1#8767
mvaligursky merged 1 commit into
playcanvas:mainfrom
lucaheft:fix/opacity-dithering

Conversation

@lucaheft
Copy link
Copy Markdown
Contributor

Summary

  • Fix opacity dithering so alpha = 0 produces a fully invisible surface and alpha = 1 produces a fully opaque surface, regardless of dither mode.
  • Previously, dither thresholds in [0, 1] (Bayer can hit 0, blue-noise textures can hit 0 and 1, IGN noise can hit 0) caused alpha < noise to leave stray pixels visible at alpha = 0 (and could discard pixels at alpha = 1 for blue-noise).
  • Adds explicit boundary early-outs in opacityDither so the contract is enforced at the source instead of relying on the noise range.
  • Applied to both the GLSL and WGSL chunks (src/scene/shader-lib/glsl/chunks/standard/frag/opacity-dither.js, src/scene/shader-lib/wgsl/chunks/standard/frag/opacity-dither.js).

Before:
image

After:
image

Test plan

  • Open the dithered-transparency example and confirm a material with opacity intensity 0 renders fully invisible (no scattered dots) across all three dither modes: BAYER8, BLUENOISE, IGNNOISE.
  • Confirm opacity intensity 1 renders fully opaque with no discarded pixels in all three modes.
  • Confirm intermediate values still dither correctly (smooth fade-in/out as intensity sweeps 0 → 1).
  • Verify on both WebGL2 and WebGPU backends.

Fixes #

Checklist

  • I have read the contributing guidelines
  • My code follows the project's coding standards
  • This PR focuses on a single change

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens the contract of the standard shader’s opacity dithering so the boundary cases behave deterministically across all dither sources (Bayer / blue-noise / IGN): alpha = 0 always fully discards and alpha = 1 never discards.

Changes:

  • Add early-out discard when alpha <= 0.0 to prevent stray visible pixels at zero opacity.
  • Add early-out return when alpha >= 1.0 to prevent any accidental discards at full opacity.
  • Apply the same boundary behavior to both WGSL (WebGPU) and GLSL (WebGL) opacity dither chunks.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/scene/shader-lib/wgsl/chunks/standard/frag/opacity-dither.js Adds explicit alpha boundary early-outs in WGSL opacityDither to enforce fully hidden/fully opaque behavior.
src/scene/shader-lib/glsl/chunks/standard/frag/opacity-dither.js Mirrors the same alpha boundary early-outs in GLSL opacityDither for consistent behavior across backends.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mvaligursky mvaligursky merged commit e0e574d into playcanvas:main May 22, 2026
4 of 6 checks passed
@mvaligursky
Copy link
Copy Markdown
Contributor

Thanks!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants