Skip to content

Effects: a renderable keeps its opacity through its effects - #1792

Merged
obiot merged 1 commit into
masterfrom
fix/opacity-effect-chain
Oct 29, 2026
Merged

obiot merged 1 commit into
masterfrom
fix/opacity-effect-chain

Conversation

@obiot

@obiot obiot commented Oct 29, 2026

Copy link
Copy Markdown
Member

Description

Fixes #1791: a renderable drawn through two or more post effects ignored its opacity on WebGL and WebGPU.

With two or more effects (or one, on a renderable that sets postEffectNeedsCapture: UIPanel, ProgressBar, Trail), the renderable is drawn opaque into a render target of its own, and the chain's last blit composites the result onto the screen. That blit drew with an opaque white tint, so the opacity the renderable was drawn at, its own and its containers', never reached the screen. setOpacity() or a fading tween changed nothing while the effects were on. With one effect drawn straight on the sprite it always worked, and Canvas never had the bug.

It was the opacity being dropped, not a shader problem: every effect already multiplies by the vertex color, which a blit set to opaque white.

  • beginPostEffect records the opacity it was begun under, one slot per nesting depth, next to the projection it already saves.
  • endPostEffect passes it to the chain's last blit only, as the blit's tint. The ping-pong blits between effects stay opaque, so the opacity is applied once. A camera's chain composites whole, as before.
  • blitEffect (and the batchers' blitTexture) take an optional alpha, 1 by default, so existing callers are unchanged.

Same change on WebGL (QuadBatcher, LitQuadBatcher) and WebGPU (WebGPUQuadBatcher).

Example: the sprite example's red monster now wears Outline + Glow while it fades, so the case stays in view.

Type of change

  • Bug fix
  • New feature
  • Documentation update
  • Performance improvement
  • Refactoring (no functional changes)

Checklist

  • I have read the Contributing Guide
  • My code follows the existing code style (pnpm lint passes): eslint and biome on the changed files, tsc on melonjs
  • I have tested my changes locally (pnpm test passes): 364 files, 9096 tests
  • I have added tests that cover my changes (if applicable)
  • The build succeeds (pnpm build)

Tests:

  • tests/effect-chain-opacity.spec.js (new, WebGL pixels): a white sprite at opacity 0.2 over black reads 51 through 0, 1, 2 and 3 effects; an opaque one stays 255; a parent's opacity multiplies in; a chain inside a chain lands at both opacities once each; "lighter" at 0.5 matches the one-effect result; one effect with postEffectNeedsCapture fades; a camera chain stays whole.
  • tests/webgpu_post_effect_flow.spec.js: the alpha each blit gets, for a sprite chain, a camera chain and nested chains.
  • Mutation-checked: dropping the final alpha, fading every blit, fading a camera's chain, or a batcher ignoring the alpha each fail tests.

Checked in a browser: in the sprite example the two-effect monster stays solid before the fix and fades with the others after it, on WebGPU and WebGL; Canvas fades as before. No console errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx

A renderable drawn through two or more post effects (or through one, on
a UIPanel, ProgressBar or Trail, which set postEffectNeedsCapture) is
drawn opaque into a render target of its own, and the chain's last blit
composites the result. That blit drew with an opaque white tint, so the
opacity the renderable was drawn at, its own and its containers', never
reached the screen: setOpacity() or a fade did nothing while the
effects were on. Fixes #1791.

beginPostEffect records the opacity it was begun under, per nesting
depth, and endPostEffect hands it to the chain's last blit only, as the
blit's tint. The blits between effects stay opaque, and a camera's
chain composites whole. Same on WebGL and WebGPU: blitEffect and the
batchers' blitTexture take an optional alpha (1 by default).

The sprite example's red monster now fades through Outline + Glow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeWCkETjW35ZbXDRswV1hx
Copilot AI balanced review requested due to automatic review settings October 29, 2026 11:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@obiot
obiot merged commit d99673f into master Oct 29, 2026
6 checks passed
@obiot
obiot deleted the fix/opacity-effect-chain branch October 29, 2026 11:19
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.

Renderable: opacity is ignored when it draws through two or more post effects

2 participants