contrails: implement simple robust contrail ribbon pass
This commit is contained in:
1 parent
adad36881a
commit
1da34c913b
1 file changed
+137
-104
Vendored
+137
-104
@@ -5390,6 +5390,65 @@ var planeV = null, planeVbo = null, planeProg = null, planeU = null, planeA = nu
|
|||||||
var ctBuf = null, ctVbo = null, ctVerts = 0;
|
var ctBuf = null, ctVbo = null, ctVerts = 0;
|
||||||
var ctProg = null, ctA = null, ctU = null;
|
var ctProg = null, ctA = null, ctU = null;
|
||||||
|
|
||||||
|
var CT2_SEG = 18;
|
||||||
|
var ct2Buf = null, ct2Vbo = null, ct2Verts = 0;
|
||||||
|
var ct2Prog = null, ct2A = null, ct2U = null;
|
||||||
|
|
||||||
|
function _ct2Vert(x, y, z, u, v, s) {
|
||||||
|
var i = ct2Verts * 6;
|
||||||
|
ct2Buf[i] = x; ct2Buf[i + 1] = y; ct2Buf[i + 2] = z;
|
||||||
|
ct2Buf[i + 3] = u; ct2Buf[i + 4] = v; ct2Buf[i + 5] = s;
|
||||||
|
ct2Verts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var CT2_VERT_SRC = [
|
||||||
|
'attribute vec3 aPos;',
|
||||||
|
'attribute float aU;',
|
||||||
|
'attribute float aV;',
|
||||||
|
'attribute float aS;',
|
||||||
|
'uniform mat4 uProj;',
|
||||||
|
'uniform mat4 uView;',
|
||||||
|
'uniform vec3 uCamPos;',
|
||||||
|
'uniform float uEarthRadius;',
|
||||||
|
'uniform float uCurvatureOn;',
|
||||||
|
'varying float vU;',
|
||||||
|
'varying float vV;',
|
||||||
|
'varying float vS;',
|
||||||
|
'varying vec3 vWorld;',
|
||||||
|
'void main() {',
|
||||||
|
'vU = aU; vV = aV; vS = aS;',
|
||||||
|
'vec3 pos = aPos;',
|
||||||
|
'if (uCurvatureOn > 0.5) {',
|
||||||
|
'vec2 d = pos.xz - uCamPos.xz;',
|
||||||
|
'pos.y -= dot(d, d) / (2.0 * max(1.0, uEarthRadius));',
|
||||||
|
'}',
|
||||||
|
'vWorld = pos;',
|
||||||
|
'gl_Position = uProj * uView * vec4(pos, 1.0);',
|
||||||
|
'}'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var CT2_FRAG_SRC = [
|
||||||
|
'precision mediump float;',
|
||||||
|
'uniform float uDay;',
|
||||||
|
'uniform float uNight;',
|
||||||
|
'varying float vU;',
|
||||||
|
'varying float vV;',
|
||||||
|
'varying float vS;',
|
||||||
|
'varying vec3 vWorld;',
|
||||||
|
'void main() {',
|
||||||
|
'float side = abs(vV);',
|
||||||
|
'float edge = 1.0 - smoothstep(0.25, 1.0, side);',
|
||||||
|
'float head = 0.35 + 0.65 * vU;',
|
||||||
|
'float a = clamp(vS * edge * head, 0.0, 0.85);',
|
||||||
|
'if (a <= 0.004) discard;',
|
||||||
|
'vec3 dayCol = vec3(1.0, 1.0, 1.0);',
|
||||||
|
'vec3 nightCol = vec3(0.45, 0.55, 0.75);',
|
||||||
|
'vec3 col = mix(nightCol, dayCol, clamp(uDay, 0.0, 1.0));',
|
||||||
|
'col *= 0.35 + 0.65 * clamp(uDay + uNight, 0.0, 1.0);',
|
||||||
|
'gl_FragColor = vec4(col, a);',
|
||||||
|
'}'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
function _ctVert(x, y, z, u, v, s, p) {
|
function _ctVert(x, y, z, u, v, s, p) {
|
||||||
var i = ctVerts * 7;
|
var i = ctVerts * 7;
|
||||||
ctBuf[i] = x; ctBuf[i + 1] = y; ctBuf[i + 2] = z;
|
ctBuf[i] = x; ctBuf[i + 1] = y; ctBuf[i + 2] = z;
|
||||||
@@ -6799,138 +6858,113 @@ window.clouds3dRender = function(target) {
|
|||||||
gl.depthMask(true);
|
gl.depthMask(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// -- contrails --
|
// -- contrails: simple robust ribbon pass --
|
||||||
// Only CONFIG.flights.contrailChance of the aircraft leave a
|
|
||||||
// persistent stripe (the ice-humidity window is not everywhere),
|
|
||||||
// and the stripe follows the curved leg: walked backwards from
|
|
||||||
// the aircraft in fixed steps. Visible by day (sun-lit) AND by
|
|
||||||
// night as dimmed moonlit streaks.
|
|
||||||
var tvis = Math.max(dayf, carNight * CT_NIGHT);
|
var tvis = Math.max(dayf, carNight * CT_NIGHT);
|
||||||
if (tvis > 0.02) {
|
if (tvis > 0.01) {
|
||||||
if (!ctBuf) ctBuf = new Float32Array(CT_MAX * CT_SEG * 6 * 7);
|
if (!ct2Buf) ct2Buf = new Float32Array(FLT_MAX * CT2_SEG * 6 * 6);
|
||||||
ctVerts = 0;
|
ct2Verts = 0;
|
||||||
var _cs = { x: 0, z: 0, tx: 1, tz: 0 };
|
var _cs2 = { x: 0, z: 0, tx: 1, tz: 0 };
|
||||||
var _sk0 = { x: 0, z: 0, px: 0, pz: 0, u: 0 };
|
|
||||||
var _sk1 = { x: 0, z: 0, px: 0, pz: 0, u: 0 };
|
|
||||||
for (var cj = 0; cj < fltCount; cj++) {
|
for (var cj = 0; cj < fltCount; cj++) {
|
||||||
if (ctVerts + CT_SEG * 6 > CT_MAX * CT_SEG * 6) break;
|
if (ct2Verts + CT2_SEG * 6 > ct2Buf.length / 6) break;
|
||||||
var stc = fltState[cj];
|
var stc = fltState[cj];
|
||||||
if (!stc || stc.gnd) continue;
|
if (!stc || stc.gnd) continue;
|
||||||
if (fltRand(cj * 3.7 + 0.77) > CT_CHANCE) continue;
|
if (fltRand(cj * 3.7 + 0.77) > CT_CHANCE) continue;
|
||||||
// formation gate: above the configured trail altitude, smooth
|
var gt = (stc.alt - (CT_START_FRAC - 0.10)) / 0.18;
|
||||||
var gt = (stc.alt - (CT_START_FRAC - 0.06)) / 0.12;
|
gt = Math.max(0, Math.min(1, gt));
|
||||||
gt = gt < 0 ? 0 : (gt > 1 ? 1 : gt);
|
if (gt < 0.03) continue;
|
||||||
gt = gt * gt * (3 - 2 * gt);
|
var tl = Math.min(Math.max(1.0, CT_T * fltData[cj].v), stc.s);
|
||||||
if (gt < 0.05) continue;
|
if (tl < 0.2) continue;
|
||||||
var tl = Math.min(CT_T * fltData[cj].v, stc.s);
|
|
||||||
if (tl < 0.05) continue;
|
|
||||||
fltLegAt(stc.leg, stc.s - tl * 0.5, _cs);
|
|
||||||
var cdx = _cs.x - camX, cdy = stc.y - camY, cdz = _cs.z - camZ;
|
|
||||||
var distc = Math.sqrt(cdx * cdx + cdy * cdy + cdz * cdz);
|
|
||||||
// distance fade: full out to 35% of contrailFadeKm, gone at
|
|
||||||
// contrailFadeKm. Trails that thin only at 200-340 km were
|
|
||||||
// the remaining shimmer at 4x pixel scale.
|
|
||||||
var dfc = 1 - (distc - CT_FADE_START) / (CT_FADE_END - CT_FADE_START);
|
|
||||||
if (dfc > 1) dfc = 1; else if (dfc <= 0.02) continue;
|
|
||||||
// sun-lit by day, moonlit after dusk. dayf alone made the
|
|
||||||
// stripes vanish completely at night although the pass ran.
|
|
||||||
var lit = Math.max(dayf, carNight * CT_NIGHT);
|
var lit = Math.max(dayf, carNight * CT_NIGHT);
|
||||||
var strg = gt * lit * dfc;
|
var strg = Math.max(0.30, gt * lit);
|
||||||
if (strg < 0.18) strg = 0.18;
|
|
||||||
// widen with distance: a fixed 310 m ribbon is sub-pixel at
|
|
||||||
// 150 km and blends away; grow it to hold ~2-4 screen px
|
|
||||||
var hw = CT_HALF_W + distc * 0.0025;
|
|
||||||
var ph = fltData[cj].phase;
|
|
||||||
var hy = stc.y;
|
var hy = stc.y;
|
||||||
var prev = null, cur = _sk0;
|
var prev = null;
|
||||||
for (var sk = 0; sk <= CT_SEG; sk++) {
|
var cur = { x: 0, z: 0, px: 0, pz: 0, u: 0 };
|
||||||
var dd = stc.s - tl + tl * (sk / CT_SEG);
|
for (var sk = 0; sk <= CT2_SEG; sk++) {
|
||||||
|
var dd = stc.s - tl + tl * (sk / CT2_SEG);
|
||||||
if (dd < 0) dd = 0;
|
if (dd < 0) dd = 0;
|
||||||
fltLegAt(stc.leg, dd, _cs);
|
fltLegAt(stc.leg, dd, _cs2);
|
||||||
cur.x = _cs.x; cur.z = _cs.z;
|
var dx = _cs2.x - camX, dz = _cs2.z - camZ, dy = hy - camY;
|
||||||
cur.px = _cs.tz * hw; cur.pz = -_cs.tx * hw;
|
var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
cur.u = sk / CT_SEG;
|
var hw = CT_HALF_W + dist * 0.004;
|
||||||
|
var tx = _cs2.tx, tz = _cs2.tz;
|
||||||
|
var sl = Math.hypot(tx, tz) || 1;
|
||||||
|
tx /= sl; tz /= sl;
|
||||||
|
cur.x = _cs2.x; cur.z = _cs2.z;
|
||||||
|
cur.px = tz * hw; cur.pz = -tx * hw;
|
||||||
|
cur.u = sk / CT2_SEG;
|
||||||
if (prev) {
|
if (prev) {
|
||||||
_ctVert(prev.x + prev.px, hy, prev.z + prev.pz, prev.u, 1, strg, ph);
|
_ct2Vert(prev.x + prev.px, hy, prev.z + prev.pz, prev.u, 1, strg);
|
||||||
_ctVert(prev.x - prev.px, hy, prev.z - prev.pz, prev.u, -1, strg, ph);
|
_ct2Vert(prev.x - prev.px, hy, prev.z - prev.pz, prev.u, -1, strg);
|
||||||
_ctVert(cur.x - cur.px, hy, cur.z - cur.pz, cur.u, -1, strg, ph);
|
_ct2Vert(cur.x - cur.px, hy, cur.z - cur.pz, cur.u, -1, strg);
|
||||||
_ctVert(prev.x + prev.px, hy, prev.z + prev.pz, prev.u, 1, strg, ph);
|
_ct2Vert(prev.x + prev.px, hy, prev.z + prev.pz, prev.u, 1, strg);
|
||||||
_ctVert(cur.x - cur.px, hy, cur.z - cur.pz, cur.u, -1, strg, ph);
|
_ct2Vert(cur.x - cur.px, hy, cur.z - cur.pz, cur.u, -1, strg);
|
||||||
_ctVert(cur.x + cur.px, hy, cur.z + cur.pz, cur.u, 1, strg, ph);
|
_ct2Vert(cur.x + cur.px, hy, cur.z + cur.pz, cur.u, 1, strg);
|
||||||
}
|
}
|
||||||
prev = cur; cur = (cur === _sk0) ? _sk1 : _sk0;
|
prev = cur;
|
||||||
|
cur = { x: 0, z: 0, px: 0, pz: 0, u: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ctVerts > 0) {
|
if (ct2Verts > 0) {
|
||||||
if (!ctVbo) ctVbo = gl.createBuffer();
|
if (!ct2Vbo) ct2Vbo = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, ctVbo);
|
gl.bindBuffer(gl.ARRAY_BUFFER, ct2Vbo);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, ctBuf.subarray(0, ctVerts * 7), gl.STREAM_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, ct2Buf.subarray(0, ct2Verts * 6), gl.STREAM_DRAW);
|
||||||
if (!ctProg) {
|
if (!ct2Prog) {
|
||||||
var cpm = makeProgram(gl, CT_VERT_SRC, CT_FRAG_SRC);
|
var c2p = makeProgram(gl, CT2_VERT_SRC, CT2_FRAG_SRC);
|
||||||
if (cpm) {
|
if (c2p) {
|
||||||
ctProg = cpm;
|
ct2Prog = c2p;
|
||||||
ctA = { pos: gl.getAttribLocation(cpm, 'aPos'),
|
ct2A = {
|
||||||
u: gl.getAttribLocation(cpm, 'aU'),
|
pos: gl.getAttribLocation(c2p, 'aPos'),
|
||||||
v: gl.getAttribLocation(cpm, 'aV'),
|
u: gl.getAttribLocation(c2p, 'aU'),
|
||||||
s: gl.getAttribLocation(cpm, 'aS'),
|
v: gl.getAttribLocation(c2p, 'aV'),
|
||||||
p: gl.getAttribLocation(cpm, 'aP') };
|
s: gl.getAttribLocation(c2p, 'aS')
|
||||||
ctU = { proj: gl.getUniformLocation(cpm, 'uProj'),
|
};
|
||||||
view: gl.getUniformLocation(cpm, 'uView'),
|
ct2U = {
|
||||||
camPos: gl.getUniformLocation(cpm, 'uCamPos'),
|
proj: gl.getUniformLocation(c2p, 'uProj'),
|
||||||
earthRadius: gl.getUniformLocation(cpm, 'uEarthRadius'),
|
view: gl.getUniformLocation(c2p, 'uView'),
|
||||||
curvatureOn: gl.getUniformLocation(cpm, 'uCurvatureOn'),
|
camPos: gl.getUniformLocation(c2p, 'uCamPos'),
|
||||||
sunDir: gl.getUniformLocation(cpm, 'uSunDir'),
|
earthRadius: gl.getUniformLocation(c2p, 'uEarthRadius'),
|
||||||
sunColor: gl.getUniformLocation(cpm, 'uSunColor'),
|
curvatureOn: gl.getUniformLocation(c2p, 'uCurvatureOn'),
|
||||||
sunInt: gl.getUniformLocation(cpm, 'uSunInt'),
|
day: gl.getUniformLocation(c2p, 'uDay'),
|
||||||
moonColor: gl.getUniformLocation(cpm, 'uMoonColor'),
|
night: gl.getUniformLocation(c2p, 'uNight')
|
||||||
moonInt: gl.getUniformLocation(cpm, 'uMoonInt'),
|
};
|
||||||
skyAmb: gl.getUniformLocation(cpm, 'uSkyAmb'),
|
|
||||||
starAmb: gl.getUniformLocation(cpm, 'uStarAmb'),
|
|
||||||
nightBoost: gl.getUniformLocation(cpm, 'uNightBoost') };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ctProg) {
|
if (ct2Prog) {
|
||||||
gl.useProgram(ctProg);
|
gl.useProgram(ct2Prog);
|
||||||
gl.uniformMatrix4fv(ctU.proj, false, proj);
|
gl.uniformMatrix4fv(ct2U.proj, false, proj);
|
||||||
gl.uniformMatrix4fv(ctU.view, false, view);
|
gl.uniformMatrix4fv(ct2U.view, false, view);
|
||||||
gl.uniform3f(ctU.camPos, camX, camY, camZ);
|
gl.uniform3f(ct2U.camPos, camX, camY, camZ);
|
||||||
gl.uniform1f(ctU.earthRadius, EARTH_RADIUS_UNITS);
|
gl.uniform1f(ct2U.earthRadius, EARTH_RADIUS_UNITS);
|
||||||
gl.uniform1f(ctU.curvatureOn, CURVATURE_ON ? 1.0 : 0.0);
|
gl.uniform1f(ct2U.curvatureOn, CURVATURE_ON ? 1.0 : 0.0);
|
||||||
gl.uniform3f(ctU.sunDir, sd[0], sd[1], sd[2]);
|
gl.uniform1f(ct2U.day, dayf);
|
||||||
gl.uniform3f(ctU.sunColor, _beam[0], _beam[1], _beam[2]);
|
gl.uniform1f(ct2U.night, carNight);
|
||||||
gl.uniform1f(ctU.sunInt, _beamL);
|
gl.enableVertexAttribArray(ct2A.pos);
|
||||||
gl.uniform3f(ctU.moonColor, _mBeam[0], _mBeam[1], _mBeam[2]);
|
gl.vertexAttribPointer(ct2A.pos, 3, gl.FLOAT, false, 24, 0);
|
||||||
gl.uniform1f(ctU.moonInt, _moonI);
|
gl.enableVertexAttribArray(ct2A.u);
|
||||||
gl.uniform1f(ctU.skyAmb, _amb * ATMO_SKY_GAIN);
|
gl.vertexAttribPointer(ct2A.u, 1, gl.FLOAT, false, 24, 12);
|
||||||
gl.uniform1f(ctU.starAmb, _star);
|
gl.enableVertexAttribArray(ct2A.v);
|
||||||
gl.uniform1f(ctU.nightBoost, 1.0 + carNight * (CT_NIGHT_BOOST - 1.0));
|
gl.vertexAttribPointer(ct2A.v, 1, gl.FLOAT, false, 24, 16);
|
||||||
gl.enableVertexAttribArray(ctA.pos);
|
gl.enableVertexAttribArray(ct2A.s);
|
||||||
gl.vertexAttribPointer(ctA.pos, 3, gl.FLOAT, false, 28, 0);
|
gl.vertexAttribPointer(ct2A.s, 1, gl.FLOAT, false, 24, 20);
|
||||||
gl.enableVertexAttribArray(ctA.u);
|
|
||||||
gl.vertexAttribPointer(ctA.u, 1, gl.FLOAT, false, 28, 12);
|
|
||||||
gl.enableVertexAttribArray(ctA.v);
|
|
||||||
gl.vertexAttribPointer(ctA.v, 1, gl.FLOAT, false, 28, 16);
|
|
||||||
gl.enableVertexAttribArray(ctA.s);
|
|
||||||
gl.vertexAttribPointer(ctA.s, 1, gl.FLOAT, false, 28, 20);
|
|
||||||
gl.enableVertexAttribArray(ctA.p);
|
|
||||||
gl.vertexAttribPointer(ctA.p, 1, gl.FLOAT, false, 28, 24);
|
|
||||||
gl.disable(gl.DEPTH_TEST);
|
gl.disable(gl.DEPTH_TEST);
|
||||||
gl.depthMask(false);
|
gl.depthMask(false);
|
||||||
gl.enable(gl.BLEND);
|
gl.enable(gl.BLEND);
|
||||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, ctVerts);
|
gl.drawArrays(gl.TRIANGLES, 0, ct2Verts);
|
||||||
gl.disable(gl.BLEND);
|
gl.disable(gl.BLEND);
|
||||||
gl.depthMask(true);
|
gl.depthMask(true);
|
||||||
gl.enable(gl.DEPTH_TEST);
|
gl.enable(gl.DEPTH_TEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Diagnostic snapshot: how many aircraft are in range, how many
|
// Diagnostic snapshot: how many aircraft are in range, how many
|
||||||
// meshes were actually drawn and how much contrail geometry made
|
// meshes were actually drawn and how much contrail geometry made
|
||||||
// it past the distance gates.
|
// it past the distance gates.
|
||||||
window.__flightDbg = {
|
window.__flightDbg = {
|
||||||
aircraft: fltCount,
|
aircraft: fltCount,
|
||||||
meshDrawn: _meshDrawn,
|
meshDrawn: _meshDrawn,
|
||||||
ctVerts: ctVerts,
|
ctVerts: ct2Verts,
|
||||||
day: +dayf.toFixed(2),
|
day: +dayf.toFixed(2),
|
||||||
night: +carNight.toFixed(2),
|
night: +carNight.toFixed(2),
|
||||||
meshRangeKm: Math.round(16 * KM_PER_TILE)
|
meshRangeKm: Math.round(16 * KM_PER_TILE)
|
||||||
@@ -6939,7 +6973,6 @@ window.clouds3dRender = function(target) {
|
|||||||
_flightDbgT = now;
|
_flightDbgT = now;
|
||||||
dbgLog('[clouds3d] flights:', JSON.stringify(window.__flightDbg));
|
dbgLog('[clouds3d] flights:', JSON.stringify(window.__flightDbg));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore default state for the next frame.
|
// Restore default state for the next frame.
|
||||||
|
|||||||
Reference in new issue
Block a user