fixed disconnected shadows

This commit is contained in:
DramaticShape
2026-07-29 21:51:42 -04:00
parent f841378330
commit 18857e039a
5 changed files with 81 additions and 10 deletions
+12
View File
@@ -161,6 +161,18 @@
Pokemon go solid white for those frames, silhouette and all, and nothing
else in the frame moves.
- **A standing figure's shadow detached from its feet under a low sun.** The
shadow compare forgives `slack` world pixels so lit ground does not acne
against its own texels, and that same forgiveness lit the first `slack` of
every cast shadow -- so the shadow started a bias-width away from the feet,
further the lower the sun reached (the classic peter-panning, invisible at
the old fixed 45 degrees and plain at a day/night golden hour). Sprite
cards -- characters, authored figures, flowers, battle mons -- are now
drawn into the shadow map sunk exactly `slack` down the sun ray
(`ShadowMap.sink`), which cancels the bias for the shadow they throw and
nothing else: no terrain moved, so the acne margin is untouched, and the
shadow root lands back under the feet at every hour.
### Changed
- **The two HP boxes snap to the window's edges during a staged battle.** The
+7 -3
View File
@@ -237,17 +237,21 @@ local function castShadows(state, arena, terrain, nbMesh, cx, cy, vw, vh,
for i, nb in ipairs(neighbors) do
ShadowMap.draw(nbMesh[i], atlasFor(nb.map), Mat4.translate(nb.ox, 0, nb.oy))
end
ShadowMap.draw(ChunkMesher.flowers(host), atlasFor(host), nil)
-- thin cards are sunk along the ray (ShadowMap.sink) so their shadows
-- keep contact with their bases instead of starting a bias-width away
ShadowMap.draw(ChunkMesher.flowers(host), atlasFor(host),
ShadowMap.sink(nil))
for _, nb in ipairs(neighbors) do
ShadowMap.draw(ChunkMesher.flowers(nb.map), atlasFor(nb.map),
Mat4.translate(nb.ox, 0, nb.oy))
ShadowMap.sink(Mat4.translate(nb.ox, 0, nb.oy)))
end
-- the mons themselves, as the same cards the camera will see. Their alpha
-- is the silhouette, so what lands on the ground is the shape of the
-- Pokemon rather than a blob standing in for one.
for _, card in ipairs(cards or {}) do
ShadowMap.draw(BattleBillboard.mesh(), card.tex, card.model)
ShadowMap.draw(BattleBillboard.mesh(), card.tex,
ShadowMap.sink(card.model))
end
ShadowMap.finish(sig)
+28
View File
@@ -362,6 +362,34 @@ local function fit(cx, cy, vw, vh)
ShadowMap.bias = ShadowMap.slack / math.max(1, far - near)
end
-- A CASTER sunk slightly down the sun ray before it is drawn into the map.
--
-- The depth compare forgives `slack` world pixels (BIAS + the SLOPE term)
-- so lit surfaces do not acne against their own texels -- but that same
-- forgiveness is what lets the ground right next to a standing figure read
-- as lit: the first `slack` of its shadow falls inside the compare's
-- tolerance, so on screen the shadow starts that far from the feet, and the
-- gap grows as the sun drops (the same bias reaches further horizontally
-- the more oblique the ray). Peter-panning, in the literature; it went
-- unseen while the sun hung at a fixed 45 degrees and shows plainly at a
-- day/night golden hour.
--
-- Storing the card that much DEEPER along the ray cancels the bias for the
-- shadow it throws -- the root lands back under the feet -- while touching
-- nothing else: the card's own lookup now reads a deeper stored value and
-- is more confidently lit, no terrain moved, and the acne margin the slack
-- exists for is intact. For sprite cards and other thin stand-ins only;
-- sinking the TERRAIN would cancel the bias everywhere, which is the acne.
--
-- Valid between begin() and finish(), which is when `slack` matches the
-- frustum the map is being drawn with.
function ShadowMap.sink(model)
local f = sunDir()
local s = ShadowMap.slack
return Mat4.mul(Mat4.translate(f[1] * s, f[2] * s, f[3] * s),
model or IDENTITY)
end
-- Whether the map has to be redrawn for `sig` -- a caller-built stamp of
-- everything the pass depends on (camera, terrain meshes, every pose). A
-- frame that changes none of it reuses the map it already has, which is
+12 -7
View File
@@ -520,20 +520,24 @@ local function castShadows(state, terrain, nbMesh, posed, cx, cy, vw, vh,
end
-- flower billboards live outside the terrain mesh (they draw after the
-- characters, pulled -- see render), but the sun still sees them: a
-- handful of cutouts per meadow, unlike the grass left out below
ShadowMap.draw(ChunkMesher.flowers(state.map), atlasFor(state.map), nil)
-- handful of cutouts per meadow, unlike the grass left out below.
-- Every thin card from here down is SUNK slightly along the ray
-- (ShadowMap.sink) so its shadow keeps contact with its feet instead of
-- starting a bias-width away.
ShadowMap.draw(ChunkMesher.flowers(state.map), atlasFor(state.map),
ShadowMap.sink(nil))
for _, nb in ipairs(state.neighbors or {}) do
ShadowMap.draw(ChunkMesher.flowers(nb.map), atlasFor(nb.map),
Mat4.translate(nb.ox, 0, nb.oy))
ShadowMap.sink(Mat4.translate(nb.ox, 0, nb.oy)))
end
-- authored figures cast too, for the same reason the flowers do: a
-- handful of cards per map, and a person with no shadow reads as pasted on
eachFigure(state.map, 0, 0, function(mesh, _, caster)
ShadowMap.draw(mesh, atlasFor(state.map), caster)
ShadowMap.draw(mesh, atlasFor(state.map), ShadowMap.sink(caster))
end)
for _, nb in ipairs(state.neighbors or {}) do
eachFigure(nb.map, nb.ox, nb.oy, function(mesh, _, caster)
ShadowMap.draw(mesh, atlasFor(nb.map), caster)
ShadowMap.draw(mesh, atlasFor(nb.map), ShadowMap.sink(caster))
end)
end
for _, p in ipairs(posed) do
@@ -542,8 +546,9 @@ local function castShadows(state, terrain, nbMesh, posed, cx, cy, vw, vh,
local mesh = SpriteBillboards.shadowQuad(def, frame)
if mesh then
ShadowMap.draw(mesh, p.sprite:resolveImage(),
Voxel3D.casterMatrix(p.px, p.py, p.gh + (p.lift or 0),
mirror))
ShadowMap.sink(
Voxel3D.casterMatrix(p.px, p.py, p.gh + (p.lift or 0),
mirror)))
end
end
+22
View File
@@ -2106,6 +2106,28 @@ Voxel3D.tint = { 1, 1, 1 }
Voxel3D.vp = nil
end
-- ------- a shadow keeps hold of the feet that throw it
--
-- The depth compare forgives `slack` world pixels so lit ground does not
-- acne, and that forgiveness detaches a standing card's shadow from its
-- feet by the same amount -- worse the lower the sun. Cards are drawn into
-- the map sunk exactly `slack` down the ray, which cancels the bias for the
-- shadow they throw and nothing else.
do
local ShadowMap = run.loader.exports.DRAMATIC_SHAPE.lib.require("ShadowMap")
local Mat4 = run.loader.exports.DRAMATIC_SHAPE.lib.require("Mat4")
local dir = ShadowMap.sunDir()
local m = ShadowMap.sink(nil)
T.check(math.abs(m[4] - dir[1] * ShadowMap.slack) < 1e-9
and math.abs(m[8] - dir[2] * ShadowMap.slack) < 1e-9
and math.abs(m[12] - dir[3] * ShadowMap.slack) < 1e-9,
"sink() moves a caster exactly `slack` down the sun ray")
local sunk = ShadowMap.sink(Mat4.translate(10, 0, 6))
T.check(math.abs(sunk[4] - (10 + dir[1] * ShadowMap.slack)) < 1e-9
and math.abs(sunk[12] - (6 + dir[3] * ShadowMap.slack)) < 1e-9,
"and composes over the caster's own transform, not instead of it")
end
Pipelines.reset()
run.release()