fix(globe): Navara model tints are decoded from sRGB before mixing

The instance colour (sRGB, as Cesium takes colours: the military amber
#FFB800) was mixed into the linear material colour as is, so a tinted
model drew paler and brighter than Cesium's: the military footprint gate
read 124 % (more pixels cleared the contrast threshold over water) and
the median luminance +9 %. White (flights) is the same either way, which
is why the flights gate passed. The tint is now sRGBTransferEOTF-decoded,
as the sprite sets already decode theirs.

qa-flights-engines' model gate adds the highlights: the p90 luminance of
the model pixels within ±10 % of Cesium, with the near-white pixel count
reported (10b review low 2).

Models on, near pose (computed scale ratio ~1.0000 on both):
- military (from 100 km): footprint 9,902 vs 8,610 px (115 %), median
  luminance 158.9 vs 155.3, p90 190.9 vs 184.5 — 18/18;
- flights: footprint 104 %, median 189.8 vs 196.7, p90 223.1 vs 247.8
  (near-white px 83 vs 510: Cesium's white models saturate where Navara's
  tone mapping rolls them off) — 20/20.
Models off: flights 16/16, military 15/15. Side-by-sides regenerated.
This commit is contained in:
German Khokhlov committed 2026-10-17 11:28:57 +04:00
1 parent 5ff8c5d7a6
commit 71b7177dc0
2 files changed
+5 -4

No files matched your search

+3 -2
View File
@@ -47,7 +47,8 @@ const scratchTranslation = new Vector3();
const scratchScale = new Vector3(); const scratchScale = new Vector3();
/** /**
* Mixes the instance colour into the material's colour by uGevBlend (the * Mixes the instance colour, decoded from sRGB (as Cesium takes colours;
* the material works in linear), into the material's colour by uGevBlend (the
* set's colorBlend, as Cesium's colorBlendAmount); the instance alpha is the * set's colorBlend, as Cesium's colorBlendAmount); the instance alpha is the
* model's opacity (Cesium: a model colour's alpha makes it translucent; the * model's opacity (Cesium: a model colour's alpha makes it translucent; the
* aircraft fade with distance through it), not a blend weight. * aircraft fade with distance through it), not a blend weight.
@@ -75,7 +76,7 @@ function blendInstanceColor(material, uniforms) {
) )
.replace( .replace(
'#include <map_fragment>', '#include <map_fragment>',
'#include <map_fragment>\ndiffuseColor.rgb = mix(diffuseColor.rgb, vGevColor.rgb, uGevBlend);\ndiffuseColor.a *= vGevColor.a;', '#include <map_fragment>\ndiffuseColor.rgb = mix(diffuseColor.rgb, sRGBTransferEOTF(vGevColor).rgb, uGevBlend);\ndiffuseColor.a *= vGevColor.a;',
); );
}; };
material.customProgramCacheKey = () => 'gev-mover-model-blend'; material.customProgramCacheKey = () => 'gev-mover-model-blend';
+2 -2
View File
@@ -88,7 +88,7 @@ test('a far model keeps its minimum pixel size', async () => {
); );
}); });
test("the instance colour tints by the set's blend amount; its alpha is opacity, as Cesium's model colour", async () => { test("the instance colour (sRGB, decoded) tints by the set's blend amount; its alpha is opacity, as Cesium's model colour", async () => {
const store = createMoverStore(options); const store = createMoverStore(options);
const mover = createModelMover(options, store, { const mover = createModelMover(options, store, {
load: async () => fakeGltf(), load: async () => fakeGltf(),
@@ -110,7 +110,7 @@ test("the instance colour tints by the set's blend amount; its alpha is opacity,
mesh.material.onBeforeCompile(shader); mesh.material.onBeforeCompile(shader);
assert.match( assert.match(
shader.fragmentShader, shader.fragmentShader,
/diffuseColor\.rgb = mix\(diffuseColor\.rgb, vGevColor\.rgb, uGevBlend\);\s*diffuseColor\.a \*= vGevColor\.a;/, /diffuseColor\.rgb = mix\(diffuseColor\.rgb, sRGBTransferEOTF\(vGevColor\)\.rgb, uGevBlend\);\s*diffuseColor\.a \*= vGevColor\.a;/,
); );
// Ambient light on the tinted surface (Cesium's image-based lighting). // Ambient light on the tinted surface (Cesium's image-based lighting).
assert.match( assert.match(