fix(globe): Navara sprites, points and lines drawn after tone mapping

Review of Tasks 2–3: on Navara the overlays went through the look's
tone mapping and grading (magenta came out as 247/40/228, green as
147/228/89 in the cinematic look), where Cesium draws billboards and
polylines in the colours asked for.

src/globe/navara/overlay.js: a public Navara EffectDesc (gevOverlay)
inserted after toneMapping and before the gev sharpening, SMAA/FXAA and
final, whose pass renders one three scene over the tone-mapped input
buffer (in Navara's own Pass wrapper: Navara inserts a bare pass only
when it is an instance of its own postprocessing copy); it costs nothing
while the scene is empty. The chain has no depth buffer, so each overlay
material tests the scene depth itself (overlayDepth.js): its log depth
against Navara's MRT depth (RGBA packed), modes always / normal /
depth-fail, disableWithinM as a per-vertex escape. Sprites and points
(moverMesh.js) and line ribbons (lineMesh.js) live in that scene with
their per-frame work as overlay hooks; models stay Navara meshes, lit
and graded like the tiles. The sharpening now sits after the overlay
(Cesium's sharpen stage sharpens billboards too).

Also (review): Navara pixel dashes are measured in CSS pixels
(gl_FragCoord over uGevPixelRatio), and the line viewport uniform is
updated in place.

Measured: a magenta sprite draws 255/0/255. The contract suite's colour
checks are now within 40 of the asked colour on every channel on both
engines (were: dominant channels); Cesium 68/68, Navara 74/74 (its
sharpen-order check accepts the overlay between tone mapping and the
sharpening). Shell: cesium 23/23, cross 4/4; navara 23/25 — the two map
stack pictures from space fail on stddev 7.2/7.6 against 8, as on the
Task 3 commit run at the same hour (the cinematic sun follows the clock
into night over the reset view), not from this change.
This commit is contained in:
German Khokhlov committed 2026-10-16 19:04:57 +04:00
1 parent 3c2e924363
commit e804351205
11 files changed
+388 -125

No files matched your search

+10 -3
View File
@@ -32,10 +32,14 @@ test('the shader patch sizes the quad in pixels and colours it per instance', ()
/uGevFootprint \* max\(-mvCenter\.z, 0\.0\)/,
);
assert.match(shader.vertexShader, /vMapUv = mix\(gevUv\.xy, gevUv\.zw, uv\)/);
assert.match(shader.vertexShader, /vFragDepth = 1\.0;/);
assert.match(
shader.vertexShader,
/vGevDepthFree = gevDistance < uGevDisableWithinM/,
);
assert.match(shader.fragmentShader, /if \(gevOverlayHidden\(\)\) discard;/);
assert.doesNotMatch(shader.vertexShader, /#include <project_vertex>/);
assert.match(shader.fragmentShader, /diffuseColor \*= vGevColor;/);
assert.doesNotMatch(shader.fragmentShader, /discard/);
assert.doesNotMatch(shader.fragmentShader, /gevRadius > 1\.0\) discard/);
const point = patchMoverShader(basicShader(), {}, { point: true });
assert.match(point.fragmentShader, /if \(gevRadius > 1\.0\) discard;/);
});
@@ -46,7 +50,9 @@ test('a sprite mesh uploads committed instances relative to its origin', () => {
const { mesh, uniforms } = mover;
assert.equal(mesh.renderOrder, 3);
assert.equal(mesh.count, 0);
assert.equal(mesh.material.depthTest, true);
// The overlay tests depth in the shader, against the scene's.
assert.equal(mesh.material.depthTest, false);
assert.equal(uniforms.uGevDepthMode.value, 1);
assert.equal(uniforms.uGevDisableWithinM.value, 2000);
assert.deepEqual(uniforms.uGevScaleByDistance.value, [1000, 1, 100000, 0.5]);
store.set(0, { position: { x: 6_378_137, y: 10, z: 20 } });
@@ -98,6 +104,7 @@ test('points have no atlas and draw a disc', () => {
const mover = createMoverMesh(options, createMoverStore(options));
assert.equal(mover.mesh.material.map, null);
assert.equal(mover.mesh.material.depthTest, false);
assert.equal(mover.uniforms.uGevDepthMode.value, 0, "'always': no test");
assert.equal(mover.uniforms.uGevOutlinePx.value, 1);
mover.dispose();
});