mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 08:01:09 +02:00
fix water again on android
This commit is contained in:
+39
-24
@@ -583,7 +583,7 @@ end
|
||||
-- The overworld's alone: the staged battle draws its water plain, always --
|
||||
-- its placed camera reads this pass wrong, and a stage set wants painted
|
||||
-- water anyway (see BattleScene, where the choice is argued).
|
||||
-- ------- and why the flat draw happens FIRST, every time
|
||||
-- ------- and why the flat draw happens FIRST while the world is curved
|
||||
--
|
||||
-- The reflective pass writes no depth -- it cannot, the depth canvas is
|
||||
-- detached for the length of it so the shader can READ it -- and it does its
|
||||
@@ -592,31 +592,39 @@ end
|
||||
-- one: WATER IN FRONT OF WATER. Nothing puts water in the depth buffer, so
|
||||
-- no lake can hide another, and the pass simply paints them in mesh order.
|
||||
--
|
||||
-- On flat water that never mattered, because every surface lies in the one
|
||||
-- plane at its own recessed height and a farther sheet always lands farther
|
||||
-- down the screen. THE WORLD CURVE ENDS THAT. The bend drops the world by
|
||||
-- the square of its distance, so the far side of the map swings down and
|
||||
-- back up into the near field of view -- and a sheet of sea a hundred and
|
||||
-- fifty tiles away, drawn later in the same mesh, paints straight over the
|
||||
-- pond at the player's feet. That is the tall grass and the water "from the
|
||||
-- other side of the map": not a reflection of them, THEM, rasterised on top
|
||||
-- of the water in front of you.
|
||||
-- On a flat world that never matters: every surface lies in the one plane
|
||||
-- at its own recessed height, and a farther sheet always lands farther down
|
||||
-- the screen. THE WORLD CURVE ENDS THAT. The bend drops the world by the
|
||||
-- square of its distance, so the far side of the map swings down and back
|
||||
-- up into the near field of view -- and a sheet of sea a hundred and fifty
|
||||
-- tiles away, drawn later in the same mesh, paints straight over the pond
|
||||
-- at the player's feet. Not a reflection of the far shore: the far shore
|
||||
-- itself, rasterised on top of the water in front of you.
|
||||
--
|
||||
-- So the meshes go down flat first, through the ordinary scene shader with
|
||||
-- depth writes on, and the reflective pass draws over the top of what
|
||||
-- survived. Three things fall out of it and all three are wanted: the depth
|
||||
-- buffer now holds the water surface, so the pass's own test throws the far
|
||||
-- sheet away; the reflection COPY holds it too, so a ray grazing another
|
||||
-- part of the lake reads water rather than the void behind it; and the flat
|
||||
-- draw is the same fallback this function already ended with, so a frame
|
||||
-- that cannot run the reflective pass is unchanged.
|
||||
-- So WHILE THE CURVE IS ON, the meshes go down flat first, through the
|
||||
-- ordinary scene shader with depth writes on, and the reflective pass draws
|
||||
-- over the top of what survived: the depth buffer now holds the water
|
||||
-- surface, so the pass's own test throws the far sheet away, and the
|
||||
-- reflection COPY holds it too, so a ray grazing another part of the lake
|
||||
-- reads water rather than the void behind it.
|
||||
--
|
||||
-- It costs one more rasterisation of the water meshes. Water is a small
|
||||
-- share of a frame's triangles and this is the cheapest shader in the mode.
|
||||
-- With the curve OFF the prepass is not just unnecessary, it is a LIABILITY,
|
||||
-- and it stays off -- the reflective pass tests only against terrain, as it
|
||||
-- always did. Painting the surface into the depth texture turns the pass's
|
||||
-- test into a comparison of the surface against ITSELF, which asks the two
|
||||
-- rasterisations to agree to within interpolation error -- and on mobile
|
||||
-- GPUs they don't reliably (that fight is what put the Android port back on
|
||||
-- flat water). Confined to the curve there is no regression to reach: the
|
||||
-- flat world never had the far-shore bug in the first place.
|
||||
function VoxelScene.drawWater(draws, cast)
|
||||
for _, d in ipairs(draws) do
|
||||
Voxel3D.draw(d[1], d[2], d[3])
|
||||
-- prepass only under the bend; see the header
|
||||
local curved = (Voxel3D.curveK or 0) > 0
|
||||
if curved then
|
||||
for _, d in ipairs(draws) do
|
||||
Voxel3D.draw(d[1], d[2], d[3])
|
||||
end
|
||||
end
|
||||
local plain = not curved
|
||||
if Water.enabled() and Voxel3D.depthReadable() then
|
||||
local mirror, depth = Voxel3D.beginWater(cast)
|
||||
local w, h = Voxel3D.size()
|
||||
@@ -634,15 +642,22 @@ function VoxelScene.drawWater(draws, cast)
|
||||
Water.draw(d[1], d[2], d[3])
|
||||
end
|
||||
Water.finish()
|
||||
plain = false
|
||||
end
|
||||
-- Unconditionally, and OUTSIDE the success branch: beginWater unbinds
|
||||
-- the shader and the depth mode BEFORE it can discover it cannot go on,
|
||||
-- so a frame that bails halfway through has to be put back together
|
||||
-- exactly like one that succeeded -- otherwise every pass after it runs
|
||||
-- with no shader and no depth test. (What a bail leaves on screen is the
|
||||
-- flat draw above, which is where this function used to end anyway.)
|
||||
-- with no shader and no depth test.
|
||||
Voxel3D.endWater()
|
||||
end
|
||||
-- the fallback flat draw -- unless the curve's prepass already put the
|
||||
-- same meshes down, in which case a bailed frame is already whole
|
||||
if plain then
|
||||
for _, d in ipairs(draws) do
|
||||
Voxel3D.draw(d[1], d[2], d[3])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- A stamp of everything the sun pass depends on. Nothing in it moving
|
||||
|
||||
+24
-3
@@ -32,8 +32,17 @@ local Voxel = {}
|
||||
-- Its ANGLE is 35 degrees, the same as the rung of that name. The duplicate
|
||||
-- in the table is deliberate: the ladder is a list of what each rung LOOKS
|
||||
-- like, and two rungs may look the same while meaning different things.
|
||||
Voxel.ANGLES_DEG = { 0, 35, 15, 35, 50, 75 }
|
||||
Voxel.ANGLE_LABELS = { "OFF", "FULL", "15", "35", "50", "75" }
|
||||
--
|
||||
-- 1ST is the other rung that is more than an angle: the camera steps off its
|
||||
-- orbit entirely and stands in the player's own eyes (lib/FirstPerson.lua),
|
||||
-- with free look and free movement. Its ANGLE entry is 75 -- the orbit rung
|
||||
-- it hands over from -- because the tween in and out of first person starts
|
||||
-- from whatever the orbit shows, and the lowest rung is the one a dive into
|
||||
-- a head should start from. Everything angle-derived (the sky's fade, the
|
||||
-- billboard lean the blend eases away) reads that 75 while the first-person
|
||||
-- rig owns the actual camera.
|
||||
Voxel.ANGLES_DEG = { 0, 35, 15, 35, 50, 75, 75 }
|
||||
Voxel.ANGLE_LABELS = { "OFF", "FULL", "15", "35", "50", "75", "1ST" }
|
||||
Voxel.MAX_LEVEL = #Voxel.ANGLES_DEG - 1
|
||||
|
||||
-- the rung FULL sits on, so nothing has to hunt for it by label
|
||||
@@ -43,6 +52,13 @@ function Voxel.isFull(level)
|
||||
return (level or Voxel.level) == Voxel.FULL_LEVEL
|
||||
end
|
||||
|
||||
-- the rung the first-person camera sits on, likewise
|
||||
Voxel.FP_LEVEL = 6
|
||||
|
||||
function Voxel.isFirstPerson(level)
|
||||
return (level or Voxel.level) == Voxel.FP_LEVEL
|
||||
end
|
||||
|
||||
-- ------- what the hotkey walks
|
||||
--
|
||||
-- The ANGLE rungs only, with FULL left out. The key is a display-mode
|
||||
@@ -51,7 +67,12 @@ end
|
||||
-- mid-walk, would silently turn the blur to maximum and flatten the horizon
|
||||
-- with no indication that a keypress had done so. FULL stays on the OPTIONS
|
||||
-- row, which is where a preset that changes other rows belongs.
|
||||
Voxel.HOTKEY_ORDER = { 0, 2, 3, 4, 5 } -- OFF, 15, 35, 50, 75
|
||||
--
|
||||
-- 1ST is on the path: it changes the camera and only the camera, which is
|
||||
-- exactly what the key promises -- and the key is also the way back OUT of
|
||||
-- first person on a keyboard, where the mouse is captured and the OPTIONS
|
||||
-- menu is a trip.
|
||||
Voxel.HOTKEY_ORDER = { 0, 2, 3, 4, 5, 6 } -- OFF, 15, 35, 50, 75, 1ST
|
||||
|
||||
-- The rung a press moves to from `level`.
|
||||
--
|
||||
|
||||
+22
-4
@@ -960,11 +960,29 @@ vec4 effect(mediump vec4 color, Image tex, mediump vec2 tc, mediump vec2 sc) {
|
||||
// The buffer now holds THIS SURFACE too (VoxelScene draws the water flat
|
||||
// before the pass that reflects it), which is what makes one lake able to
|
||||
// hide another -- and it means every fragment here is testing against its
|
||||
// own depth. The two draws reach it through different shader programs, so
|
||||
// ask for a hair of slack rather than bit-equality: far less than the gap
|
||||
// to anything genuinely in front, far more than two compilers disagree by.
|
||||
// own depth. That raises the bar on the fragment's own z: gl_FragCoord is
|
||||
// allowed to be MEDIUMP on GLES (and is, on Adreno), and fp16 near the far
|
||||
// end of the range steps by about half a thousandth -- which the old test
|
||||
// against the terrain far behind the surface never felt, and a comparison
|
||||
// of the surface against itself loses outright. Every fragment failed, the
|
||||
// pass discarded the whole lake, and Android showed the flat draw
|
||||
// underneath. So the depth is recomputed HERE, in highp, from the same
|
||||
// vBent and vp the vertex stage used -- full precision on every driver.
|
||||
//
|
||||
// The slack is sized to what remains after that, which is not rounding:
|
||||
// the buffer holds depth interpolated LINEARLY IN SCREEN SPACE, while the
|
||||
// recomputation projects the perspective-interpolated vBent -- the exact
|
||||
// answer. The two agree at the vertices and drift apart across a quad's
|
||||
// interior, by more the bigger the quad stands on screen; on a phone
|
||||
// (fit scale 6, water quads hundreds of pixels tall) the drift crosses
|
||||
// 1e-5 mid-quad, which discarded the middle of every tile row and looked
|
||||
// like flat water with reflective seams. Anything GENUINELY in front of a
|
||||
// water pixel is whole world units nearer -- upward of 1e-3 in depth --
|
||||
// so 2e-4 clears the drift with room while still catching every occluder.
|
||||
vec2 uv = sc / love_ScreenSize.xy;
|
||||
if (gl_FragCoord.z > Texel(depthTex, uv).r + 1e-5) discard;
|
||||
vec4 selfC = vp * vec4(vBent, 1.0);
|
||||
float selfZ = selfC.z / selfC.w * 0.5 + 0.5;
|
||||
if (selfZ > Texel(depthTex, uv).r + 2e-4) discard;
|
||||
|
||||
// THE COLUMN THIS FRAGMENT IS LOOKING AT. Every water pixel is a bar of
|
||||
// its own standing a whole number of pixels tall, and the ray decides
|
||||
|
||||
@@ -1924,9 +1924,11 @@ T.check(plain:find("sc / love_ScreenSize.xy", 1, true) ~= nil,
|
||||
-- the world curve drops the far side of the map into the near field of view,
|
||||
-- and a sea a hundred and fifty tiles away came out rasterised on top of the
|
||||
-- pond at the player's feet, tall grass and all.
|
||||
T.check(plain:find("Texel(depthTex, uv).r + 1e-5", 1, true) ~= nil,
|
||||
"with a hair of slack, because every water fragment is now testing "
|
||||
.. "against its own depth through a second shader program")
|
||||
T.check(plain:find("vec4 selfC = vp * vec4(vBent, 1.0)", 1, true) ~= nil
|
||||
and plain:find("Texel(depthTex, uv).r + 2e-4", 1, true) ~= nil,
|
||||
"testing a HIGHP recomputed depth (gl_FragCoord.z is mediump on mobile "
|
||||
.. "GLES -- fp16 loses a self-comparison outright) with slack covering "
|
||||
.. "the buffer's screen-linear interpolation drift across big quads")
|
||||
T.check(VoxelScene.drawWater ~= nil, "and the flat draw that puts it there")
|
||||
|
||||
-- ------- the lift itself
|
||||
|
||||
Reference in New Issue
Block a user