fix glinting on windows and striping

This commit is contained in:
DramaticShape
2026-07-29 22:43:20 -04:00
parent 5ff56eab46
commit 6d27d91411
5 changed files with 68 additions and 28 deletions
+19 -12
View File
@@ -130,14 +130,18 @@
at any angle, in free-roam and staged battles alike, with no geometry
work.
By day a thin diagonal glint crosses the panes WHILE THE VIEW MOVES: the
sweep's phase is fed by the camera's own travel and its strength fades out
within a beat of standing still -- a reflection is something the viewpoint
does, so still camera means still glass. It lifts the texels toward
sky-white and leaves the diagonal shine art visible through it. The mask
is consulted only by meshes textured from the tileset atlas
(Voxel3D.glass), never by sprite sheets, whose coordinates would land on
the panes' atlas positions by accident.
By day a thin glint crosses the panes WHILE THE VIEW MOVES: the sweep's
phase is fed by the camera's own travel and its strength fades out within
a beat of standing still -- a reflection is something the viewpoint does,
so still camera means still glass. The sweep pattern lives in the pane's
OWN texels, not the screen's: a screen-anchored pattern has the world
sliding through it at zoom speed whenever the camera pans, which strobed
(worst walking against the sweep); anchored to the glass, panning moves
nothing and a step advances the glint a fraction of a texel, the same in
every direction. It lifts the texels toward sky-white and leaves the
shine art visible through it. The mask is consulted only by meshes
textured from the tileset atlas (Voxel3D.glass), never by sprite sheets,
whose coordinates would land on the panes' atlas positions by accident.
After dark the panes are LIT: the texel's own pattern carried into a warm
lamp colour, replacing the shaded answer entirely -- a lit window ignores
the sun, every shadow and the hour's tint, exactly as a window with a lamp
@@ -202,10 +206,13 @@
own ray (`ShadowMap.snug`): moving along the ray changes nothing about
where a shadow falls, but storing the card shallower takes three quarters
of the forgiveness back for the shadow it throws -- and for nothing else:
no terrain moved, so the acne margin is untouched where it matters, and
the quarter left keeps the card off the float-equality knife edge against
its own stored depth. The shadow root lands back under the feet at every
hour.
no terrain moved, so the acne margin is untouched where it matters. The
obligation that comes with it: every snugged caster's LIT draw hands the
same snugged transform to its own shadow lookup (Voxel3D.draw's
`sunModel`), so stored and lookup agree exactly and the compare keeps its
full margin -- read un-snugged, the missing nine tenths showed up as
diagonal moire bands crawling across every sprite. The shadow root lands
back under the feet at every hour.
### Changed
+7 -3
View File
@@ -416,8 +416,10 @@ function BattleScene.render(state, arena, textures, token)
-- tileset atlas, so the mask's coordinates mean nothing on them
Voxel3D.glass(false)
for _, card in ipairs(monCards(arena, groundY, textures)) do
-- the sun stored this card snugged (castShadows), so its own shadow
-- lookup must read the same snugged transform -- see ShadowMap.snug
Voxel3D.draw(BattleBillboard.mesh(), card.tex, card.model,
BattleBillboard.PULL)
BattleBillboard.PULL, ShadowMap.snug(card.model))
end
Voxel3D.glass(true)
Voxel3D.seams(true)
@@ -433,10 +435,12 @@ function BattleScene.render(state, arena, textures, token)
Mat4.translate(nb.ox, 0, nb.oy), pull)
end
local fpull = math.max(0, pull - 8 * math.sin(math.max(pitch, 0.05)))
Voxel3D.draw(ChunkMesher.flowers(host), atlasFor(host), nil, fpull)
Voxel3D.draw(ChunkMesher.flowers(host), atlasFor(host), nil, fpull,
ShadowMap.snug(nil))
for _, nb in ipairs(neighbors) do
Voxel3D.draw(ChunkMesher.flowers(nb.map), atlasFor(nb.map),
Mat4.translate(nb.ox, 0, nb.oy), fpull)
Mat4.translate(nb.ox, 0, nb.oy), fpull,
ShadowMap.snug(Mat4.translate(nb.ox, 0, nb.oy)))
end
local canvas = Voxel3D.endScene()
if not canvas then return end
+10 -2
View File
@@ -390,8 +390,16 @@ ShadowMap.SNUG = 0.9
-- terrain moved, so the acne margin the slack exists for is intact where
-- it matters. For sprite cards and other thin stand-ins only.
--
-- Valid between begin() and finish(), which is when `slack` matches the
-- frustum the map is being drawn with.
-- ONE OBLIGATION comes with it: the caster's LIT draw must hand this same
-- snugged transform to its shadow lookup (Voxel3D.draw's `sunModel`).
-- Stored and lookup then agree exactly, as they did before snugging, and
-- the compare keeps its full acne margin. A caster stored snugged but read
-- un-snugged is 0.9 of the margin short, and the loss shows up as diagonal
-- moire bands crawling across the card.
--
-- Valid between begin() and the next begin(): `slack` and the sun hold
-- still between redraws of the map, so a lit frame that reuses last
-- frame's map computes the same displacement it was stored with.
function ShadowMap.snug(model)
local f = sunDir()
local s = -ShadowMap.slack * ShadowMap.SNUG
+13 -2
View File
@@ -205,6 +205,7 @@ local SHADER = [[
uniform float ghost; // 0 = shade normally, 1 = flatten to it
uniform vec3 dayTint; // the hour's light on the world; 1,1,1 = noon
uniform Image glassMask; // opaque where the atlas texel is window glass
uniform vec2 glassSize; // the mask's dimensions: tc -> atlas texels
uniform float glassNight; // 0 = daylight .. 1 = the lamps are on
uniform float glassPhase; // the glint's phase: advances with TRAVEL
uniform float glassGlint; // and its strength: 0 while standing still
@@ -241,7 +242,13 @@ local SHADER = [[
// cast with lamplight at night.
float glass = Texel(glassMask, tc).a * glassOn;
if (glass > 0.0) {
float sweep = sin((sc.x + sc.y) * 0.04 - glassPhase);
// the sweep lives in the PANE's own space (atlas texels), not the
// screen's: a pattern anchored to the screen has the world sliding
// through it at zoom speed whenever the camera pans, which strobed --
// worst where the pan and the phase ran opposite ways. Anchored to
// the glass, panning moves nothing; only the phase does, a fraction
// of a texel per step, the same in every walking direction.
float sweep = sin(tc.x * glassSize.x * 0.8 - glassPhase);
float glint = pow(max(sweep, 0.0), 20.0) * 0.55 * glassGlint;
vec3 pane = mix(rgb, vec3(0.93, 0.97, 1.0), glint * glass);
float shine = dot(p.rgb, vec3(0.299, 0.587, 0.114));
@@ -607,7 +614,11 @@ function Voxel3D.beginScene(w, h, cx, cy, vw, vh, sky, slot)
-- declared either way, and unbound is a driver-dependent crash), how lit
-- the panes are, and the movement-fed glint as the caller last set it
local mask = Voxel3D.glassMask or GlassMask.blank()
if mask then pcall(sh.send, sh, "glassMask", mask) end
if mask then
pcall(sh.send, sh, "glassMask", mask)
local ok, mw, mh = pcall(mask.getDimensions, mask)
pcall(sh.send, sh, "glassSize", { ok and mw or 1, ok and mh or 1 })
end
pcall(sh.send, sh, "glassNight", Voxel3D.glassNight or 0)
pcall(sh.send, sh, "glassPhase", Voxel3D.glassPhase or 0)
pcall(sh.send, sh, "glassGlint", Voxel3D.glassGlint or 0)
+19 -9
View File
@@ -307,12 +307,13 @@ local function drawEntity(sprite, px, py, facing, phase, flip, gh, colors,
-- drift): lets the leaned-back head win against the wall it leans
-- OVER while a character genuinely BEHIND a building is dozens of
-- pixels deeper and still loses, so real occlusion works.
-- the same card UNLEANED is what the sun saw (castShadows draws
-- exactly this mesh), so that is where each vertex asks whether the
-- light reached it -- see Voxel3D.draw
-- the same card UNLEANED -- and SNUGGED, exactly as the sun stored it
-- (castShadows draws this mesh through ShadowMap.snug) -- is where each
-- vertex asks whether the light reached it; see ShadowMap.snug for why
-- the lookup must match the stored transform to the letter
Voxel3D.draw(mesh, tex, billboardMatrix(px, py, y, mirror),
billboardPull(),
Voxel3D.casterMatrix(px, py, y, mirror))
ShadowMap.snug(Voxel3D.casterMatrix(px, py, y, mirror)))
return true
end
@@ -464,7 +465,11 @@ end
-- with distance covered and its strength fades in over a few steps of
-- walking and back out within a beat of standing still. Stand still and
-- the glass is still; move and the light crosses it.
VoxelScene.GLINT_RATE = 0.22 -- radians of sweep per world pixel travelled
-- The rate is slow on purpose: the sweep pattern lives in the pane's own
-- texels (see the scene shader), so this is a FRACTION of a texel per world
-- pixel walked -- one full pass of the glint across a pane per eight or so
-- cells of travel, with no frame ever jumping it far enough to strobe.
VoxelScene.GLINT_RATE = 0.05 -- radians of sweep per world pixel travelled
VoxelScene.GLINT_IN = 0.12 -- strength gained per moving frame
VoxelScene.GLINT_OUT = 0.08 -- and lost per resting frame
@@ -699,11 +704,13 @@ function VoxelScene.render(state, w, h, vw, vh, paletteFor)
-- wins the overlap, which is the order the flat game draws them in.
local figPull = billboardPull()
eachFigure(state.map, 0, 0, function(mesh, model, caster)
Voxel3D.draw(mesh, atlasFor(state.map), model, figPull, caster)
Voxel3D.draw(mesh, atlasFor(state.map), model, figPull,
ShadowMap.snug(caster))
end)
for _, nb in ipairs(state.neighbors or {}) do
eachFigure(nb.map, nb.ox, nb.oy, function(mesh, model, caster)
Voxel3D.draw(mesh, atlasFor(nb.map), model, figPull, caster)
Voxel3D.draw(mesh, atlasFor(nb.map), model, figPull,
ShadowMap.snug(caster))
end)
end
-- and the seams are back on for the terrain art that follows: grass and
@@ -733,11 +740,14 @@ function VoxelScene.render(state, w, h, vw, vh, paletteFor)
-- ON, while the nearest flower of the cell south (+20) stays in front
-- and keeps overdrawing their feet.
local fpull = math.max(0, pull - 8 * math.sin(math.max(Voxel.angle, 0.05)))
-- flowers are snugged casters too, so they read their own shadowing
-- through the same snugged transform the sun stored them with
Voxel3D.draw(ChunkMesher.flowers(state.map), atlasFor(state.map), nil,
fpull)
fpull, ShadowMap.snug(nil))
for _, nb in ipairs(state.neighbors or {}) do
Voxel3D.draw(ChunkMesher.flowers(nb.map), atlasFor(nb.map),
Mat4.translate(nb.ox, 0, nb.oy), fpull)
Mat4.translate(nb.ox, 0, nb.oy), fpull,
ShadowMap.snug(Mat4.translate(nb.ox, 0, nb.oy)))
end
return Voxel3D.endScene()