Skip to content

fix(rendering): let Color carry HDR values above 1 - #702

Closed
stormmuller wants to merge 1 commit into
devfrom
ccr-56fda202-uajza2-hdr-colors
Closed

stormmuller wants to merge 1 commit into
devfrom
ccr-56fda202-uajza2-hdr-colors

Conversation

@stormmuller

Copy link
Copy Markdown
Member

Summary

Color clamped r, g, b and a to [0, 1] in its constructor. Forge has HDR render targets, bloom and tone mapping, but an HDR tint, clear color or emissive color (e.g. new Color(4, 2, 0.5)) was silently capped at white. The demo game worked around this: it darkens its UI buttons at rest "since forge clamps tints to 1", because otherwise hover can't make them any brighter.

The constructor was the only place values were capped. Everything downstream already carries plain floats with no cap: sprite tint instance data, text effect data, material uniforms, and RenderContext.clear.

  • r, g, b are clamped only at 0 from below; values above 1 are kept ("overbright"). Unity, Godot and Bevy all keep colors unbounded too.
    • ldr targets and the canvas saturate overbright values when they're written; hdr targets keep them.
    • Negative values are still clamped to 0: negative rgb would feed Reinhard's c/(c+1), which divides by zero at -1.
  • Alpha stays clamped to [0, 1]. Alpha above 1 gives a negative weight in the straight-alpha SRC_ALPHA, ONE_MINUS_SRC_ALPHA blend.
  • NaN and Infinity now throw a descriptive error instead of being hidden by the clamp.
  • toRGBAString() caps each channel at 255, since CSS can't express HDR.
  • lerpColor in the UI transition system: back and elastic easings that overshoot can now show a brief overbright tint, which is what an overshoot easing means. There's a comment on this.

solution-reviewer verdict: approve. It also asked for:

  • the non-finite check;
  • the reasons for each clamp written in the JSDoc;
  • bloom.md and hdr-rendering.md updated to cover HDR tints and clear colors.

All three are done.

Related issue(s)

Once released, the demo game can drop its button-dimming workaround in src/ui/create-menu-button.ts.

Verification checklist

  • npm run check-types passes with 0 errors
  • npm test passes. New tests cover:
    • overbright values kept, including in toFloat32Array;
    • negative channels clamped to 0;
    • alpha clamped at both ends;
    • non-finite values throwing;
    • toRGBAString capping at 255.
  • npm run lint passes with 0 errors
  • npm run cspell passes with 0 errors ("overbright" added to the project words)
  • npm run check-exports passes
  • No public API added
  • Docs: rendering/bloom.md (HDR tints as a way to make something glow) and rendering/hdr-rendering.md (new "Brighter-than-white colors" section)
  • No demo uses colors above 1, and no API changed, so the demos are unaffected

Changelog

  • A bullet has been added under ## [Unreleased] in CHANGELOG.md (Fixed, including what consumers relying on the clamp need to do)

🤖 Generated with Claude Code

https://claude.ai/code/session_01FLZGR5YxNP6W4AW3coFP9h


Generated by Claude Code

Color clamped r, g and b to [0, 1], so HDR tints and clear colors were
silently capped at white before reaching an hdr render target. RGB is now
only clamped to 0 from below; alpha stays in [0, 1]; non-finite channels
throw; toRGBAString clamps its output to 255.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLZGR5YxNP6W4AW3coFP9h
@stormmuller stormmuller closed this Oct 7, 2026

Copy link
Copy Markdown
Member Author

Closing: #710 ("let colors go brighter than white") already landed on dev. Color values above 1 now reach bloom and tone mapping on an HDR render target.


Generated by Claude Code

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.

2 participants