Repository navigation
feat!: sky grades — authored time-of-day LUT, editor, gradients, night ambient; looks removed - #57
Merged
Merged
Conversation
…, with an editor; looks removed
Adds `SkyGrade`: keyframes on sun elevation that grade the physical sky per
region of the dome (zenith, horizon, sun side, away side, glow, below), with
whole-sky controls, a colour gradient layer (`amount` swaps hue and keeps the
physical brightness, `replace` makes the gradient the sky), a display-referred
night fill and keyed ambient light. Each keyframe bakes to one slice of a
32×64×16 half-float 3D table; the sun's place between keyframes is a uniform,
so a sun move costs two uniform writes and an edit ~1.3 ms of CPU plus the
usual cube re-bake. The operator is linear in the light, so aerial-perspective
haze and the shadow deficit are graded by the same table and silhouettes stay
seamless.
- `sky.setGrade(grade | name | definition | JSON)`, React `grade` prop
- helpers `horizonToZenith`, `gradientGrade`, `solidSky` for fixed /
gradient / solid skies
- `SkyAmbient` (`sky.createAmbient()`): a hemisphere night fill that fades in
after sunset or follows the grade's ambient keyframes
- `SkyGradePreview`: the flattened dome (physical / graded / split / grade)
for grading UIs, on any canvas or target
- `ditherOutput`: tone-map, encode and dither — fixes 8-bit contour banding in
dusk and night gradients (57 px flat runs → 2 px)
- `examples/vanilla/25-sky-grade-editor.html`: split-screen editor (scene,
timeline, dome view, keyframe inspector), undo, autosave, save/load JSON
- `scripts/verify-grade.mjs`: GPU grade vs `SkyGrade.apply` (≤ 3/255)
- docs: Sky grades guide, Grade editor and demos guide, 0.6 upgrade notes
BREAKING CHANGE: looks are removed — `setLook`, `setLookTrack`,
`registerLook`, `registerLookTrack`, `resolveLook`, `sampleLook` and the rest
of `looks.ts`, the React `look` / `lookTrack` / `lookTrackOverrides` props,
`baker.setLook` and `createHazeOutputNode({ lookUniforms })`. Use sky grades:
a look becomes `gradientGrade(stops, { amount: chroma, replace: value })` with
stop `at` in degrees (`asin(at) × 180 / π`), a look track a `SkyGrade` with a
gradient per keyframe. `baker.sky.lookUniforms.displayScale` is now
`baker.sky.displayScale`. See docs/guides/upgrading.mdx.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A designer asked to author the sky rather than tweak gradient overrides, and reported harsh banding at sunset with the looks. This replaces looks with sky grades: an artist-authored lookup table over the physical sky, keyed on the time of day, plus a split-screen editor to make one.
SkyGrade— keyframes on sun elevation. Each grades the physical sky per region of the dome (zenith, horizon, sun side, away side, sun glow, below the horizon), with whole-sky controls (exposure, saturation, temperature, tint, hue), a gradient layer (amountswaps hue and keeps physical brightness;replacemakes the gradient the sky — fixed or solid skies), a display-referred night fill, and keyed ambient light. Save/load as versioned JSON.horizonToZenith(a, b),gradientGrade(stops),solidSky(color)return an editable one-keyframe grade.sky.setGrade(...)accepts aSkyGrade, a name, a definition or saved JSON; React<Sky grade={...} />.SkyAmbient(sky.createAmbient()) — hemisphere night fill that fades in after sunset, or follows the grade's ambient keyframes. Fixes "the world goes black at night".SkyGradePreview— the flattened sky dome (physical / graded / split / grade-only) for grading UIs, on any canvas.ditherOutput— tone-map + encode + ±1 LSB triangular dither. The sunset/dusk banding was 8-bit quantisation of dark gradients; nothing in three's WebGPU output dithers.examples/vanilla/25-sky-grade-editor.html: scene on top; timeline with keyframes and a whole-day strip; dome view with zone guides (click the sky to jump to its zone); keyframe inspector with auto-key; undo/redo, autosave, Save / Load / Copy JSON, bypass (B).How it works
Each keyframe bakes on the CPU to one slice of a 32×64×16 RGBA half-float 3D table (colorize target × amount, gain, fill). The sun's place between keyframes is a JS-computed depth coordinate, so linear filtering in depth is the keyframe blend: a sun move writes two uniforms; an edit costs ~1.3 ms CPU plus the usual cube + IBL re-bake (never the atmosphere LUTs).
The operator is linear in the light —
max(M·c, 0), colorize keeping the channel average (not luminance: a saturated blue at the luminance of a bright sunset needs a ~14× blue channel, which is what clipped into bands), scalar gain, plus an affine fill /replaceterm. So aerial-perspective inscatter and the light-shaft deficit are graded by the same table, with the affine part weighted by haze opacity: silhouettes stay seamless and fully hazed geometry lands on the graded sky.Verification
pnpm test: 181 pass (grade bake/JSON/interpolation/linearity/gradients,SkyAmbient,Sky.setGradeintegration, Reactgradeprop).scripts/verify-grade.mjs(new): GPU grade vsSkyGrade.applyover 504 dome samples, at a keyframe and between two — worst 2.8 / 255.main(geometry-free regions; the haze demo's random mountains shift because three's UUIDs consumeMath.random).main: sky mesh 31.3 → 29.1 KB WGSL, haze 48.1 → 47.3 KB.ditherOutput.verify-paritypasses.verify-sky-fogfails onlyno-console-errors, from the pre-existing "update() before compile" warning on24-sky-fog.html(that page never awaitscompileAsync; unrelated).Breaking
Looks are removed (
setLook,setLookTrack,registerLook*,resolveLook,sampleLook, …, Reactlook/lookTrack/lookTrackOverrides,baker.setLook,createHazeOutputNode({ lookUniforms })).baker.sky.lookUniforms.displayScale→baker.sky.displayScale. Migration table indocs/guides/upgrading.mdx: a look isgradientGrade(stops, { amount: chroma, replace: value })with stopatin degrees. The ghibli and baked demos are converted; the looks demo and its verify scripts are gone (superseded by the editor andverify-grade).Docs
New:
docs/guides/sky-grades.mdx,docs/guides/grade-editor.mdx. Updated: upgrading (0.5 → 0.6), API (Sky, baker, React), haze, night sky, tuning (Unreal-parity knobs moved here), README, CLAUDE.md (grade architecture, dither,CanvasTargetresize trap).🤖 Generated with Claude Code