Skip to content

Etna 0.9.0: World and Auto Resolution, own anti-aliasing, entities lit like the world, no code from other packs - #5

Draft
Atomsk-debug wants to merge 34 commits into
mainfrom
claude/project-thread-5pxz6a
Draft

Atomsk-debug wants to merge 34 commits into
mainfrom
claude/project-thread-5pxz6a

Conversation

@Atomsk-debug

@Atomsk-debug Atomsk-debug commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Before: Etna always shaded every screen pixel. Its temporal anti-aliasing and the light shafts' 2x2 order were ported from Photon Shaders, and the jar carried the Photon Shaders License, which blocks Modrinth and CurseForge. Mobs, chests and the hand kept vanilla's flat lighting, so they didn't match the shaded world around them.

After: a new World Resolution setting (50 to 100%) draws the world at fewer pixels, and Etna's own anti-aliasing builds the full-size picture back up. Auto Resolution (off by default) picks that size by itself to hold a chosen frame rate. The anti-aliasing is new, and it is steadier and sharper than the old one even at full size. Mobs, animals, chests, signs, item frames and the first-person hand now take Etna's sun, sky and block light and sit in its shadows; this is the new Entity Lighting toggle on the Lighting page. No code from other shader packs is left, so LICENSE-PHOTON.txt is gone and the licence is plain MIT.

This branch includes PR #3 (0.8.1, merged in up to 10f1337) because the licence cleanup sits on top of that branch's own shadow warp and Fresnel. Merge #3 first; this PR's diff then shrinks to its own changes. Not tested in game yet, so don't merge until Cosimo has tested it: a merge publishes a release.

How

  • Render scale. While the level is drawn, GameRenderer.mainRenderTarget() hands out a smaller target of Etna's (EtnaScale), so the whole level is drawn into it.

    • The scaled world chain (etna_world_scaled) runs on that target.
    • Its anti-aliasing history stays screen-sized, and its last pass writes Minecraft's own picture.
    • The hand, the final chain and the HUD stay at full size. The final chain reads the level's depth through EtnaScale.LevelDepth.
    • Main's depth is cleared on scaled frames, so the bloom's hand mask sees only the hand.
    • Texture mip selection follows the screen's pixels.
  • Auto Resolution (EtnaAutoScale). It picks the scale from fixed steps (100% down to 50%) by the GPU's own time per frame, up to the end of Etna's final pass, timed on one frame in eight. Using GPU time means VSync and frame limiters don't hide spare room.

    • Two timed frames over budget step down; one far over budget steps down two at once.
    • It steps up only after two seconds in which every timed frame would still fit with headroom at the bigger size. Only the level and world passes are counted as growing.
    • It never goes below World Resolution (70% when that is at 100%).
    • It is off during the benchmark and without GPU timestamps.
    • Each step change restarts the TAA history.
  • Anti-aliasing (taa.fsh), clean-room from the cited papers.

    • Each frame is rebuilt from 3x3 level pixels with Gaussian weights that account for the jitter. The history's weight is kept in alpha.
    • Reprojection uses the nearest depth.
    • The history is read with Catmull-Rom from five bilinear reads.
    • The history is clipped to a YCoCg variance box, along the line to this frame's colour.
    • Frames are blended with 1 / (1 + luma) weights, so bright glints don't linger.
  • Jitter. 8 Halton steps at full size; at smaller sizes, 8 / scale² rounded up to a power of two. The terrain now reads the current jitter from its block instead of keeping its own copy of the table.

  • Entity Lighting.

    • A mixin on the render type's draw swaps vanilla entity and item pipelines for Etna copies: etna:lit/... while the level is drawn, etna:hand/... for the first-person hand. It also binds Etna's light block and shadow maps next to the default uniforms.
    • The copies compile two per frame as the level starts, so a new mob or the hand never stalls a frame. After that, each draw is one identity-map lookup with no allocation.
    • GUI drawing and the screen effects (the totem) are never swapped, and Etna off or the toggle off draws exactly vanilla.
    • The lighting functions moved out of terrain.fsh into include/etna_light.glsl, shared by terrain, entities and items, so a mob in shade darkens like the grass next to it.
    • Order-independent translucent pipelines, and fog for entities under water, stay vanilla for now.
  • Benchmark. Two new steps: "shadow map fade" holds a fade at its middle using PR Etna 0.8.1: solid water reflections, thin shoreline foam, shadows that soften with distance #3's hook, and "entity lighting off". That makes 23 steps, about 150 seconds.

  • Post target sizes. Fixed sizes are now written as REFERENCE_SIZED + size. Before, a size that happened to equal the chain's own size was taken for "no size", which made the half-size passes four times too big at 1280x720 and 75%.

  • Robustness from review.

    • The frame state resets at render HEAD and renderLevel RETURN.
    • The smaller target uses main's own formats and is freed without a level.
    • A failed scaled pass clears the screen instead of compositing over last frame's picture.
    • The TAA's centre pixel is clamped at the screen edges.
    • validate_shaders.py rejects option-like arguments.
  • Licence cleanup. Removed:

    • LICENSE-PHOTON.txt and its jar entry;
    • the Photon rows in CREDITS and the Modrinth note;
    • the LICENSE footer;
    • the Photon licence in fabric.mod.json.

    The techniques are credited in CREDITS.md. Version 0.9.0.

Offline TAA test bench (64 frames against a 16x supersampled reference), old vs new at full size:

Measure Old New
Still error 0.0138 0.0101
Flicker 0.0081 0.0053
Edge sharpness (of the reference) 0.69 0.82
Ghosting 0.057 0.054

At 75% it is still steadier than the old one at full size; in motion it is slightly softer.

🤖 Generated with Claude Code

https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx

Reflections: fixed steps (24 + 6 halvings, no per-pixel noise, one fixed
pixel per 2x2 block), a hit only where the ray runs into a surface rather
than going behind something in front of it, misses written with their
distance so the upsampler averages them (0.8.0 skipped them, so one lone
hit grew into a speck), a 16-texel steady upsample, and the ray following
a quarter of the swell's slope. The sky's reflection that is taken out
keeps the full swell, as the water shader drew it.

Foam: no foam over shallow floors and none on water flowing down a slope
(new etnaWaterLevel flag from the unsnapped face normal); thinner, fainter
line (0.2 blocks, 0.7) broken by fine bubbles, pale non-glowing colour.

Version 0.8.1, changelog.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
Contact hardening (the PCSS idea): 8 gathers look for casters round the
point; the blur radius is the softness plus 0.025 x (softness - 0.25) of
their average distance along the light, capped at 1.2 blocks / 32 texels.
The near part keeps the exact area-weighted box, the wide part a
12-point bilinear-PCF Vogel disk, blended between. No casters found:
lit at once. At the lowest softness all of it is skipped.

Low sun: shadows fade up to 35% lighter below ~20 degrees.

From a review of the shadow code: receivers beyond the shadow casters'
chunk box (16 * ceil((distance + 14) / 16) blocks along world x/z) fade
to lit instead of ending at a hard line; points beyond the map's far
depth plane count as lit (terrain filter, unfiltered test, leaves,
shafts) instead of shadowed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
The shadows that soften with distance read the map up to 1.2 blocks round
a point and compare it with the surface's plane over that disc, but the
shadow pass still dropped casters by the old filter's reach (softness + 1
texels). Near the camera that is a few hundredths of a block, so a caster
just outside the view, or a little below everything on screen, could be
left out of the map while a pixel's blur still read where it would be:
part of a soft edge missing at the side of the screen, coming back as the
camera turns. The view test's margins (sideways and behind) and the
receiver floor now take the wider reach into account; nothing changes at
the lowest softness, where the wider blur is off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
Contact hardening no longer turns its reads every frame or gives up where
a few reads find nothing: the box filter now also reports blockers, six
fixed bilinear search reads cover the ring beyond it, the disc (16 reads,
at most 16 texels) is skipped where everything read is blocked or open,
and only discs wider than about eight texels turn, by more the wider they
are. In the offline bench the flicker after TAA is 35-65 % below the
tested build's and below 0.8.0's with a low sun.

The shadow map warp and the water's fresnelDielectric are rewritten from
first principles and the Cook-Torrance form, without Photon's code: the
warp's distance n = sqrt(1 - (1-x^2)(1-y^2)) fills the whole map and keeps
far texels two to five times finer towards the corners. EtnaShadows'
margins follow its bounds. CREDITS and README list them as Etna's own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
- World Resolution (50 to 100%): the level is drawn into a smaller target of
  Etna's (GameRenderer.mainRenderTarget() hands it out during the level), the
  scaled world chain runs on it, and Etna's new anti-aliasing rebuilds the
  full-size picture into Minecraft's own. Hand, screen effects, bloom and the
  HUD stay at full size. Texture detail follows the screen's pixels.
- Etna's own temporal anti-aliasing replaces the Photon port: this frame
  rebuilt from the 3x3 level pixels with jitter-aware Gaussian weights, the
  history's weight kept in alpha, nearest-depth reprojection, Catmull-Rom
  history, YCoCg variance clipping along the line to this frame's colour,
  and a 1 / (1 + luma) weighted blend.
- Jitter: 8 Halton steps at full size, 8 / scale^2 rounded up to a power of
  two below it. The terrain reads the current jitter from its block.
- Light shafts: the 2x2 order comes from Bayer's ordered-dither matrix.
- Fixed-size post targets are written as an explicit offset size, so a size
  equal to the chain's own is never mistaken for "no size".
- Benchmark step "world resolution 75%"; docs and translations.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
With the anti-aliasing and the shafts' order rewritten here and 0.8.1's own
shadow warp and water Fresnel, nothing ported from Photon Shaders is left:
LICENSE-PHOTON.txt and its jar entry go, the CREDITS rows and the Modrinth
note go (the techniques are credited instead), LICENSE is plain MIT, and the
README, fabric.mod.json and the changelog say so. Version 0.9.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
@Atomsk-debug
Atomsk-debug force-pushed the claude/project-thread-5pxz6a branch from f766f94 to 26034ac Compare October 11, 2026 02:06
@Atomsk-debug Atomsk-debug changed the title Render scale with Etna's own anti-aliasing and upscaler (WIP) Etna 0.9.0: World Resolution, Etna's own anti-aliasing, no code from other packs Oct 11, 2026
claude added 12 commits October 11, 2026 02:14
The water's top took the sharp shadow of its own point as the body's
light, while the bottom seen through it had its own shadow, shifted by
the depth; the two copies crossed each other out. The body's sun share is
now the shadow at four points down the refracted view, placed at equal
shares of the light scattered back above the bottom (green extinction
down along the sun and back up along the view), each a bilinear 2x2
lookup (new shadowSoftAt). Clear shallow water shows the bottom's
shadow; murky or deep water a soft shadow in the water.

Water whose bottom the shadow map can't show (wide shade, a roof) went
back to the plain, far more opaque look in the shadow's shape. A fourth
ring at 8 blocks looks for the bottom further round, and where none is
found the depth model keeps going with 4 blocks of water.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
A ray that only just meets or misses an edge went either way from frame
to frame (the jittered picture's edges move by up to half a pixel, the
waves move on), and a whole half-resolution texel, spread over its
neighbours by the world pass, changed at once: too smooth for the TAA's
neighbourhood clip to tell from a real change, so the reflection
flickered.

The reflections pass now traces from the pixel's unjittered centre and
reads depth and colour at the jittered spot that holds each point, and
with anti-aliasing on it blends each texel with its own history
(SSR_BLEND 0.3, more of this frame while the camera moves), reprojected
by the water point's position with PrevViewProj and kept only where last
frame held water at that distance. The history is two persistent half-
size targets, water_ssr0/1, swapped with the TAA history
(PostChainMixin); the plain chain binds the shafts as a placeholder and
never reads it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
…ct-thread-5pxz6a

# Conflicts:
#	src/main/resources/assets/etna/post_effect/etna_world.json
#	tools/gen_post_effects.py
The sun and sky colours, the direct sun's strength, the sky light on a
face, the soft terminator and the torch colour move unchanged from
terrain.fsh into an include, so entities and items can be lit by the
same functions. The terrain includes it where they were and draws
exactly as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
Etna's own entity and item shaders, made from vanilla's: everything is
vanilla's but the light, which is the terrain's (sun, sky, shadow map,
clouds, torch colour, cave light, haze), shared through etna_light.glsl.
The first-person hand is lit as if it stood at the camera. The
validator builds them with the defines of the pipelines Etna will copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
Vanilla's prepared render types draw every entity, block entity and
item through one private method. A new optional mixin swaps its
pipeline for Etna's copy (entity or item shader, Etna's bind group)
while Etna lights the draw, and binds the terrain block, shadow maps and
cloud texture with the default uniforms; the terrain's binding moves to
EtnaShadows.bindLight for both. Only the level's draws and the
first-person hand's are swapped, never the menus', and only classic
pipelines (no Improved Transparency stages).

New Entity Lighting switch on the Lighting page (default on), saved in
etna.properties, and an "entity lighting off" benchmark step.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
The benchmark holds the sun, so no sun-step fade happens during a run and
its table never shows what the fades cost in play (every sunlit pixel
filtering both maps for part of the time). benchmarkFade, set and put back
by EtnaBenchmark, keeps a fade running along the held direction, held at
its middle once the second map is drawn: both maps drawn in turn and read
half and half, as in a real fade. Off by default; nothing else changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
The benchmark holds the sun, so the fade between shadow maps as the sun
moves never ran in it. "shadow map fade" turns on EtnaShadows.benchmarkFade
for its step, so the table shows what sunrise and sunset add.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
@Atomsk-debug Atomsk-debug changed the title Etna 0.9.0: World Resolution, Etna's own anti-aliasing, no code from other packs Etna 0.9.0: World Resolution, own anti-aliasing, entities lit like the world, no code from other packs Oct 11, 2026
Far water seen at a grazing angle changes its distance from the camera by
several blocks a pixel, more than the history check's 0.3 + 3 % allowed,
so the reflections pass threw its history away exactly where the shore's
reflection flickers most (from about 60 blocks out, sooner from a boat).
The check now also allows four pixels' change in distance on the water
(the history texels lie up to two pixels away, plus last frame's jitter),
capped at a quarter of the distance.

The water body's four shadow points go down along the bent view, so where
the bottom rises ahead of it (a beach stepping up out of a lake) the deeper
ones ended up inside the sand and read the bottom above them as a shadow: a
darker band in front of the steps. Each point is now looked up where its
way to the sun crosses the water's surface (the same texels of the map),
so only what stands above the water shades it.

The header no longer claims a still scene traces the same rays every
frame: each ray starts from the point the jittered pixel shows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
…tem keeps vanilla light

From an independent review of the entity lighting:
- The copies are built and compiled two a frame as the level starts, not at
  their first draw, which stalled the game when a new mob, an enchanted item
  or the hand first came into view.
- The draw path is a plain map lookup: no lambda per draw, the compile is
  checked once per copy (again after a resource reload), and the shadow
  block's slice is made once per buffer.
- The hand phase ends right after the hand, so the totem screen effect keeps
  vanilla's light (its normals aren't world-oriented).
- The special glint copies get names of their own.
- A hand copy that fails no longer disables the level's copy, and back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
A new General-page setting (off, 60, 90, 120, 144, 165 fps). EtnaAutoScale
picks the World Resolution from a few fixed steps (100% down to 50%) by the
GPU's own time per frame, from the GPU timer on one frame in eight, so
VSync and frame limiters don't hide the room there is:
- two timed frames over the budget step down, one far over it two steps;
- a step up only when every timed frame of the last two seconds would still
  fit with headroom at the bigger size, counting the whole frame as growing
  with the pixels, so it doesn't bounce straight back;
- frames drawn before a step aren't counted after it.
It never goes below World Resolution (70% when that is at 100%), is off
during the benchmark, and keeps the setting without GPU timestamps. The
performance log shows the resolution it picked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
…ict steps up by the scaled passes

From an independent review:
- The lowest step is the first one at or below World Resolution and draws
  at that floor exactly: at 95% Auto drew at 100% and could never go down.
- It steers by the GPU time up to the end of Etna's final pass, without the
  HUD and the copy to the window, where a VSync wait can land on some
  drivers and would have held it at its lowest.
- A step up predicts only the level and world passes growing with the
  pixels; the shadow pass, the hand, bloom and grade keep their cost.
- No sentinel time for 'room since', frames timed while it didn't steer
  aren't counted, the GPU timer's doc names Auto Resolution.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
@Atomsk-debug Atomsk-debug changed the title Etna 0.9.0: World Resolution, own anti-aliasing, entities lit like the world, no code from other packs Etna 0.9.0: World and Auto Resolution, own anti-aliasing, entities lit like the world, no code from other packs Oct 11, 2026
The warp is centred on the map's middle, so each time the middle moved, every
texel of the map landed somewhere else on the world by a fraction of a texel
and the map took every edge and every leaf's holes at new points. It moved a
texel (0.1 blocks) at a time: while walking, most frames. The dappled shade
under trees and the shadow edges further out shimmered for as long as the
camera moved.

Now the middle holds still in the world and steps after the camera once it is
three quarters of a 2-block step away on an axis of the map (a hold, so it
can't flick back and forth). Offline (a canopy of leaf blocks with holes,
swaying, under the area-weighted filter, walking at 4.3 blocks a second) the
mean change from one frame to the next drops from 0.05-0.09 to 0.004-0.008 of
full shade, 16 to 150 blocks away; the frame the map steps is no worse than any
frame was before. Near the camera texels get at most 7% wider (16% along the
line to the middle).

The shadows fade out towards the map's edge counted from the camera
(etnaMapOut, EtnaShadowInfo.w: the share of the radius the map always covers
round the camera), so where they end no longer jumps when the middle steps.
The caster tests count from the middle (the square, the caster floor) or widen
by the camera's lead (the columns, the view margins).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
etnaMapOut now returns at least 1 for a point outside the map itself, so every
reader (filter, leaf light, single and bilinear tests, water) treats it as past
the edge, as post/shafts.fsh already did. It only happens while the maps fade,
when the older map is read a frame late behind a camera flying faster than the
slack a frame; those points were nearly faded out but read the map's clamped
edge texels.

The middle-step comment now gives the texel growth for both the 160 and the
96-block distance, and says how much shorter the shadows end.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
…mera

From 20 to 44 blocks out the shadow pass fades the sway out. Further away a
texel of the map is wider than the sway, which then only flips leaf texels
between lit and shaded every frame: far tree shade twinkled even with the
camera still. On screen the sway is under a pixel there anyway, and the
leaves themselves keep swaying, by less than their shadow lookup's offset
and bias (1.5 texels along the light plus the thin bias).

Offline (leaflab, swaying leaf blocks with holes, area-weighted filter, the
2-block map steps): with the camera still the change per frame at 32-150
blocks goes from 0.003-0.004 to none; walking it drops by about 40%, the rest
coming from the map's steps. Average shade unchanged. The far vertices also
skip the wind maths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
Two fine noise octaves (9x and 15x the base) give the star clouds and the
dust lanes' edges detail down to about three pixels at 1080p; before, the
smallest detail was about 14 pixels and the band looked low resolution.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7MajtknSApr1T68EGCPtx
waterBodyShadow follows the straight line of sight into the water instead
of the refracted ray. The bottom shows through the water unbent (the blend
lays the water over it), so the shade in the water now lands on the shadow
seen on the bottom. Before, the two sat side by side: shaded water over a
lit bottom looked colourless and see-through, lit water over a shaded
bottom like a patch of colour.

Far water seen nearly edge on: the waves a pixel no longer resolves still
tilt the facets it sees towards the eye (HIDDEN_TILT, 0.08), so they mirror
sky from a little above the horizon and less of it than a flat mirror
(waterSeenCos, waterSeenMirror in water_waves.glsl, used by the terrain and
by the reflections pass alike). From a block or two above the water, far
water mirrored almost only the whitish horizon.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzo7zbTpyU7a3y3XQNbw1n
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