mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
Hide Navara infrastructure stems and data pack meshes behind walls, terrain and the Earth
Navara draws its transparent scene after the post effects into a target without the scene depth, so stems and pack polygons and images painted through 3D tiles. A scene-depth test now drops each of their fragments behind the depth Navara packs after its opaque pass; they stay in the transparent pass, so their look in the open is unchanged (scripts/qa-edge-occlusion.mjs compares it with the previous build). Wind flow was already hidden and is kept as a guard in the same scenario.
This commit is contained in:
1 parent
c392d6fb50
commit
eee4f48745
6 files changed
+298
-5
No files matched your search
@@ -71,7 +71,11 @@ const renderedLayer = (file) =>
|
||||
RENDERED_LAYER_DIRS.some((dir) => file.startsWith(dir));
|
||||
const cesiumLayerRenderer = (file) =>
|
||||
/\/render\/cesium(?:[^/]*|\/[^/]+)\.js$/.test(file);
|
||||
const navaraLayerRenderer = (file) => /\/render\/navara[^/]*\.js$/.test(file);
|
||||
// Navara renderers outside a layer's render/ directory: like those, they
|
||||
// reach the app only through the Navara mode's import().
|
||||
const NAVARA_SCENE_RENDERERS = new Set(['src/scenes/dataPacks/navara.js']);
|
||||
const navaraLayerRenderer = (file) =>
|
||||
/\/render\/navara[^/]*\.js$/.test(file) || NAVARA_SCENE_RENDERERS.has(file);
|
||||
const renderer = (file) =>
|
||||
/^src\/(?:ui|app|standalone|overlays)\//.test(file) ||
|
||||
/^src\/layers\/[^/]+\/(?:index|rendering|snapshotRenderer|overlay|presentation|cards|controls)\.js$/.test(
|
||||
|
||||
@@ -3706,6 +3706,7 @@
|
||||
"src/layers/awareness/render/navara.js",
|
||||
"src/layers/infrastructure/render/navara.js",
|
||||
"src/layers/shared/render/navaraStems.js",
|
||||
"src/layers/shared/render/navaraSceneDepth.js",
|
||||
"src/layers/submarineCables/render/navara.js",
|
||||
"src/layers/directions/render/navara.js",
|
||||
"src/layers/traffic/render/navara.js",
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// Navara draws its transparent scene after the post effects, into a target
|
||||
// that does not hold the scene's depth, so a transparent mesh behind a wall,
|
||||
// terrain or the Earth painted through them. Moving such a mesh into the
|
||||
// opaque scene (as CCTV did) would also run it through the effects and change
|
||||
// its look. This test keeps the mesh in the transparent pass and drops every
|
||||
// fragment that lies behind the depth Navara packs after its opaque pass
|
||||
// (globe, 3D tiles, MRT and opaque meshes): an unhidden fragment is drawn
|
||||
// exactly as before. scripts/qa-edge-occlusion.mjs checks it in a browser.
|
||||
import { RGBADepthPacking, Vector2 } from 'three';
|
||||
|
||||
const KEY = 'gevSceneDepth';
|
||||
|
||||
// Log-depth units (about 0.01 % of the distance): a stem's foot or a ground
|
||||
// overlay on the very surface it stands on is not cut by its own ground.
|
||||
export const SCENE_DEPTH_BIAS = 5e-6;
|
||||
|
||||
const PARS = /* glsl */ `
|
||||
uniform sampler2D tGevSceneDepth;
|
||||
uniform vec2 uGevTargetSize;
|
||||
uniform float uGevSceneDepthOn;
|
||||
`;
|
||||
|
||||
const TEST = /* glsl */ `
|
||||
if (uGevSceneDepthOn > 0.5) {
|
||||
vec2 gevDepthSize = vec2(textureSize(tGevSceneDepth, 0));
|
||||
ivec2 gevTexel = ivec2(gl_FragCoord.xy * gevDepthSize / uGevTargetSize);
|
||||
float gevScene = unpackRGBAToDepth(texelFetch(tGevSceneDepth, gevTexel, 0));
|
||||
#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )
|
||||
float gevDepth = vIsPerspective == 0.0
|
||||
? gl_FragCoord.z
|
||||
: log2(vFragDepth) * logDepthBufFC * 0.5;
|
||||
#else
|
||||
float gevDepth = gl_FragCoord.z;
|
||||
#endif
|
||||
if (gevScene < 1.0 && gevDepth > gevScene + ${SCENE_DEPTH_BIAS.toExponential()})
|
||||
discard;
|
||||
}
|
||||
`;
|
||||
|
||||
/** Patch a fragment shader source with the scene-depth test. */
|
||||
export function withSceneDepthTest(fragmentShader) {
|
||||
const anchor = '#include <logdepthbuf_fragment>';
|
||||
if (!fragmentShader.includes(anchor))
|
||||
throw new Error('A scene-depth test needs logdepthbuf_fragment');
|
||||
const pars = fragmentShader.includes('#include <packing>')
|
||||
? PARS
|
||||
: `#include <packing>\n${PARS}`;
|
||||
return fragmentShader
|
||||
.replace('void main() {', `${pars}\nvoid main() {`)
|
||||
.replace(anchor, `${anchor}\n${TEST}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* One scene-depth test for a set of transparent meshes: `material()` patches
|
||||
* each material, `object()` binds the depth before each draw, and the mesh
|
||||
* descriptor calls `attach(ctx)` with its view context once created.
|
||||
*/
|
||||
export function createSceneDepthTest() {
|
||||
const uniforms = {
|
||||
tGevSceneDepth: { value: null },
|
||||
uGevTargetSize: { value: new Vector2(1, 1) },
|
||||
uGevSceneDepthOn: { value: 0 },
|
||||
};
|
||||
let ctx = null;
|
||||
const bind = (renderer) => {
|
||||
const mrt = ctx?.findEffect?.('mrt');
|
||||
const texture =
|
||||
mrt?.depthBufferPacking === RGBADepthPacking ? mrt.depthBuffer : null;
|
||||
uniforms.tGevSceneDepth.value = texture ?? null;
|
||||
uniforms.uGevSceneDepthOn.value = texture ? 1 : 0;
|
||||
const target = renderer.getRenderTarget?.();
|
||||
if (target) uniforms.uGevTargetSize.value.set(target.width, target.height);
|
||||
else renderer.getDrawingBufferSize?.(uniforms.uGevTargetSize.value);
|
||||
};
|
||||
const test = {
|
||||
uniforms,
|
||||
material(material) {
|
||||
const previousCompile = material.onBeforeCompile;
|
||||
const previousKey = material.customProgramCacheKey;
|
||||
material.onBeforeCompile = (shader, renderer) => {
|
||||
Object.assign(shader.uniforms, uniforms);
|
||||
shader.fragmentShader = withSceneDepthTest(shader.fragmentShader);
|
||||
previousCompile?.call(material, shader, renderer);
|
||||
};
|
||||
material.customProgramCacheKey = () =>
|
||||
`${previousKey ? previousKey.call(material) : ''}_gevSceneDepth`;
|
||||
material.needsUpdate = true;
|
||||
return material;
|
||||
},
|
||||
object(object) {
|
||||
const previous = object.onBeforeRender;
|
||||
object.onBeforeRender = function onBeforeRender(renderer, ...rest) {
|
||||
bind(renderer);
|
||||
return previous?.call(this, renderer, ...rest);
|
||||
};
|
||||
object.userData[KEY] = test;
|
||||
return object;
|
||||
},
|
||||
attach(next) {
|
||||
ctx = next ?? null;
|
||||
},
|
||||
};
|
||||
return test;
|
||||
}
|
||||
|
||||
/** Give an object's scene-depth test (if any) its descriptor's context. */
|
||||
export function attachSceneDepth(object, ctx) {
|
||||
object?.userData?.[KEY]?.attach(ctx);
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
BasicDepthPacking,
|
||||
DataTexture,
|
||||
MeshBasicMaterial,
|
||||
RGBADepthPacking,
|
||||
ShaderLib,
|
||||
} from 'three';
|
||||
import {
|
||||
SCENE_DEPTH_BIAS,
|
||||
createSceneDepthTest,
|
||||
withSceneDepthTest,
|
||||
} from './navaraSceneDepth.js';
|
||||
import { createStemBatch } from './navaraStems.js';
|
||||
import { createNavaraPackRenderer } from '../../../scenes/dataPacks/navara.js';
|
||||
|
||||
const count = (text, part) => text.split(part).length - 1;
|
||||
|
||||
test('the scene-depth test goes after the log depth and brings packing once', () => {
|
||||
for (const source of [
|
||||
ShaderLib.basic.fragmentShader,
|
||||
'#include <common>\n#include <packing>\nvoid main() {\n#include <logdepthbuf_fragment>\n}',
|
||||
]) {
|
||||
const patched = withSceneDepthTest(source);
|
||||
assert.equal(count(patched, '#include <packing>'), 1);
|
||||
assert.equal(count(patched, 'uniform sampler2D tGevSceneDepth;'), 1);
|
||||
const depth = patched.indexOf('#include <logdepthbuf_fragment>');
|
||||
const test = patched.indexOf('if (uGevSceneDepthOn > 0.5)');
|
||||
assert.ok(depth >= 0 && test > depth, 'tested after gl_FragDepth is set');
|
||||
assert.ok(
|
||||
patched.indexOf('uniform float uGevSceneDepthOn;') <
|
||||
patched.indexOf('void main() {'),
|
||||
);
|
||||
assert.match(patched, /gevDepth > gevScene \+ 5e-6\)\s*discard;/);
|
||||
}
|
||||
assert.ok(SCENE_DEPTH_BIAS > 0 && SCENE_DEPTH_BIAS < 1e-4);
|
||||
assert.throws(() => withSceneDepthTest('void main() {}'), /logdepthbuf/);
|
||||
});
|
||||
|
||||
test('the test binds the MRT pass packed depth before each draw, and only packed RGBA depth', () => {
|
||||
const depth = createSceneDepthTest();
|
||||
const material = depth.material(new MeshBasicMaterial());
|
||||
const shader = {
|
||||
uniforms: {},
|
||||
vertexShader: ShaderLib.basic.vertexShader,
|
||||
fragmentShader: ShaderLib.basic.fragmentShader,
|
||||
};
|
||||
material.onBeforeCompile(shader, null);
|
||||
assert.match(material.customProgramCacheKey(), /_gevSceneDepth$/);
|
||||
assert.equal(shader.uniforms.tGevSceneDepth, depth.uniforms.tGevSceneDepth);
|
||||
assert.match(shader.fragmentShader, /discard;/);
|
||||
|
||||
const drawn = [];
|
||||
const object = depth.object({
|
||||
userData: {},
|
||||
onBeforeRender: (renderer) => drawn.push(renderer),
|
||||
});
|
||||
const texture = new DataTexture();
|
||||
const mrt = { depthBuffer: texture, depthBufferPacking: RGBADepthPacking };
|
||||
const renderer = { getRenderTarget: () => ({ width: 640, height: 360 }) };
|
||||
// Before the descriptor attaches its context the test stays off.
|
||||
object.onBeforeRender(renderer);
|
||||
assert.equal(depth.uniforms.uGevSceneDepthOn.value, 0);
|
||||
depth.attach({ findEffect: (key) => (key === 'mrt' ? mrt : undefined) });
|
||||
object.onBeforeRender(renderer);
|
||||
assert.equal(depth.uniforms.tGevSceneDepth.value, texture);
|
||||
assert.equal(depth.uniforms.uGevSceneDepthOn.value, 1);
|
||||
assert.deepEqual(depth.uniforms.uGevTargetSize.value.toArray(), [640, 360]);
|
||||
assert.deepEqual(drawn, [renderer, renderer], 'the mesh hook still runs');
|
||||
// Another packing would be read wrong: the mesh draws as before.
|
||||
mrt.depthBufferPacking = BasicDepthPacking;
|
||||
object.onBeforeRender(renderer);
|
||||
assert.equal(depth.uniforms.uGevSceneDepthOn.value, 0);
|
||||
assert.equal(depth.uniforms.tGevSceneDepth.value, null);
|
||||
});
|
||||
|
||||
/** A Navara view that builds each descriptor as the SDK does. */
|
||||
function fakeView() {
|
||||
const kinds = new Map();
|
||||
const created = [];
|
||||
const mrt = {
|
||||
depthBuffer: new DataTexture(),
|
||||
depthBufferPacking: RGBADepthPacking,
|
||||
};
|
||||
const ctx = { findEffect: (key) => (key === 'mrt' ? mrt : undefined) };
|
||||
class MeshDesc {
|
||||
constructor(view, context, config) {
|
||||
this.ctx = context;
|
||||
this.config = config;
|
||||
}
|
||||
onDestroy() {}
|
||||
}
|
||||
const view = {
|
||||
registerMesh: (kind, desc) => kinds.set(kind, desc),
|
||||
addMesh(config) {
|
||||
const kind = Object.keys(config)[0];
|
||||
const desc = new (kinds.get(kind))(view, ctx, config);
|
||||
const object = desc.createMesh();
|
||||
created.push({ kind, pass: desc.getPassKey(), object });
|
||||
return { delete: () => desc.onDestroy() };
|
||||
},
|
||||
};
|
||||
return { view, MeshDesc, created, mrt };
|
||||
}
|
||||
|
||||
/** Every renderable under an object compiles with the test and binds depth. */
|
||||
function assertTested(object, mrt) {
|
||||
const renderables = [];
|
||||
object.traverse?.((child) => {
|
||||
if (child.material) renderables.push(child);
|
||||
});
|
||||
if (!object.traverse && object.material) renderables.push(object);
|
||||
assert.ok(renderables.length > 0);
|
||||
for (const child of renderables) {
|
||||
const shader = {
|
||||
uniforms: { ...(child.material.uniforms ?? {}) },
|
||||
vertexShader: '',
|
||||
fragmentShader:
|
||||
child.material.fragmentShader ?? ShaderLib.basic.fragmentShader,
|
||||
};
|
||||
child.material.onBeforeCompile(shader, null);
|
||||
assert.match(shader.fragmentShader, /if \(uGevSceneDepthOn > 0\.5\)/);
|
||||
assert.ok(shader.uniforms.tGevSceneDepth);
|
||||
child.onBeforeRender(
|
||||
{
|
||||
getRenderTarget: () => ({ width: 8, height: 8 }),
|
||||
getViewport: (target) => target.set(0, 0, 8, 8),
|
||||
},
|
||||
null,
|
||||
null,
|
||||
child.geometry,
|
||||
child.material,
|
||||
);
|
||||
assert.equal(shader.uniforms.tGevSceneDepth.value, mrt.depthBuffer);
|
||||
assert.equal(shader.uniforms.uGevSceneDepthOn.value, 1);
|
||||
}
|
||||
}
|
||||
|
||||
test('stems and pack meshes stay transparent and test the scene depth', () => {
|
||||
const stems = fakeView();
|
||||
createStemBatch(stems, 0xffffff, 4);
|
||||
const packs = fakeView();
|
||||
const owned = [];
|
||||
const engine = { render: { request() {} } };
|
||||
createNavaraPackRenderer(engine, packs).image(
|
||||
{ id: 'p', placement: { bounds: [0, 0, 0.01, 0.01], height: 300 } },
|
||||
{},
|
||||
{ own: (fn) => owned.push(fn) },
|
||||
);
|
||||
for (const { created, mrt } of [stems, packs]) {
|
||||
assert.equal(created.length, 1);
|
||||
assert.equal(created[0].pass, 'transparent', created[0].kind);
|
||||
assertTested(created[0].object, mrt);
|
||||
}
|
||||
for (const release of owned) release();
|
||||
});
|
||||
@@ -1,11 +1,18 @@
|
||||
import { LineSegments2 } from 'three/addons/lines/LineSegments2.js';
|
||||
import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';
|
||||
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
||||
import { attachSceneDepth, createSceneDepthTest } from './navaraSceneDepth.js';
|
||||
|
||||
const KEY = 'gevInfrastructureStems';
|
||||
const views = new WeakSet();
|
||||
/** All visible stems use one draw and one fixed buffer; tips are a point bank. */
|
||||
export function createStemBatch({ view, MeshDesc }, color, capacity, width = 3.5, opacity = 1) {
|
||||
export function createStemBatch(
|
||||
{ view, MeshDesc },
|
||||
color,
|
||||
capacity,
|
||||
width = 3.5,
|
||||
opacity = 1,
|
||||
) {
|
||||
if (!views.has(view)) {
|
||||
view.registerMesh(
|
||||
KEY,
|
||||
@@ -14,10 +21,13 @@ export function createStemBatch({ view, MeshDesc }, color, capacity, width = 3.5
|
||||
super(host, ctx, config);
|
||||
this.batch = config[KEY];
|
||||
}
|
||||
// Transparent, so the effects leave its look alone; the scene-depth
|
||||
// test hides a stem behind terrain, 3D tiles and the Earth.
|
||||
getPassKey() {
|
||||
return 'transparent';
|
||||
}
|
||||
createMesh() {
|
||||
attachSceneDepth(this.batch.line, this.ctx);
|
||||
return this.batch.line;
|
||||
}
|
||||
onDestroy() {
|
||||
@@ -39,7 +49,9 @@ export function createStemBatch({ view, MeshDesc }, color, capacity, width = 3.5
|
||||
transparent: true,
|
||||
toneMapped: false,
|
||||
});
|
||||
const line = new LineSegments2(geometry, material);
|
||||
const depthTest = createSceneDepthTest();
|
||||
depthTest.material(material);
|
||||
const line = depthTest.object(new LineSegments2(geometry, material));
|
||||
line.frustumCulled = false;
|
||||
const holder = view.addMesh({
|
||||
[KEY]: {
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
SRGBColorSpace,
|
||||
Texture,
|
||||
} from 'three';
|
||||
import {
|
||||
attachSceneDepth,
|
||||
createSceneDepthTest,
|
||||
} from '../../layers/shared/render/navaraSceneDepth.js';
|
||||
const KEY = 'gevScenePack',
|
||||
registered = new WeakSet();
|
||||
const cyan = [0, 1, 1, 1],
|
||||
@@ -24,10 +28,13 @@ export function createNavaraPackRenderer(engine, { view, MeshDesc }) {
|
||||
super(host, ctx, config);
|
||||
this.object = config[KEY];
|
||||
}
|
||||
// Transparent, so the effects leave its look alone; the scene-depth
|
||||
// test hides a pack behind terrain, 3D tiles and the Earth.
|
||||
getPassKey() {
|
||||
return 'transparent';
|
||||
}
|
||||
createMesh() {
|
||||
attachSceneDepth(this.object.group, this.ctx);
|
||||
return this.object.group;
|
||||
}
|
||||
onDestroy() {
|
||||
@@ -39,7 +46,8 @@ export function createNavaraPackRenderer(engine, { view, MeshDesc }) {
|
||||
registered.add(view);
|
||||
}
|
||||
const meshGroup = (handle) => {
|
||||
const group = new Group(),
|
||||
const depthTest = createSceneDepthTest();
|
||||
const group = depthTest.object(new Group()),
|
||||
owned = [];
|
||||
let released = false;
|
||||
const dispose = () => {
|
||||
@@ -62,7 +70,7 @@ export function createNavaraPackRenderer(engine, { view, MeshDesc }) {
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
return { group, owned };
|
||||
return { group, owned, depthTest };
|
||||
};
|
||||
function addMesh(batch, shape, properties, id, texture) {
|
||||
if (!shape) return;
|
||||
@@ -99,6 +107,8 @@ export function createNavaraPackRenderer(engine, { view, MeshDesc }) {
|
||||
.copy(pigment)
|
||||
.multiplyScalar(1 / Math.max(1, view.toneMappingExposure ?? 1));
|
||||
};
|
||||
batch.depthTest.material(material);
|
||||
batch.depthTest.object(mesh);
|
||||
mesh.position.set(origin.x, origin.y, origin.z);
|
||||
mesh.userData.pickId = id;
|
||||
batch.group.add(mesh);
|
||||
|
||||
Reference in new issue
Block a user