mapspinner 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/workflows/fps-perf.js +147 -0
- package/.claude/workflows/optimize-dna.js +201 -0
- package/.claude/workflows/shader-bottleneck-dna.js +168 -0
- package/.claude/workflows/speed-dna.js +209 -0
- package/.claude/workflows/startup-perf.js +117 -0
- package/.github/workflows/publish.yml +43 -0
- package/AGENTS.md +265 -0
- package/CHANGELOG.md +31 -0
- package/CLAUDE.md +1 -0
- package/README.md +82 -0
- package/examples/basic-sdk-usage.html +114 -0
- package/package.json +28 -0
- package/planet.html +2181 -0
- package/planet.zip +0 -0
- package/scripts/backend-ab.mjs +88 -0
- package/scripts/dev-chrome.cmd +22 -0
- package/scripts/verify.mjs +69 -0
- package/server.js +127 -0
- package/src/anchor-field.js +559 -0
- package/src/gl-render.js +944 -0
- package/src/index.js +41 -0
- package/src/planet-orchestrator.js +790 -0
- package/src/quadtree.js +160 -0
- package/src/shaders/atmosphere.glsl +215 -0
- package/src/shaders/terrain.glsl +2109 -0
- package/src/terrain-gen-controls.js +122 -0
- package/tests/run.js +58 -0
- package/textures/grass-color.jpg +0 -0
- package/textures/grass-displacement.jpg +0 -0
- package/textures/rock-color.jpg +0 -0
- package/textures/rock-displacement.jpg +0 -0
- package/textures/sand-color.jpg +0 -0
- package/textures/sand-displacement.jpg +0 -0
- package/textures/snow-color.jpg +0 -0
- package/textures/snow-displacement.jpg +0 -0
|
@@ -0,0 +1,2109 @@
|
|
|
1
|
+
// Proland authentic terrain render shader, ported to WebGL2 (#version 300 es) from
|
|
2
|
+
// vendor/proland-build/proland-git/terrain/examples/terrain2/terrainShader.glsl.
|
|
3
|
+
// VS: spherical deformation + CLOD blend (zf<->zc) of the wasm-driven elevation tiles.
|
|
4
|
+
// FS: sample normal + (placeholder) albedo, simple sun-lit Lambert with an ambient floor.
|
|
5
|
+
// Driven by the thin WebGL2 render layer (per-quad deformation uniforms + textureTile
|
|
6
|
+
// coords from the Proland wasm). No geometry/tess -> WebGL2-clean. The JS prepends
|
|
7
|
+
// #version + precision and compiles _VERTEX_ / _FRAGMENT_ separately.
|
|
8
|
+
|
|
9
|
+
// (Proland SamplerTile/textureTile atlas plumbing DELETED 2026-06-11 dead-code sweep: never set, never called.)
|
|
10
|
+
|
|
11
|
+
// --- DIRECT per-vertex sphere-projection uniforms (replaces the Proland corner-blend CLOD) ------
|
|
12
|
+
// SINGLE INSTANCED DRAW: defOffset (ox,oy,l,level) + the face frame are PER-INSTANCE now (one
|
|
13
|
+
// gl.drawElementsInstanced over the whole visible leaf set, no per-quad uniform churn). In the VS
|
|
14
|
+
// they come from instance attributes (iOffset + iFace); defRadius/defViewProjRel stay uniforms
|
|
15
|
+
// (same for every instance). The _PROBE_ collision program uses its own probeDir, not these.
|
|
16
|
+
// W7 highp ISLANDS: every world-scale quantity here is ~6.4e6 m and the projection cancels at that
|
|
17
|
+
// magnitude -- fp16 (mediump, range +-65504, ~3 sig digits) would shatter it. Declared highp under the
|
|
18
|
+
// mediump global default so the planet-scale fp32 cancellation fix (camera-relative vRel) stays intact.
|
|
19
|
+
uniform highp float defRadius; // R, sphere radius (~6.4e6 m)
|
|
20
|
+
uniform highp mat4 defViewProjNoEye; // proj*viewRel WITHOUT folded translate(-eye) -- for camera-relative VS pos
|
|
21
|
+
uniform highp vec3 defCamDir; // unit camera direction (eye/|eye|) -- vertex-jitter precision fix
|
|
22
|
+
uniform highp float defCamAlt; // camera altitude above the sphere (|eye|-R)
|
|
23
|
+
|
|
24
|
+
// HIERARCHICAL PARAMETER FIELD (HPF) continental texture (shared VS+FS): per-face 2D-arrays
|
|
25
|
+
// baked from anchor-field.js. W12 PACK (mob-w12): the old single RGBA32F (16B/texel) is split
|
|
26
|
+
// into TWO smaller textures -- hpfPool RG16F (R=seaBias[m], G=elevAmp; the two PRECISION-sensitive
|
|
27
|
+
// floats the geometry rides on) + hpfPool2 RG8 (R=temp, G=humid; both already [0,1] climate, 8-bit
|
|
28
|
+
// is ample). 4B+2B = 6B/texel vs 16B = 62% less HPF VRAM/bandwidth, with NO silhouette change
|
|
29
|
+
// (seaBias/elevAmp keep float precision; temp/humid quantize to 1/255, far below the biome
|
|
30
|
+
// soft-threshold widths). hpfSample DECODES both back into the same vec4 callers expect
|
|
31
|
+
// (seaBias, elevAmp, temp, humid) so every consumer (VS bias, vClimate, probe, heightbake) is
|
|
32
|
+
// unchanged. UNCONDITIONAL format (single version; no tier branch).
|
|
33
|
+
uniform sampler2DArray hpfPool; // RG16F: r=seaBias[m], g=elevAmp
|
|
34
|
+
uniform sampler2DArray hpfPool2; // RG8: r=temp[0,1], g=humid[0,1]
|
|
35
|
+
uniform int hasHpf;
|
|
36
|
+
uniform float uHpfInset; // HPF seam-inset sampler (window.__hpfInset; 0=centred bake default, 1=edge-inset seam->0). MUST match planet-orchestrator bakeFace fu mapping. Declared here (before hpfSample) so all stages see it.
|
|
37
|
+
// W11 FORMAT PROBE (NOT a quality tier): OES_texture_float_linear probed ONCE at init (gl-render.js
|
|
38
|
+
// sets this). 1 = the float/half-float atlas pools filter LINEAR in hardware -> the manual 4-tap
|
|
39
|
+
// bilinear in hpfSample collapses to a single hardware texture() call.
|
|
40
|
+
// 0 = the driver would silently fall back to NEAREST (the 'square steps / banding' regression) so the
|
|
41
|
+
// manual 4-tap is kept for correctness. Numerically equivalent either way -- a FORMAT-correctness
|
|
42
|
+
// branch, the one admissible runtime branch.
|
|
43
|
+
uniform int uFloatLinearOK;
|
|
44
|
+
// world unit dir -> cube face + uv[0,1] (matches anchor-field.js dirToFaceUV / FACE_FRAME).
|
|
45
|
+
void hpfFaceUV(vec3 d, out int face, out vec2 uv) {
|
|
46
|
+
vec3 a = abs(d);
|
|
47
|
+
float u, v, sc;
|
|
48
|
+
if (a.x >= a.y && a.x >= a.z) { sc = 1.0/a.x;
|
|
49
|
+
if (d.x > 0.0) { face = 0; u = -d.z*sc; } else { face = 1; u = d.z*sc; } v = d.y*sc; }
|
|
50
|
+
else if (a.y >= a.z) { sc = 1.0/a.y;
|
|
51
|
+
if (d.y > 0.0) { face = 2; v = -d.z*sc; } else { face = 3; v = d.z*sc; } u = d.x*sc; }
|
|
52
|
+
else { sc = 1.0/a.z;
|
|
53
|
+
if (d.z > 0.0) { face = 4; u = d.x*sc; } else { face = 5; u = -d.x*sc; } v = d.y*sc; }
|
|
54
|
+
uv = vec2(u*0.5 + 0.5, v*0.5 + 0.5);
|
|
55
|
+
}
|
|
56
|
+
highp vec4 hpfSample(vec3 dir) { // W7: R=seaBias is metres (~1600) -> highp so cbias keeps full precision
|
|
57
|
+
if (hasHpf == 0) return vec4(0.0, 1.0, 0.5, 0.5); // seaBias 0, elevAmp 1, temp/humid .5
|
|
58
|
+
int face; vec2 uv; hpfFaceUV(normalize(dir), face, uv);
|
|
59
|
+
// W11: when float-linear filters in hardware, one texture() per pool == the 4-tap result. The
|
|
60
|
+
// bake samples at texel centres so the centred (non-inset) hardware tap matches the manual one
|
|
61
|
+
// exactly; the inset bake keeps the manual taps (its tap centres are k/(sz-1), not the hardware
|
|
62
|
+
// (k+0.5)/sz grid) so it stays correct regardless.
|
|
63
|
+
// HARDWARE-TAP FAST PATH DISABLED for the crease fix: a single hardware texture() is HARDWARE
|
|
64
|
+
// BILINEAR = the SAME C0 texel-edge slope kink as raw manual weights, so on a float-linear GPU it
|
|
65
|
+
// bypassed the quintic-weight fix below and the seaBias crease persisted (user 2026-06-09). Always
|
|
66
|
+
// take the manual path so the quintic C2 weights apply to seaBias/elevAmp (geometry-critical). Cost:
|
|
67
|
+
// 4 taps vs 1, but only per-vertex in VS/PROBE (negligible; FS reads the interpolated varying).
|
|
68
|
+
// (uHpfInset>0.5 inset bake already used the manual path; this just unifies onto it always.)
|
|
69
|
+
// MANUAL BILINEAR (2026-06-06, user: 'elevation divided into squares -> square STEPS / STAIRS,
|
|
70
|
+
// should be smooth'). hpfSample drives vH = cbias + bShape (terrain.glsl ~743): cbias=seaBias and
|
|
71
|
+
// reliefMul/ridgeMul derive from this sample, so any quantization here steps the GEOMETRY in
|
|
72
|
+
// ~50km (128-texel/face) squares. The hpfPool texture is FLAGGED LINEAR (planet-orchestrator.js)
|
|
73
|
+
// but RGBA32F LINEAR filtering REQUIRES OES_texture_float_linear; if that extension is absent the
|
|
74
|
+
// driver SILENTLY falls back to NEAREST -> per-texel-cell constants -> the visible stairs (the
|
|
75
|
+
// orchestrator's own getExtension comment names this exact 'elevations look square from NEAREST'
|
|
76
|
+
// failure). Fix: bilinear in-shader so the field is smooth REGARDLESS of hardware float-linear --
|
|
77
|
+
// textureSize for the texel grid, 4 taps at the surrounding texel centres, 2D lerp. Tap coords are
|
|
78
|
+
// clamped to texel centres in [0.5/sz, 1-0.5/sz] = CLAMP_TO_EDGE equivalent, so this reproduces the
|
|
79
|
+
// intended hardware LINEAR exactly (no new cube-face seam) when the ext IS present, and fixes the
|
|
80
|
+
// steps when it is not.
|
|
81
|
+
vec2 sz = vec2(textureSize(hpfPool, 0).xy);
|
|
82
|
+
// SEAM-INSET MATCHED SAMPLER (hpf-seam-inset-bake, gated by uHpfInset to match the bake fu=x/(RES-1)).
|
|
83
|
+
// Inset: texel k sits at uv=k/(sz-1) (edge texels at 0 and 1), so t=uv*(sz-1) and the tap centres are
|
|
84
|
+
// k/(sz-1). Centred (default): texel k at uv=(k+0.5)/sz, t=uv*sz-0.5. Both must agree with the bake.
|
|
85
|
+
bool inset = uHpfInset > 0.5;
|
|
86
|
+
vec2 denom = inset ? (sz - 1.0) : sz;
|
|
87
|
+
vec2 t = inset ? (uv * denom) : (uv * sz - 0.5);
|
|
88
|
+
vec2 f = fract(t);
|
|
89
|
+
vec2 t0 = floor(t);
|
|
90
|
+
vec2 hb = 0.5 / sz; // half-texel in uv (clamp band, centred case)
|
|
91
|
+
vec2 c0 = inset ? (t0) / denom : clamp((t0 + 0.5) / sz, hb, 1.0 - hb);
|
|
92
|
+
vec2 c1 = inset ? (t0 + vec2(1.0, 0.0)) / denom : clamp((t0 + vec2(1.5, 0.5)) / sz, hb, 1.0 - hb);
|
|
93
|
+
vec2 c2 = inset ? (t0 + vec2(0.0, 1.0)) / denom : clamp((t0 + vec2(0.5, 1.5)) / sz, hb, 1.0 - hb);
|
|
94
|
+
vec2 c3 = inset ? (t0 + vec2(1.0, 1.0)) / denom : clamp((t0 + 1.5) / sz, hb, 1.0 - hb);
|
|
95
|
+
vec2 uv00 = clamp(c0, vec2(0.0), vec2(1.0));
|
|
96
|
+
vec2 uv10 = clamp(c1, vec2(0.0), vec2(1.0));
|
|
97
|
+
vec2 uv01 = clamp(c2, vec2(0.0), vec2(1.0));
|
|
98
|
+
vec2 uv11 = clamp(c3, vec2(0.0), vec2(1.0));
|
|
99
|
+
float ff = float(face);
|
|
100
|
+
// W12 DECODE: 4 bilinear taps from EACH packed texture, reassembled into the legacy
|
|
101
|
+
// vec4 (seaBias, elevAmp, temp, humid). Same uv/weights for both -> one bilinear field.
|
|
102
|
+
vec2 s00 = texture(hpfPool, vec3(uv00, ff)).rg; // (seaBias, elevAmp)
|
|
103
|
+
vec2 s10 = texture(hpfPool, vec3(uv10, ff)).rg;
|
|
104
|
+
vec2 s01 = texture(hpfPool, vec3(uv01, ff)).rg;
|
|
105
|
+
vec2 s11 = texture(hpfPool, vec3(uv11, ff)).rg;
|
|
106
|
+
// QUINTIC C2 bilinear weights (NOT raw f=fract(t)). Linear weights are C0 -> the interpolated
|
|
107
|
+
// seaBias/elevAmp SLOPE kinks at every texel-cell edge; seaBias is added RAW to h (cbias+bShape)
|
|
108
|
+
// so on a slope crossing a texel boundary that kink is a STRAIGHT ELEVATION CREASE (user 5 grids
|
|
109
|
+
// 2026-06-09: a vertical cliff at one grid column, 0.4-38m, all rows). Quintic w is value-identical
|
|
110
|
+
// at texel centres (w(0)=0,w(1)=1) so amplitude/field UNCHANGED -- only the inter-texel slope is C2.
|
|
111
|
+
// Same fix class as the vnoise2 quintic. SEAM-SAFE: the texel-centre clamp (above) is untouched.
|
|
112
|
+
vec2 w = f*f*f*(f*(f*6.0-15.0)+10.0);
|
|
113
|
+
vec2 se = mix(mix(s00, s10, w.x), mix(s01, s11, w.x), w.y);
|
|
114
|
+
vec2 t00 = texture(hpfPool2, vec3(uv00, ff)).rg; // (temp, humid)
|
|
115
|
+
vec2 t10 = texture(hpfPool2, vec3(uv10, ff)).rg;
|
|
116
|
+
vec2 t01 = texture(hpfPool2, vec3(uv01, ff)).rg;
|
|
117
|
+
vec2 t11 = texture(hpfPool2, vec3(uv11, ff)).rg;
|
|
118
|
+
vec2 th = mix(mix(t00, t10, w.x), mix(t01, t11, w.x), w.y); // same quintic C2 weights (temp/humid)
|
|
119
|
+
return vec4(se.x, se.y, th.x, th.y);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 3D value noise of a world-space point (continuous everywhere on the sphere). SHARED by the VS
|
|
123
|
+
// (broadShapeM continuous-field shape) AND the FS (riverMask drainage network), so it must live
|
|
124
|
+
// in the common preamble -- NOT inside #ifdef _VERTEX_, else the FS sees no snoise3 and the
|
|
125
|
+
// fragment program fails to link ('snoise3: no matching overloaded function'), aborting renderer init.
|
|
126
|
+
// W7 highp ISLAND: snoise3/shash3 lattice inputs reach freq*dir ~1.4e4 (fine octaves) and rely on
|
|
127
|
+
// integer floor()/fract() cell precision -- fp16 (~3 digits) would collapse every cell to mush. ALL
|
|
128
|
+
// noise primitives + their P/p args are highp; the [-1,1] RESULT narrows back to the mediump default.
|
|
129
|
+
highp float shash3(highp vec3 p){ p=fract(p*0.3183099+vec3(0.1,0.2,0.3)); p+=dot(p,p.yzx+19.19); return fract((p.x+p.y)*p.z + (p.y+p.z)*p.x); }
|
|
130
|
+
float snoise3(highp vec3 P){ highp vec3 i=floor(P),f=fract(P); highp vec3 u=f*f*(3.0-2.0*f);
|
|
131
|
+
float n000=shash3(i),n100=shash3(i+vec3(1,0,0)),n010=shash3(i+vec3(0,1,0)),n110=shash3(i+vec3(1,1,0));
|
|
132
|
+
float n001=shash3(i+vec3(0,0,1)),n101=shash3(i+vec3(1,0,1)),n011=shash3(i+vec3(0,1,1)),n111=shash3(i+vec3(1,1,1));
|
|
133
|
+
float x00=mix(n000,n100,u.x),x10=mix(n010,n110,u.x),x01=mix(n001,n101,u.x),x11=mix(n011,n111,u.x);
|
|
134
|
+
return mix(mix(x00,x10,u.y),mix(x01,x11,u.y),u.z)*2.0-1.0; } // [-1,1]
|
|
135
|
+
|
|
136
|
+
// ANALYTIC-DERIVATIVE value noise (IQ): returns vec4(value[-1,1], d/dPx, d/dPy, d/dPz). The exact
|
|
137
|
+
// gradient of the SAME trilinear+smoothstep field as snoise3 above, so the geometry it shapes and the
|
|
138
|
+
// normal it lights are the one field with NO finite-difference step to alias the fine octaves -- this
|
|
139
|
+
// is the core of the one-comprehensive-system design (mem tv8-one-system-design-analytic-deriv-fbm):
|
|
140
|
+
// the lit normal becomes exact at every scale, killing the deck flat-clay (the 200m/2000m FD steps
|
|
141
|
+
// averaged the fine octaves out). du = derivative of the smoothstep weight 6f(1-f).
|
|
142
|
+
// (snoise3D analytic-derivative noise DELETED 2026-06-11 dead-code sweep: its sole consumer broadShapeMD was removed earlier.)
|
|
143
|
+
|
|
144
|
+
// ---- CONTENT CARVE FIELDS (SHARED VS+FS preamble). These cut lakes/rivers/canyons into the
|
|
145
|
+
// elevation (VS uses the depth) AND gate the water/rock colour (FS uses the wet/depth mask), so the
|
|
146
|
+
// colour sits EXACTLY in the carved depression at every LOD -- they MUST be visible to BOTH stages
|
|
147
|
+
// (defining them inside #ifdef _VERTEX_ made the FS fail to link: 'no matching overloaded function',
|
|
148
|
+
// the same class as the historical snoise3-in-VS-only bug). Pure fn of world dir -> seam-safe +
|
|
149
|
+
// LOD-invariant. Each has an EROSION profile: a wide graded shoulder/valley/bench that blends into
|
|
150
|
+
// terrain plus a deeper core, and returns a wet/depth mask the FS colours.
|
|
151
|
+
const float LAKE_CARVE_DEPTH = 90.0; // metres of bowl depth at basin centre
|
|
152
|
+
float lakeBasinField(vec3 dir){ return 0.5 + 0.5 * snoise3(dir * 55.0 + vec3(4.0, 9.0, 1.0)); }
|
|
153
|
+
float lakeCarveM(vec3 dir, out float wet){
|
|
154
|
+
float basin = lakeBasinField(dir);
|
|
155
|
+
float shoulder = smoothstep(0.50, 0.74, basin); // eroded outwash apron (blends into terrain)
|
|
156
|
+
float bowl = smoothstep(0.62, 0.80, basin); // deeper water body
|
|
157
|
+
wet = smoothstep(0.64, 0.70, basin); // crisp shoreline of the open water
|
|
158
|
+
return -LAKE_CARVE_DEPTH * (0.45 * shoulder + 0.55 * bowl);
|
|
159
|
+
}
|
|
160
|
+
float lakeCarveM(vec3 dir){ float w; return lakeCarveM(dir, w); }
|
|
161
|
+
// River and canyon are the SAME incision algorithm (a 4-octave ridged 1-abs(snoise3) network),
|
|
162
|
+
// differing only in base frequency, phase, depth, and threshold band. One shared field helper.
|
|
163
|
+
// `d` is already the sampling coordinate (normalized dir, optionally phase-shifted by the caller).
|
|
164
|
+
//
|
|
165
|
+
// AXIS-BIAS + ORGANIC FIX (user: 'canyons draw H/V lines, nothing in between; less predictable').
|
|
166
|
+
// snoise3 is VALUE noise on an integer lattice, so ridged 1-abs(snoise3) crests align to the x/y/z
|
|
167
|
+
// axes -> horizontal/vertical channel lines. Two fixes, both keeping it a pure world-dir field:
|
|
168
|
+
// ROTATE the sample domain per octave by a fixed CONSTANT 3D rotation (precomputed mat3, no
|
|
169
|
+
// per-call trig/normalize, no extra noise taps -- cheap) so each octave's lattice axes point
|
|
170
|
+
// differently -> the summed ridged crests run in EVERY direction, not just the x/y/z lattice H/V.
|
|
171
|
+
// The matrix rows are a fixed tilted basis (~no axis alignment); det ~= 1 so it doesn't drift scale.
|
|
172
|
+
const mat3 OCT_ROT = mat3( 0.80, 0.36, -0.48, -0.48, 0.86, -0.18, 0.36, 0.36, 0.86);
|
|
173
|
+
float inciseRidgeField(vec3 d, float baseFreq, float freqMul){
|
|
174
|
+
vec3 p = d;
|
|
175
|
+
float freq = baseFreq, amp = 1.0, sum = 0.0, norm = 0.0;
|
|
176
|
+
for (int o = 0; o < 3; o++){ // 4->3 octaves (max-speed sweep 2026-06-10: finest ~6km gullies sub-pixel at flight altitudes; -1 tap x4 calls x3 composeHeight/vertex)
|
|
177
|
+
sum += amp * (1.0 - abs(snoise3(p * freq)));
|
|
178
|
+
norm += amp; freq *= freqMul; amp *= 0.5; p = OCT_ROT * p; // rotate domain each octave
|
|
179
|
+
}
|
|
180
|
+
return sum / norm; // ->1 on the channel network
|
|
181
|
+
}
|
|
182
|
+
const float RIVER_INCISE_DEPTH = 120.0; // metres at the channel thalweg
|
|
183
|
+
float riverRidgeField(vec3 dir){ return inciseRidgeField(normalize(dir), 320.0, 2.03); }
|
|
184
|
+
float riverCarveM(vec3 dir, out float wet){
|
|
185
|
+
float ridge = riverRidgeField(dir);
|
|
186
|
+
float valley = smoothstep(0.78, 0.94, ridge); // wide eroded valley sides
|
|
187
|
+
float thalweg = smoothstep(0.88, 0.96, ridge); // deep channel core
|
|
188
|
+
wet = smoothstep(0.90, 0.94, ridge); // flowing-water line
|
|
189
|
+
return -RIVER_INCISE_DEPTH * (0.5 * valley + 0.5 * thalweg);
|
|
190
|
+
}
|
|
191
|
+
float riverCarveM(vec3 dir){ float w; return riverCarveM(dir, w); }
|
|
192
|
+
const float CANYON_INCISE_DEPTH = 1400.0; // metres at the gorge floor (user 2026-06-02: deepen+widen so canyons visibly sculpt the elevation; was 480m = invisible vs multi-km relief)
|
|
193
|
+
uniform float canyonDepthMul; // LIVE canyon-depth lever (window.__canyonDepth; 1.0 default)
|
|
194
|
+
// W5: uNrmGain deleted (only fed the removed VS slopeGain).
|
|
195
|
+
uniform float uVertexAO; // per-vertex shading/AO strength lever (window.__vertexAO; 1.0 default)
|
|
196
|
+
// SEPARATE WATER SURFACE (user 2026-06-11 'the ocean should be a separate surface'): the same
|
|
197
|
+
// program draws TWO instanced passes -- terrain (uIsWater=0, true seabed geometry, land shading)
|
|
198
|
+
// then water (uIsWater=1, the same leaves pinned to sea level, animated ocean shading, alpha-
|
|
199
|
+
// blended over the seabed). One program + a uniform flag = no second cold compile, no duplicated
|
|
200
|
+
// per-frame uniform churn, and the branch is uniform-coherent (free on the GPU).
|
|
201
|
+
uniform float uIsWater; // 0 = terrain pass, 1 = water-surface pass
|
|
202
|
+
uniform float uBeachTopM; // beach ceiling (m): below this, grass/snow yield to sand (window.__beachTop; 30 default)
|
|
203
|
+
uniform float uHiFreqCut; // hi-freq elevation-noise attenuation, applied to ALL hi-freq sources:
|
|
204
|
+
// broadShapeM/MD fine octaves (o>=6) + vtxDisplace micro-relief
|
|
205
|
+
// (window.__hiFreqCut; default 0.25 = the user's 4x reduction, 2026-06-06)
|
|
206
|
+
// ANCHOR-STEP A/B TOGGLES (per-area stairstep root-hunt, workflow wrxo0rr7a). Each defaults to 0.0 =
|
|
207
|
+
// CURRENT behaviour; set window.__<name>=1 to WIDEN that anchor-keyed smoothstep band so the 0->1 swing
|
|
208
|
+
// spreads over more ground (the narrow-band-on-slow-anchor mechanism that snaps relief+normal "here and
|
|
209
|
+
// there" along anchor contours). The user binary-searches these live at the affected spot; the one that
|
|
210
|
+
// dissolves the step = the root. Once found, the winning widen is baked in unconditionally + toggle removed.
|
|
211
|
+
uniform float uMtnBandWide; // widen mtn=smoothstep(16.8,18.6,elevAmp) -> (14.5,19.5) [TOP SUSPECT: unlocks 2600m belt massif across a thin contour]
|
|
212
|
+
uniform float uClimateRelief; // widen wetLowFlat(0.66,0.9,humid) + coldFlat(0.18,0.34,temp) reliefMul gates
|
|
213
|
+
uniform float uIsleWide; // widen isleZone seaBias gates (50,350)+(900,1600) -> (30,600)+(600,2200)
|
|
214
|
+
uniform float uCarveWide; // widen the river/canyon/lake/dune CLIMATE gates so carve depth fades in over a wide span
|
|
215
|
+
float canyonRidgeField(vec3 dir){ return inciseRidgeField(normalize(dir) + vec3(13.7, -4.2, 8.9), 380.0, 2.07); } // phase offset matches FS canyonMask
|
|
216
|
+
// CANYON cross-section now reads as a CANYON, not a V-notch: STEEP WALLS + a FLAT FLOOR.
|
|
217
|
+
// wall = a sharp smoothstep band -> the carve drops fast over a narrow ridge interval (the cliff
|
|
218
|
+
// walls), instead of the old gentle bench+gorge blend that made shallow V-troughs.
|
|
219
|
+
// floor = clamped: once past the wall the depth saturates to the flat gorge bottom (a river runs
|
|
220
|
+
// there, not an ever-deepening point). bench keeps a soft eroded rim above the wall lip.
|
|
221
|
+
// Pure world-dir (LOD-invariant, seam-safe). depth out = 0 rim -> 1 floor (drives FS strata + AO).
|
|
222
|
+
float canyonCarveM(vec3 dir, out float depth){
|
|
223
|
+
float ridge = canyonRidgeField(dir);
|
|
224
|
+
// WIDENED bands (user 2026-06-02 deepen+widen): the old .78/.86/.905/.93 intervals were a very
|
|
225
|
+
// narrow ridge slice -> thin hairline gorges. Widen so the canyon reads as a BROAD gorge: a wide
|
|
226
|
+
// eroded rim shoulder, a steep but visible wall, and a wide flat floor that the gorge bottoms out on.
|
|
227
|
+
float bench = smoothstep(0.62, 0.78, ridge); // wide eroded rim shoulder
|
|
228
|
+
float wall = smoothstep(0.78, 0.86, ridge); // STEEP cliff wall (wider band)
|
|
229
|
+
float floorF= smoothstep(0.86, 0.93, ridge); // reach the flat gorge floor, then clamp
|
|
230
|
+
depth = max(wall, floorF);
|
|
231
|
+
// 0.18 rim shoulder + 0.82 wall-to-floor; floorF clamps so the bottom is flat (no infinite V).
|
|
232
|
+
float profile = 0.18 * bench + 0.82 * max(wall, floorF);
|
|
233
|
+
float dmul = canyonDepthMul > 0.0 ? canyonDepthMul : 1.0; // 0 = uniform unset (e.g. probe prog)
|
|
234
|
+
float carve = -CANYON_INCISE_DEPTH * dmul * profile;
|
|
235
|
+
// FINE TRIBUTARY GULLIES (user 2026-06-02: 'at 2m our canyons are <2m'). The main canyon network
|
|
236
|
+
// (freq 380 ~100km) is sparse + broad, so close up the ground has no canyon-scale erosion. Add a
|
|
237
|
+
// FRACTAL CONTINUATION: 2 finer ridged incision octaves (~10km + ~2km wavelength) carving shallower
|
|
238
|
+
// gullies/ravines (~55m + ~16m deep) so arid terrain shows branching erosion channels at the 10-
|
|
239
|
+
// 100m scale the low-altitude camera sees. Pure world-dir (LOD-invariant); the mesh resolves them
|
|
240
|
+
// at maxLevel 16 (~7m cells). Each gully octave = a thinned ridged field, depth scaled to its wl.
|
|
241
|
+
vec3 dn = normalize(dir);
|
|
242
|
+
float g1 = inciseRidgeField(dn + vec3(5.1, 8.3, -2.7), 3500.0, 2.11); // ~10km tributaries
|
|
243
|
+
float g2 = inciseRidgeField(dn + vec3(-7.4, 1.9, 6.2), 14000.0, 2.05); // ~2.5km gullies
|
|
244
|
+
// HIGH-FREQ fractal depth x0.65 (user 2026-06-11 'leave the low frequency canyons deep, only
|
|
245
|
+
// reduce the high frequency fractals, bring it back 30%' -- halved, then +30% restored): the
|
|
246
|
+
// main 100km gorge network keeps full CANYON_INCISE_DEPTH; only these tributary octaves shrink.
|
|
247
|
+
carve += -104.0 * dmul * smoothstep(0.78, 0.93, g1); // ~10km tributaries (160m * 0.65)
|
|
248
|
+
carve += -39.0 * dmul * smoothstep(0.80, 0.95, g2); // ~2.5km gullies (60m * 0.65)
|
|
249
|
+
return carve;
|
|
250
|
+
}
|
|
251
|
+
float canyonCarveM(vec3 dir){ float dd; return canyonCarveM(dir, dd); }
|
|
252
|
+
|
|
253
|
+
// CLIFFS AS REAL MESA LANDFORMS (user 2026-06-02: 'cliffs still patches of dots', 'we dont have
|
|
254
|
+
// realistic cliffs yet'). The height-QUANTIZATION terrace produced thin scattered contour risers =
|
|
255
|
+
// dots, NOT escarpments. REDESIGN: a cliff is a coherent raised PLATEAU (mesa) with a sharp RIM.
|
|
256
|
+
// mesaField(d) -> a smooth low-frequency field; where it exceeds a threshold the land is a mesa
|
|
257
|
+
// TOP, raised by a fixed height; the THRESHOLD CROSSING is a narrow band = the near-vertical cliff
|
|
258
|
+
// RIM, a CONNECTED line around the whole mesa (not speckle). Stacking two octaves gives mesas +
|
|
259
|
+
// buttes (smaller mesas on top). Pure world-dir, LOD-invariant, seam-safe. Returns metres to ADD;
|
|
260
|
+
// cliffOut ->1 on the rim band (drives FS strata + steep material) so cliffs are lit + textured.
|
|
261
|
+
const float MESA_HEIGHT = 1000.0; // metres a mesa top stands above the floor (user 2026-06-02 deepen+widen; was 380m = invisible vs multi-km relief)
|
|
262
|
+
uniform float cliffAmt; // LIVE cliff-strength lever (window.__cliffAmt; 1.0 default)
|
|
263
|
+
highp float broadShapeLowM(vec3 dir); // W7: forward decl (metres -> highp); atlas-backed slope helper for the region slope gate
|
|
264
|
+
// COHERENT BADLANDS REGION MASK: a LOW-FREQUENCY world-dir noise (freq 11 ~ continental patches)
|
|
265
|
+
// thresholded to a minority fraction -> WHERE mesa country occurs. Mesas exist only inside a region
|
|
266
|
+
// so cliffs cluster into badlands, never speckle the whole planet.
|
|
267
|
+
float badlandsRegion(vec3 d){
|
|
268
|
+
float r = snoise3(d * 11.0 + vec3(31.7, -12.3, 5.1)); // [-1,1], coherent ~continental patches
|
|
269
|
+
return smoothstep(0.30, 0.55, r); // top fraction -> 1 inside badlands
|
|
270
|
+
}
|
|
271
|
+
// Smooth mesa potential in [0,1]: a couple of mid-frequency octaves (freq ~55/130 -> mesas ~80km /
|
|
272
|
+
// ~35km across). Domain-warped so mesa outlines are organic (not blobby circles).
|
|
273
|
+
float mesaField(vec3 d){
|
|
274
|
+
vec3 w = d; w.xy += 0.10 * vec2(snoise3(d*40.0+5.0), snoise3(d*40.0+11.0)); // 3->2 warp taps (max-speed sweep): z-warp dropped, outlines stay organic
|
|
275
|
+
float a = snoise3(w * 55.0 + vec3(3.1, 7.7, 1.3)); // big mesas
|
|
276
|
+
float b = snoise3(w * 130.0 + vec3(9.4, 2.2, 6.6)); // buttes on top
|
|
277
|
+
return 0.5 + 0.5 * (0.7 * a + 0.3 * b); // [0,1]
|
|
278
|
+
}
|
|
279
|
+
highp float cliffTerraceM(vec3 dir, highp float h, out float cliffOut){ // W7: h is metres (highp); cliffOut mask stays mediump
|
|
280
|
+
cliffOut = 0.0;
|
|
281
|
+
if (h <= 0.0) return 0.0;
|
|
282
|
+
vec3 d = normalize(dir);
|
|
283
|
+
// REGION GATE: mesas only inside a coherent badlands region (no planet-wide speckle).
|
|
284
|
+
float region = badlandsRegion(d);
|
|
285
|
+
if (region <= 0.0) return 0.0;
|
|
286
|
+
// EXCLUDE steep mountains (a mesa is a flat-topped raised block, not a peak). Macro slope from
|
|
287
|
+
// broadShapeLowM over a fixed 1.2km step -> fade mesas out where the broad land is already steep.
|
|
288
|
+
const float e = 1200.0;
|
|
289
|
+
vec3 ux = normalize(cross(abs(d.y) < 0.99 ? vec3(0.0,1.0,0.0) : vec3(1.0,0.0,0.0), d));
|
|
290
|
+
highp float h0 = broadShapeLowM(d); // W7: metres, highp
|
|
291
|
+
highp float gx = (broadShapeLowM(normalize(d + ux * (e/defRadius))) - h0) / e;
|
|
292
|
+
float flatness = 1.0 - smoothstep(0.05, 0.16, abs(gx));
|
|
293
|
+
float gate = region * flatness;
|
|
294
|
+
if (gate <= 0.0) return 0.0;
|
|
295
|
+
// MESA STEP: raise the land where mesaField crosses THR over a NARROW band TH -> the band is the
|
|
296
|
+
// cliff RIM (a connected contour around the mesa), the inside is the raised flat-ish top. Two
|
|
297
|
+
// levels (top + a higher butte tier) so cliffs stack like real mesa country.
|
|
298
|
+
float m = mesaField(d);
|
|
299
|
+
const float THR = 0.56, TH = 0.07; // rim centre + half-width (widened for the taller 1000m escarpment -> readable slope, still a sharp rim)
|
|
300
|
+
float top = smoothstep(THR - TH, THR + TH, m); // 0 floor -> 1 mesa top
|
|
301
|
+
float butte= smoothstep(0.74 - TH, 0.74 + TH, m); // higher butte tier
|
|
302
|
+
float rise = (0.7 * top + 0.3 * butte) * MESA_HEIGHT; // metres raised
|
|
303
|
+
// rim mask: ->1 INSIDE either transition band (where the step is climbing = the steep face).
|
|
304
|
+
float rim = max(top * (1.0 - top), butte * (1.0 - butte)) * 4.0; // bell, peak 1 mid-rim
|
|
305
|
+
cliffOut = clamp(rim, 0.0, 1.0) * gate;
|
|
306
|
+
float camt = cliffAmt > 0.0 ? cliffAmt : 1.0; // 0 = uniform unset (e.g. probe prog)
|
|
307
|
+
return rise * camt * gate; // metres added (mesa top raised; rim = the cliff)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// DUNE FIELD (arid/desert anchors, ref: keaukraine/webgl-dunes). A rolling sand surface = large
|
|
311
|
+
// smooth low-frequency dunes with ASYMMETRIC crests (windward shallow, lee steep) + superimposed
|
|
312
|
+
// fine wind ripples. Pure world-dir field (LOD-invariant). Returns metres of dune relief (>=0,
|
|
313
|
+
// added on top of land) and `crest` in [0,1] (1 at a dune ridge -> the FS lightens sand there).
|
|
314
|
+
const float DUNE_AMP = 120.0; // metres of big-dune relief
|
|
315
|
+
float duneFieldM(vec3 dir, out float crest){
|
|
316
|
+
vec3 d = normalize(dir); // UNIT dir keeps freq*d bounded -> no planet-scale fp32 lattice break.
|
|
317
|
+
// BIG DUNES ~3km: low-freq smooth noise, asymmetric crest (pow sharpens the lee face).
|
|
318
|
+
float big = snoise3(d * 2000.0 + vec3(2.0, 7.0, 5.0)) * 0.5 + 0.5; // [0,1]
|
|
319
|
+
float dune = pow(big, 1.6); // windward-shallow/lee-steep
|
|
320
|
+
// MEDIUM dunes ~1km riding on the big ones. (The old ~150-220m wind RIPPLES were REMOVED: at
|
|
321
|
+
// that wavelength the mesh vertices undersample them -> shimmer/moire 'UV issue' + a wild FD
|
|
322
|
+
// normal, with no resolvable benefit. The rolling big+medium dunes are the dune read.)
|
|
323
|
+
float med = snoise3(d * 6000.0) * 0.5 + 0.5;
|
|
324
|
+
crest = smoothstep(0.55, 0.92, big);
|
|
325
|
+
return DUNE_AMP * dune + 40.0 * (med * dune);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// TERRAIN HEIGHT FIELD: one continuous world-dir fBm -- a finer LOD is a denser SAMPLE of the same
|
|
329
|
+
// field (LOD-invariant, seam-safe by construction).
|
|
330
|
+
// CHEAP 8-octave variant for the VS lit-normal finite difference (silhouette+macro slope only).
|
|
331
|
+
highp float broadShapeLowM(vec3 dir){ // W7: metres (~13000) + freq (~49152) accumulators are highp islands
|
|
332
|
+
if (hasHpf == 0) return 0.0;
|
|
333
|
+
vec3 d = normalize(dir);
|
|
334
|
+
highp float amp = 6500.0, freq = 3.0, sum = 0.0;
|
|
335
|
+
for (int o=0; o<8; o++){ sum += amp * snoise3(d*freq); amp *= (o < 6 ? 0.66 : 0.82); freq *= 2.0; }
|
|
336
|
+
return sum - 900.0;
|
|
337
|
+
}
|
|
338
|
+
// ---- SHARED micro-relief helpers (MOVED to the common preamble, THC-Normal W1, so composeHeight()
|
|
339
|
+
// in the VS/PROBE region can call them). vtxDetail = the micro-relief A/B global; vnoise2
|
|
340
|
+
// is the 2D value-noise vtxDisplace octaves sample; ruggedFromElevAmp + faceWarp map anchor->amplitude
|
|
341
|
+
// and face-local metres->warped metres. All pure, dependency-light (defRadius + smoothstep only).
|
|
342
|
+
uniform float vtxDetail; // micro-relief strength (1=on) -- kept as a global so it can be A/B'd
|
|
343
|
+
// W7 highp ISLAND: vnoise2 args are face-local metres / wavelength = fp(~6.4e6)/wl(~1m) -> ~6e6, far
|
|
344
|
+
// past fp16. The vtxDisplace micro-relief lattice needs full precision or the ground bump quantizes.
|
|
345
|
+
// W10 PRECISION: the old fract(p*443.897) hash overflowed fp32 integer-exactness at fine octaves
|
|
346
|
+
// (wl~10m -> p~6.4e5, *443.897 -> 2.84e8 >> 2^24=1.67e7), so fract() lost low bits and the corner
|
|
347
|
+
// hashes QUANTIZED to a few discrete values -> per-cell height plateaus = faint ~0.48m terracing on
|
|
348
|
+
// flat ground. Fix: hash the INTEGER lattice cell with a uint bit-mix (no large float, no fract).
|
|
349
|
+
// Pure function of the world-consistent integer cell index -> adjacent LODs and cube faces agree
|
|
350
|
+
// EXACTLY (no new seam). Amplitude/range UNCHANGED: still a uniform [0,1] per-cell value, same field
|
|
351
|
+
// statistics; this removes the quantization, it does not smooth. (p is the exact integer lattice
|
|
352
|
+
// index <2^24, so ivec2(p) is exact.)
|
|
353
|
+
highp float vhash(highp vec2 p){
|
|
354
|
+
uvec2 q = uvec2(ivec2(p)); // two's-complement bits of the exact integer cell index
|
|
355
|
+
uint h = q.x * 1597334677u + q.y * 3812015801u; // decorrelate the two axes
|
|
356
|
+
h ^= h >> 16; h *= 2654435769u; h ^= h >> 15; h *= 2246822519u; h ^= h >> 13; // bit avalanche
|
|
357
|
+
return float(h) * (1.0 / 4294967296.0); // [0,1)
|
|
358
|
+
}
|
|
359
|
+
// QUINTIC C2 interp (Perlin u=6t^5-15t^4+10t^3), NOT the C1 cubic smoothstep (3t^2-2t^3). The cubic's
|
|
360
|
+
// SECOND derivative is discontinuous at every lattice cell edge -> the noise SLOPE kinks at each ~wl cell
|
|
361
|
+
// boundary; on a gentle mountain flank (where the kink is not swamped by relief) that ~2.7m/10m slope spike
|
|
362
|
+
// every ~320m (vtxDisplace wl0) reads as a LOCAL stairstep in both elevation and the central-diff normal
|
|
363
|
+
// (user 2026-06-09 'only a few very local spots'). Quintic is C2 -> slope continuous across cells, NO kink,
|
|
364
|
+
// and amplitude UNCHANGED (this does not smooth the terrain, it removes the discontinuity in its derivative).
|
|
365
|
+
float vnoise2(highp vec2 p){ highp vec2 i=floor(p),f=fract(p); vec2 u=f*f*f*(f*(f*6.0-15.0)+10.0);
|
|
366
|
+
float a=vhash(i),b=vhash(i+vec2(1,0)),c=vhash(i+vec2(0,1)),d=vhash(i+vec2(1,1));
|
|
367
|
+
return mix(mix(a,b,u.x),mix(c,d,u.x),u.y)*2.0-1.0; } // [-1,1]
|
|
368
|
+
// (vnoise2D DELETED 2026-06-11 dead-code sweep: no callers since the VS gradient block was removed.)
|
|
369
|
+
// anchor elevAmp -> rugged multiplier (BAKED elevAmp range ~14.5-18.9).
|
|
370
|
+
float ruggedFromElevAmp(float elevAmp){ return mix(0.5, 1.8, smoothstep(15.0, 18.6, elevAmp)); }
|
|
371
|
+
// TANGENT-ADJUSTED cube->sphere warp: face-local metres -> warped metres (near-uniform cell area).
|
|
372
|
+
// Edge s=+-1 -> tan(+-pi/4)=+-1 (identity) => cross-face shared edges meet exactly (seam-safe).
|
|
373
|
+
highp vec2 faceWarp(highp vec2 p){ return defRadius * tan((p / defRadius) * 0.7853981634); } // W7: ~6.4e6 m result, highp
|
|
374
|
+
// PERLIN-EVERYWHERE lever -- shared by the composeHeight elevation term (VS/PROBE) and the FS albedo
|
|
375
|
+
// overlay, so it must be declared in ALL stages (outside the VS/PROBE guard below).
|
|
376
|
+
uniform float uDetailOverlay; // amplitude lever (user-tuned 6; 0 = off; __detailOverlay)
|
|
377
|
+
// FXC UNROLL-DEFEAT (2026-06-12, 'rocks everywhere + no normals on AMD default Chrome'): ANGLE's
|
|
378
|
+
// D3D11 backend runs FXC, which fully unrolls constant-bound loops and applies aggressive math
|
|
379
|
+
// reordering across the unrolled body -- the long-suspected mis-translation domain (vulkan on the
|
|
380
|
+
// SAME AMD GPU renders correctly; d3d11 does not). A runtime loop bound cannot be unrolled. The
|
|
381
|
+
// uniform is set to 12 by gl-render; the max(...) guard keeps an unset uniform (0) from flattening
|
|
382
|
+
// the planet -- worst case the loop still runs the literal octave count.
|
|
383
|
+
uniform int uOctMax; // broadShapeM octave count (12); runtime-bound to defeat FXC unrolling
|
|
384
|
+
uniform float uNrmStepM; // lit-normal FD step in metres (150); uniform-fed to defeat FXC constant folding
|
|
385
|
+
#if defined(_VERTEX_) || defined(_PROBE_)
|
|
386
|
+
highp float broadShapeM(vec3 dir, float reliefMul, float ridgeMul){ // W7: returns metres (~13000) -> highp
|
|
387
|
+
if (hasHpf == 0) return 0.0;
|
|
388
|
+
float mtnAmp = 1.0; // mountain-amplitude (was a window.__mtnAmp uniform; inlined at neutral 1.0)
|
|
389
|
+
vec3 d = normalize(dir);
|
|
390
|
+
highp float amp = 6500.0, freq = 3.0, sum = 0.0; // W7 highp ISLAND: amp/freq(~49152)/sum(~13000) overflow fp16
|
|
391
|
+
int octMax = (uOctMax > 0) ? uOctMax : 12; // runtime bound (FXC unroll-defeat); 12 when unset
|
|
392
|
+
for (int o=0; o<octMax; o++){ // W9: 14->12 octaves, drop the finest 2 (wavelengths <2km) UNCONDITIONALLY
|
|
393
|
+
// FINE-OCTAVE 5x FREQ (user 2026-06-06: the high-freq normals-affecting GROUND bump should be 5x
|
|
394
|
+
// SMALLER). The o>=6 fine octaves ARE that bump (they drive the VS lit normal via the broadShapeMD
|
|
395
|
+
// gradient bEx/bEy; vtxDisplace + rock dN are separate, confirmed by live __vtxDetail toggle). Sample
|
|
396
|
+
// the fine band at 5x frequency so the bump grain is 5x finer; the o<6 base octaves (silhouette +
|
|
397
|
+
// hypsometry, CLI-validated) keep freq untouched. MUST match broadShapeMD exactly (geometry+normal).
|
|
398
|
+
highp float sf = (o >= 6) ? freq * 0.667 : freq; // W7: freq island. fine/ridge band 3x WIDER (user 2026-06-10 'mountains noise still narrow, 3x wider'): *2.0 -> *0.667 = the visible o>=6 ridge texture (13-104km) widens 3x
|
|
399
|
+
float nn = snoise3(d*sf);
|
|
400
|
+
if (o >= 6) {
|
|
401
|
+
float r = (1.0 - abs(nn)) * 2.0 - 1.0; // rounded ridged crests for mountain belts
|
|
402
|
+
nn = mix(nn, r, ridgeMul * 0.8);
|
|
403
|
+
nn *= reliefMul; // per-biome amplitude on regional+fine octaves
|
|
404
|
+
nn *= 1.36; // FINE-OCTAVE INTENSITY -- 4x (user 2026-06-09 '4x our high
|
|
405
|
+
// frequency perlin noise intensity'): 0.34 -> 1.36, net
|
|
406
|
+
// 1.36 * uHiFreqCut 0.5 = 0.68 (was 0.17). The o>=6 fine
|
|
407
|
+
// octaves are the high-freq normals-affecting ground bump
|
|
408
|
+
// (user-confirmed 2026-06-06). uHiFreqCut(0.5) stays the
|
|
409
|
+
// live trim dial. Silhouette octaves o<6 untouched.
|
|
410
|
+
nn *= uHiFreqCut; // hi-freq cut, default WIDENED 0.25->0.5 (see gl-render):
|
|
411
|
+
// apply the SAME fine-octave attenuation that broadShapeMD uses, so the COLLISION/probe height
|
|
412
|
+
// (sampleGroundM runs this scalar broadShapeM) matches the RENDERED geometry (broadShapeMD). Before
|
|
413
|
+
// this, only broadShapeMD cut the fine band -> the collision height diverged from the surface by the
|
|
414
|
+
// fine-octave amount. uHiFreqCut default 0.25 = the user's 4x reduction of all hi-freq elevation noise.
|
|
415
|
+
}
|
|
416
|
+
sum += amp * nn;
|
|
417
|
+
amp *= (o < 6 ? 0.66 : 0.80); freq *= 2.0; // macro decay 0.66; fine-octave decay 0.74->0.80 (widen, 2026-06-08)
|
|
418
|
+
}
|
|
419
|
+
// LOW-ELEVATION COMPRESSION CURVE (gamma 2.5): power-curve the positive land height only so
|
|
420
|
+
// low/mid land sinks toward a flatter base while peaks keep their range; sea sign untouched.
|
|
421
|
+
{
|
|
422
|
+
const float HREF = 6500.0, GAMMA = 2.5;
|
|
423
|
+
highp float e0 = sum - 900.0; // W7: metres
|
|
424
|
+
if (e0 > 0.0) {
|
|
425
|
+
highp float ec = HREF * pow(min(e0, HREF) / HREF, GAMMA);
|
|
426
|
+
if (e0 > HREF) ec += (e0 - HREF);
|
|
427
|
+
sum = 900.0 + ec;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// PEAK-LIFT, MOUNTAIN-BELT GATED: lift the high end so ridges become tall peaks; plains flat.
|
|
431
|
+
highp float hi = max(sum - 900.0, 0.0); // W7: metres
|
|
432
|
+
float peak = smoothstep(1200.0, 5000.0, hi);
|
|
433
|
+
float liftK = 0.06 + 0.16 * clamp(reliefMul, 0.0, 1.7); // 2x LESS elevated (user 2026-06-10: halved 0.12+0.32 -> 0.06+0.16)
|
|
434
|
+
sum += hi * peak * liftK;
|
|
435
|
+
// MOUNTAIN-BELT MASSIF: a low-freq range-scale envelope lifts the belt interior as a coherent
|
|
436
|
+
// landmass; the ridged mid octaves above supply the rugged peaks ON TOP of the raised base.
|
|
437
|
+
float belt = clamp((reliefMul - 0.45) / 1.25, 0.0, 1.0);
|
|
438
|
+
if (belt > 0.0 && hi > 0.0) {
|
|
439
|
+
// (massif envelope freqs restored to 4.0/7.0 -- they set continental belt PLACEMENT, not the visible
|
|
440
|
+
// ridge spacing; the 3x-wider knob is the o>=6 fine band above, not this envelope.)
|
|
441
|
+
float e0 = snoise3(d * 4.0 + vec3(11.0, 3.0, 7.0)) * 0.5 + 0.5;
|
|
442
|
+
float e1 = snoise3(d * 7.0 + vec3(2.0, 9.0, 4.0)) * 0.5 + 0.5;
|
|
443
|
+
float modu = 0.7 + 0.3 * clamp(e0 * 0.7 + e1 * 0.3, 0.0, 1.0);
|
|
444
|
+
float landGate = smoothstep(0.0, 800.0, hi);
|
|
445
|
+
float base = belt * landGate * modu;
|
|
446
|
+
sum += base * 5200.0 * mtnAmp; // 2x LESS elevated (user 2026-06-10: halved 10400 -> 5200)
|
|
447
|
+
// STEEP RUGGED PEAKS: a high-base-freq (~30km) ridged stack concentrated into steep crests.
|
|
448
|
+
if (base > 0.01) {
|
|
449
|
+
// pf 100 REVERTED to 200 (user 2026-06-11 'most of the terrain is flat now'): the halved peak
|
|
450
|
+
// frequency flattened the 4x mountains planet-wide. The 'slopes everywhere' diagnosis it served
|
|
451
|
+
// conflated the braided TEXTURE patches (rockSlope breakup noise, since deleted) with real slope;
|
|
452
|
+
// with the braids, the dark raw-photo far field, and the UDN normal-frame bug all fixed, the
|
|
453
|
+
// original steep terrain stands.
|
|
454
|
+
highp float pf = 200.0; float pa = 1.0, ps = 0.0, pn = 0.0; // W7: pf (~4100) feeds the noise lattice -> highp
|
|
455
|
+
for (int o = 0; o < 3; o++) { // 5->3 octaves (max-speed sweep: -6 taps/vertex in the belt; coarse 2 set the peak stance, decay 0.5->0.65 reweights)
|
|
456
|
+
ps += pa * (1.0 - abs(snoise3(d * pf + vec3(3.3, 7.7, 1.1))));
|
|
457
|
+
pn += pa; pa *= 0.65; pf *= 2.13;
|
|
458
|
+
}
|
|
459
|
+
float crest = ps / pn;
|
|
460
|
+
sum += base * pow(crest, 4.5) * 8400.0 * mtnAmp; // 2x LESS elevated (user 2026-06-10: halved 16800 -> 8400); sharper eroded tips (pow 4.5) kept
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// SOFT ELEVATION CEILING (mob-w8-ceiling 2026-06-08: 'peaks come to points, no flat plateau'). Even at
|
|
464
|
+
// CK=6500/CMAX=13000 the tanh(x/(CMAX-CK)) knee was tight enough that the high band still rolled over onto
|
|
465
|
+
// a soft plateau and needed a *1.15 post-scale crutch. WIDEN THE KNEE: divide the excess by a fixed 8000m
|
|
466
|
+
// so the tanh stays in its near-linear region far longer -- peaks keep climbing to points instead of
|
|
467
|
+
// asymptoting to a ceiling -- and lift CMAX to 15000 so the asymptote sits well above any real peak. With
|
|
468
|
+
// the wider knee the high band no longer collapses, so the *1.15 scale-up crutch is DROPPED.
|
|
469
|
+
{
|
|
470
|
+
highp float e = sum - 900.0; // W7: metres
|
|
471
|
+
// GLOBAL ELEVATION SCALE 0.65 (user 2026-06-10). 0.343 over-squashed the field (45.9% land <500m, p50
|
|
472
|
+
// 723m, relief only ~4km -> mountains invisible/clamped-looking, NOT a top clip). 0.65 expands the
|
|
473
|
+
// range so mountains stand out: p50 ~1370m, p99 ~8920m, MAX ~11600m, relief ~7.5km. Scales the WHOLE
|
|
474
|
+
// relief uniformly (base octaves + belt massif + peaks) on the excess-over-900. Max 11.6km still well
|
|
475
|
+
// under the ceiling (CK 6500 / CMAX 42000, knee /26000) so no clip.
|
|
476
|
+
e *= 0.65;
|
|
477
|
+
// CEILING RAISED + KNEE WIDENED for the 4x mountaintops (user 2026-06-09: 'must not hit the ceiling
|
|
478
|
+
// anywhere, run under the normal max + elevate the maximum'). MEASURED 4x-hf+4x-mtn pre-ceiling max
|
|
479
|
+
// = 28550m (p99.9 21457m); CMAX 42000 sits 1.47x above it = no peak ever approaches the asymptote, and
|
|
480
|
+
// the knee /26000 keeps tanh near-LINEAR through the whole real range (at the 22050m excess top,
|
|
481
|
+
// tanh(0.85)=0.69 -> peaks come to POINTS, never the flat plateau the tight knee caused).
|
|
482
|
+
float CK = 6500.0, CMAX = 42000.0;
|
|
483
|
+
if (e > CK) { highp float x = e - CK; e = CK + (CMAX - CK) * tanh(x / 26000.0); }
|
|
484
|
+
return e;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// FD-GRADIENT VARIANT (FPS lever, measured: browser-9 low-alt VS=53ms/95% of frame). The lit-normal
|
|
488
|
+
// finite-difference in the VS samples broadShapeM at +/- a 2km world step; octaves whose wavelength is
|
|
489
|
+
// BELOW the FD step (~the finest 4 of the 14, wavelength < ~2km) cannot be resolved by a 2km FD -- they
|
|
490
|
+
// only add sub-step noise that smooths/aliases out. So this variant runs 10 (not 14) macro+mid octaves
|
|
491
|
+
// and a 3-octave (not 5) steep-peak stack, keeping the FULL massif/peak-lift/ceiling AMPLITUDE so the FD
|
|
492
|
+
// slope still tracks the real mountain geometry (the broadShapeLowM regression was dropping that
|
|
493
|
+
// amplitude, not the sub-step octaves). It is a GLOBALLY-FIXED world-dir function => LOD-invariant, no
|
|
494
|
+
// tile-edge seam (the refuted per-tile/LOD octave fade diverged at shared edges; this does not). Used
|
|
495
|
+
// ONLY for the two FD taps; the displaced HEIGHT still uses the full broadShapeM (full detail on the
|
|
496
|
+
// geometry, slightly-coarsened slope on the lit normal -- the FS dFdx normal carries the fine relief).
|
|
497
|
+
// ANALYTIC-DERIVATIVE broadShapeM: returns vec4(height, dHeight/dDir in WORLD-DIR space). Same field
|
|
498
|
+
// as broadShapeM, but every octave also accumulates its exact gradient via snoise3D, and the smooth
|
|
499
|
+
// post-terms (gamma compression, peak-lift, tanh ceiling) scale the gradient by their scalar
|
|
500
|
+
// derivative (chain rule). The FS builds the lit normal from this gradient instead of a finite
|
|
501
|
+
// difference -> exact relief shading at EVERY scale, no fine-octave aliasing (the deck flat-clay fix).
|
|
502
|
+
// The massif/steep-peak ridged terms contribute their dominant gradient; sub-tap detail rides the
|
|
503
|
+
// octave gradient. Returns the SAME height as broadShapeM (verified by construction).
|
|
504
|
+
// broadShapeFD (the reduced-octave FD-tap variant) REMOVED 2026-06-05: the analytic-derivative
|
|
505
|
+
// broadShapeMD replaced the 3-tap finite-difference lit-normal entirely, so the reduced-octave FD
|
|
506
|
+
// helper is dead. One comprehensive gradient source, net-smaller shader (one-system cleanup).
|
|
507
|
+
highp float broadShape(vec3 dir){ return broadShapeM(dir, 1.0, 0.0); } // W7: metres -> highp
|
|
508
|
+
highp float broadShape(vec3 dir, float tileM){ return broadShape(dir); }
|
|
509
|
+
|
|
510
|
+
// PER-VERTEX micro-relief (rolling-ground bump). MOVED into the shared VS/PROBE region
|
|
511
|
+
// (THC-Normal W1) so geometry (VS), collision (PROBE) and the height-bake all call the SAME field --
|
|
512
|
+
// no parallel re-derivation. fp = face-local warped metres (continuous across tiles), tileM = quad
|
|
513
|
+
// size for the Nyquist octave fade, rugged = anchor-driven amplitude. Pure value (no gradient).
|
|
514
|
+
float vtxDisplace(highp vec2 fp, float tileM, float rugged){ // W7: fp = face-local metres ~6.4e6 -> highp; fp/wl feeds the noise lattice
|
|
515
|
+
if (vtxDetail <= 0.0 || rugged <= 0.0) return 0.0;
|
|
516
|
+
float wl0 = 960.0; // coarsest fine octave wavelength (m) -- 3x WIDER / less frequent (user 2026-06-09:
|
|
517
|
+
// 'make this high frequency elevation noise 3x wider, its very nice but too small').
|
|
518
|
+
// Was 320m; 320*3=960. The noise the user sees on ALL land widens 3x (finest octave
|
|
519
|
+
// 30m vs 10m). The mountain-gated erosive copy below rides the same widened basis.
|
|
520
|
+
float amp0 = 11.0; // 8->11m: wider features carry a touch more amplitude to stay as visible as the old bump
|
|
521
|
+
float ridge = smoothstep(1.3, 1.75, rugged); // only the highest mountain belts fold to ridges
|
|
522
|
+
float sumF = 0.0, sumR = 0.0, a = amp0, wl = wl0;
|
|
523
|
+
// PER-PATCH STAIR-STEP FIX (user 'stair steps were per patch, not per vert'): the old Nyquist fade keyed
|
|
524
|
+
// cellM=tileM/16 / nyqWl on tileM = the LEAF SIZE (a per-patch constant). Two ADJACENT leaves at different
|
|
525
|
+
// LOD (tileM differs 2x) then weighted the octaves DIFFERENTLY, so their micro-relief disagreed at the
|
|
526
|
+
// shared edge by a constant -> a per-patch HEIGHT STEP at every LOD boundary. Make vtxDisplace a FIXED
|
|
527
|
+
// octave set, a PURE function of the world-pos fp with NO tileM dependence -> every leaf computes the
|
|
528
|
+
// IDENTICAL height at a given point = C0 across all LOD boundaries by construction (no per-patch step).
|
|
529
|
+
// A coarse leaf's 16-cell mesh simply under-samples the finest octaves (smooth interpolation, not a step);
|
|
530
|
+
// the amplitude is small (amp0 8m * uHiFreqCut 0.25 * ruggedAmp) so any residual aliasing is minor and
|
|
531
|
+
// far less visible than the per-patch steps it removes. (tileM kept in the signature for callers.)
|
|
532
|
+
for (int o=0; o<6; o++){
|
|
533
|
+
float n = vnoise2(fp / wl); // [-1,1]
|
|
534
|
+
sumF += a * n; // smooth fBm
|
|
535
|
+
float r = 1.0 - abs(n); r *= r; // ridged: sharp crest at n=0
|
|
536
|
+
sumR += a * (r * 2.0 - 1.0);
|
|
537
|
+
a *= 0.55; wl *= 0.5;
|
|
538
|
+
}
|
|
539
|
+
float sum = mix(sumF, sumR * 0.9, ridge);
|
|
540
|
+
// MOUNTAIN EROSIVE DETAIL (user 2026-06-09: 'take the high-freq elevation noise, make it 3x WIDER (less
|
|
541
|
+
// frequent) -- nice but too small -- and add it to all mountainous zones at a little less intensity as
|
|
542
|
+
// part of the mountain pattern, to simulate erosive detail'). SAME vnoise2(fp/wl) basis as the bump above
|
|
543
|
+
// (so it reads as the same noise the user likes), but wl0 = 3*320 = 960m (3x wider / lower frequency).
|
|
544
|
+
// Gated by `mtnGate` (the rugged/mountain signal) so it ONLY lands in mountainous zones, and at LOWER
|
|
545
|
+
// amplitude than the base bump (ea0 5m vs 8m) per 'a little less intensity'. 4-octave ridged-leaning fBm
|
|
546
|
+
// for erosive gully/ridgeline shape. Added INSIDE vtxDisplace so it shares the LOD-invariant, seam-safe,
|
|
547
|
+
// face-local path (no per-patch step) and is picked up by the composeHeight central-diff lit normal.
|
|
548
|
+
float mtnGate = smoothstep(0.55, 1.0, rugged); // ramps in over the mountain belt (rugged ~ elevAmp)
|
|
549
|
+
if (mtnGate > 0.0) {
|
|
550
|
+
float ea = 2.5, ewl = 2880.0, esumF = 0.0, esumR = 0.0; // user 2026-06-09: HALF the erosion elevation (5->2.5m) + 3x WIDER again (960->2880m)
|
|
551
|
+
for (int o = 0; o < 2; o++) { // 4->2 octaves (max-speed sweep: 720/360m fine erosion sub-vertex at GRID 16; coarse 2880/1440m carry the gully shape)
|
|
552
|
+
float en = vnoise2(fp / ewl);
|
|
553
|
+
esumF += ea * en;
|
|
554
|
+
float er = 1.0 - abs(en); er *= er;
|
|
555
|
+
esumR += ea * (er * 2.0 - 1.0);
|
|
556
|
+
ea *= 0.55; ewl *= 0.5;
|
|
557
|
+
}
|
|
558
|
+
sum += mix(esumF, esumR * 0.9, 0.6) * mtnGate; // ridged-leaning erosive relief, mountain-gated
|
|
559
|
+
}
|
|
560
|
+
float ruggedAmp = clamp(rugged, 0.4, 1.15);
|
|
561
|
+
return sum * vtxDetail * ruggedAmp * uHiFreqCut; // 4x high-freq cut (uHiFreqCut default 0.25)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// PERLIN-EVERYWHERE detail fbm (user 2026-06-10): ONE 3-octave value fbm shared by the FS albedo
|
|
565
|
+
// overlay and the composeHeight elevation term below (same field -> the brightness variation and the
|
|
566
|
+
// relief variation correlate, reading as one landform). World-dir keyed, seam-safe.
|
|
567
|
+
highp float detailFbm(vec3 dir) {
|
|
568
|
+
float ov = 0.0, oa = 0.0;
|
|
569
|
+
float fq = 150.0, am = 1.0; // octaves: ~42km / 8.5km (3rd 1.7km octave dropped, max-speed sweep: sub-pixel at altitude, -3 taps/vertex)
|
|
570
|
+
for (int o = 0; o < 2; o++) {
|
|
571
|
+
ov += am * snoise3(dir * fq + vec3(float(o) * 7.3));
|
|
572
|
+
oa += am;
|
|
573
|
+
fq *= 5.0; am *= 0.75;
|
|
574
|
+
}
|
|
575
|
+
return ov / oa;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// THC-Normal W1: the SINGLE composite height field. Lifts the inline vH accumulation out of the VS
|
|
579
|
+
// main() (was terrain.glsl ~801-902) into ONE value-only scalar so geometry (VS) and collision (PROBE)
|
|
580
|
+
// are derived from the EXACT same composition -- aligned by
|
|
581
|
+
// construction, no parallel mirror to drift. Returns the signed elevation h (metres) for dir0; the
|
|
582
|
+
// caller adds nothing (cbias is folded in here). dir0 = world dir of the sample (faceWarp'd),
|
|
583
|
+
// faceLocal = face-local warped metres (for vtxDisplace), tileM = quad size (Nyquist fade).
|
|
584
|
+
highp float composeHeight(vec3 dir0, highp vec2 faceLocal, float tileM){ // W7: faceLocal metres + returned h -> highp islands
|
|
585
|
+
vec4 hpf0 = hpfSample(dir0); // (seaBias=r, elevAmp=g, temp=b, humid=a)
|
|
586
|
+
highp float cbias = hpf0.r; // W7: seaBias metres
|
|
587
|
+
float rugged = ruggedFromElevAmp(hpf0.g);
|
|
588
|
+
float vDisp = vtxDisplace(faceLocal, tileM, rugged);
|
|
589
|
+
// per-biome relief from the anchor sample (mirror of the old VS main morphology)
|
|
590
|
+
float bTemp = hpf0.b, bHum = hpf0.a, bAmp = hpf0.g;
|
|
591
|
+
// mtn band: uMtnBandWide=1 spreads the 16.8->18.6 contour to 14.5->19.5 (covers the live elevAmp
|
|
592
|
+
// range ~14.5-18.9) so the belt-massif/reliefMul transition is a planet-wide gradient, not a thin
|
|
593
|
+
// iso-contour snapping a 2600m bulk lift on across gentle land (workflow wrxo0rr7a topSuspect, 2600m).
|
|
594
|
+
float mtn = smoothstep(mix(16.8, 14.5, uMtnBandWide), mix(18.6, 19.5, uMtnBandWide), bAmp);
|
|
595
|
+
// climate reliefMul gates: uClimateRelief=1 widens both bands so the cold/wet flattening fades over
|
|
596
|
+
// latitude/moisture gradients instead of a per-cell contour (wrxo0rr7a coldFlat/wetLowFlat 80m+).
|
|
597
|
+
float wetLowFlat = smoothstep(mix(0.66, 0.50, uClimateRelief), mix(0.9, 1.0, uClimateRelief), bHum) * (1.0 - mtn);
|
|
598
|
+
float coldFlat = (1.0 - smoothstep(mix(0.18, 0.05, uClimateRelief), mix(0.34, 0.45, uClimateRelief), bTemp));
|
|
599
|
+
float reliefMul = clamp(0.45 + 1.25 * mtn - 0.30 * wetLowFlat - 0.25 * coldFlat, 0.15, 1.7);
|
|
600
|
+
float ridgeMul = clamp(mtn * 1.1, 0.0, 1.0);
|
|
601
|
+
// ISLAND-TYPE VARIETY (pure fn of world dir) -- mirror of the VS main isle block.
|
|
602
|
+
// uIsleWide=1 spreads the seaBias double-gate so the volcanic/atoll remix ramps in (wrxo0rr7a 2000m).
|
|
603
|
+
float isleZone = smoothstep(mix(50.0, 30.0, uIsleWide), mix(350.0, 600.0, uIsleWide), cbias)
|
|
604
|
+
* (1.0 - smoothstep(mix(900.0, 600.0, uIsleWide), mix(1600.0, 2200.0, uIsleWide), cbias));
|
|
605
|
+
if (isleZone > 0.0) {
|
|
606
|
+
float isleType = snoise3(dir0 * 9.0);
|
|
607
|
+
float volcanic = smoothstep(0.25, 0.7, isleType);
|
|
608
|
+
float atoll = smoothstep(0.25, 0.7, -isleType);
|
|
609
|
+
reliefMul = mix(reliefMul, mix(reliefMul, 1.6, volcanic) * (1.0 - 0.7 * atoll), isleZone);
|
|
610
|
+
ridgeMul = mix(ridgeMul, max(ridgeMul, 0.9 * volcanic), isleZone);
|
|
611
|
+
}
|
|
612
|
+
highp float bShape = broadShapeM(dir0, reliefMul, ridgeMul); // W7: metres
|
|
613
|
+
highp float h = cbias + bShape; // W7: composite elevation metres (~13000)
|
|
614
|
+
// REALISTIC BATHYMETRY (user 2026-06-11 'the depth under the water doesnt seem right -- the
|
|
615
|
+
// landscape should continue underwater realistically'): raw cbias+bShape plunges at land-relief
|
|
616
|
+
// gradients, so the seabed hit kilometre depths within sight of the beach. Real margins have a
|
|
617
|
+
// wide gently-sloped CONTINENTAL SHELF (0..~120m over tens of km), then a steeper continental
|
|
618
|
+
// SLOPE down to the abyssal plain (which keeps its raw depth). Monotone remap of h, pure fn of
|
|
619
|
+
// the field -> seam-safe, LOD-invariant, and the collision probe shares it by construction.
|
|
620
|
+
if (h < 0.0) {
|
|
621
|
+
highp float d = -h;
|
|
622
|
+
h = -(min(d, 500.0) * 0.24 + max(d - 500.0, 0.0) * 1.19);
|
|
623
|
+
}
|
|
624
|
+
// displacement now continues UNDERWATER (the old land-only gate served the flat-clamped ocean,
|
|
625
|
+
// gone since 026d530): the seabed carries the same micro-relief as land = realistic continuation.
|
|
626
|
+
h += vDisp;
|
|
627
|
+
// PERLIN-EVERYWHERE ELEVATION (user 2026-06-10 'it must also affect elevation'): the same detailFbm
|
|
628
|
+
// the FS albedo overlay shows, as real relief (~30m per lever unit -> ~180m at the user-tuned 6).
|
|
629
|
+
// Shore-gated (fades in over the first 250m of land) so the coastline and the flat water planes are
|
|
630
|
+
// untouched and no noise islets pop offshore. The VS FD lit-normal picks it up automatically.
|
|
631
|
+
h += detailFbm(dir0) * uDetailOverlay * 30.0 * smoothstep(0.0, 250.0, h);
|
|
632
|
+
// LAKE CARVE + flat-water plane
|
|
633
|
+
float lakeWetV; float lakeCarveRaw = lakeCarveM(dir0, lakeWetV);
|
|
634
|
+
// uCarveWide=1 widens the carve CLIMATE gates so the gorge/lake/dune depth fades in over a wide
|
|
635
|
+
// anchor span instead of snapping along a thin climate contour (wrxo0rr7a carve cluster 90-1400m).
|
|
636
|
+
float lakeGate = smoothstep(mix(0.60, 0.50, uCarveWide), mix(0.85, 0.95, uCarveWide), hpf0.a) * step(0.0, h);
|
|
637
|
+
float lakeCarveV = lakeCarveRaw * lakeGate;
|
|
638
|
+
h += lakeCarveV;
|
|
639
|
+
float lakeWet = lakeWetV * lakeGate;
|
|
640
|
+
if (lakeWet > 0.0) { float waterLevel = max(h, 0.0) - 25.0; h = mix(h, waterLevel, lakeWet); vDisp *= (1.0 - lakeWet); }
|
|
641
|
+
// RIVER + CANYON incision (clamped so coastal gorges never punch fake inland seas)
|
|
642
|
+
float riverWet = smoothstep(mix(0.30, 0.20, uCarveWide), mix(0.55, 0.65, uCarveWide), hpf0.a) * smoothstep(mix(0.20, 0.12, uCarveWide), mix(0.34, 0.46, uCarveWide), hpf0.b);
|
|
643
|
+
float riverWetMask; float riverCarveV = riverCarveM(dir0, riverWetMask) * riverWet * step(0.0, h);
|
|
644
|
+
float canyonArid = (1.0 - smoothstep(mix(0.40, 0.30, uCarveWide), mix(0.58, 0.68, uCarveWide), hpf0.a)) * smoothstep(mix(0.38, 0.28, uCarveWide), mix(0.56, 0.66, uCarveWide), hpf0.b) * smoothstep(60.0, 200.0, h);
|
|
645
|
+
float canyonDepMask; float canyonCarveV = canyonCarveM(dir0, canyonDepMask) * canyonArid * step(0.0, h);
|
|
646
|
+
float inciseTot = riverCarveV + canyonCarveV;
|
|
647
|
+
// min(...,0): the floor term goes POSITIVE for any h below -60 and was LIFTING the entire ocean
|
|
648
|
+
// floor to exactly -60m -- the whole seabed was a uniform pan (root of 'depth under the water
|
|
649
|
+
// doesnt seem right'; probe-witnessed minSeen -60 planet-wide). Carves still cannot punch land
|
|
650
|
+
// below -60; the ocean keeps its real bathymetry.
|
|
651
|
+
inciseTot = max(inciseTot, min(-60.0 - h, 0.0)); // land: h + inciseTot >= -60m; ocean: untouched
|
|
652
|
+
h += inciseTot;
|
|
653
|
+
// CLIFF TERRACING (mesa/butte benches) -- after carves so canyon walls + risers compose
|
|
654
|
+
float cliffFaceMask; float cliffCarveV = cliffTerraceM(dir0, h, cliffFaceMask) * canyonArid * step(0.0, h);
|
|
655
|
+
h += cliffCarveV;
|
|
656
|
+
// FLAT RIVER WATER
|
|
657
|
+
float riverWetLine = riverWetMask * riverWet * step(0.0, h);
|
|
658
|
+
if (riverWetLine > 0.0) { float rWaterLevel = h - 20.0; h = mix(h, rWaterLevel, riverWetLine); vDisp *= (1.0 - riverWetLine); }
|
|
659
|
+
// DUNES on the low sand desert
|
|
660
|
+
float duneSand = smoothstep(mix(0.62, 0.50, uCarveWide), mix(0.85, 0.95, uCarveWide), 1.0 - hpf0.a) * smoothstep(mix(0.40, 0.30, uCarveWide), mix(0.58, 0.68, uCarveWide), hpf0.b) * (1.0 - smoothstep(40.0, 160.0, h));
|
|
661
|
+
float duneCrest; float duneV = duneFieldM(dir0, duneCrest) * duneSand * step(0.0, h);
|
|
662
|
+
h += duneV;
|
|
663
|
+
return h;
|
|
664
|
+
}
|
|
665
|
+
#endif // broadShapeM/broadShape/vtxDisplace/composeHeight: VS/PROBE (excluded from render FS, FS-1)
|
|
666
|
+
|
|
667
|
+
#ifdef _VERTEX_
|
|
668
|
+
layout(location=0) in vec3 vertex; // vertex.xy in [0,1] parametric quad coord
|
|
669
|
+
layout(location=1) in highp vec4 iOffset; // W7: PER-INSTANCE (ox, oy, l, level) face-local metres ~6.4e6 -> highp
|
|
670
|
+
layout(location=2) in float iFace; // PER-INSTANCE cube face index 0..5
|
|
671
|
+
out highp vec3 vWorld; // W7 highp ISLAND: absolute world pos ~6.4e6 m (FS lighting/atmosphere) -- fp16 would jitter it
|
|
672
|
+
out highp float vH; // W7: signed elevation (metres, ~13000) -- highp for the material ramp / strata / ocean depth
|
|
673
|
+
out highp vec3 vNrm; // W8: world-space per-vertex analytic normal (fixed-step central diff of the FULL composeHeight). The SOLE FS lit normal -- replaces the jittery cross(dFdx,dFdy). highp to match vWorld on the ~6.4e6 m planet.
|
|
674
|
+
out vec3 vTexWarp; // texture domain warp, computed ONCE in the VS (2026-06-12 'make warp as performant
|
|
675
|
+
// as possible': was 9 snoise3 PER PIXEL in the FS splat; the warp waves are >=1.8km
|
|
676
|
+
// so per-vertex evaluation + linear interpolation is visually identical at any GRID).
|
|
677
|
+
// The FS applies it EXACTLY ONCE (wt += vTexWarp * uTexWarp) -- single-application
|
|
678
|
+
// by construction, every texture layer inherits it through the shared wt.
|
|
679
|
+
// W5: vNrmPV/vMacroSlope/vShadeAO out-varyings deleted -- the lit normal, rock-gate slope and per-vertex
|
|
680
|
+
// AO are all derived in the FS from the Sobel normal now (THC sole path).
|
|
681
|
+
// UNIFIED-FIELD water/incision masks: computed ONCE per vertex from the same carve fields that cut
|
|
682
|
+
// the geometry, then INTERPOLATED to the FS. The FS no longer re-evaluates the sharp ridged carve
|
|
683
|
+
// fields per-pixel (that was a separate high-freq evaluation that aliased = the biome-localized
|
|
684
|
+
// moire the user reported). One field, sampled at the vertices, smooth in between.
|
|
685
|
+
out float vLakeWet; // lake open-water mask (carve basin)
|
|
686
|
+
out float vRiverWet; // river thalweg wet line
|
|
687
|
+
out float vWaterDepth; // metres the flat inland-water plane sits ABOVE the local terrain floor (>0 = submerged)
|
|
688
|
+
out float vCanyonDep; // canyon gorge depth [0,1]
|
|
689
|
+
out float vCliffFace; // cliff/escarpment riser face [0,1] (1 = steep terrace face)
|
|
690
|
+
out float vDuneCrest; // dune crest [0,1]
|
|
691
|
+
out float vLevel; // quad LOD level (per-instance iOffset.w) for the patches debug view
|
|
692
|
+
out vec2 vGrid; // per-quad parametric mesh coord [0,1] (for the wireframe overlay)
|
|
693
|
+
out vec4 vClimate; // (seaBias, elevAmp, temp, humid) sampled ONCE per vertex from the HPF
|
|
694
|
+
// texture + INTERPOLATED, so the FS never reads the HPF texture per-pixel
|
|
695
|
+
// (that per-pixel texture read showed the HPF texel grid as UV lines/moire
|
|
696
|
+
// up close, biome colours following the blocky cells -- user: 'definitely UV').
|
|
697
|
+
|
|
698
|
+
// cube face index -> face-local->world rotation (col0=U, col1=V, col2=center). MUST match the JS
|
|
699
|
+
// FACE_FRAME in planet-orchestrator.js + render localToWorld3(). Built per-instance from iFace so
|
|
700
|
+
// the whole visible leaf set draws in ONE instanced call.
|
|
701
|
+
mat3 faceFrame(float f){
|
|
702
|
+
int i = int(f + 0.5);
|
|
703
|
+
if (i==0) return mat3( 0.0,0.0,-1.0, 0.0,1.0,0.0, 1.0,0.0,0.0); // +X
|
|
704
|
+
if (i==1) return mat3( 0.0,0.0, 1.0, 0.0,1.0,0.0, -1.0,0.0,0.0); // -X
|
|
705
|
+
if (i==2) return mat3( 1.0,0.0,0.0, 0.0,0.0,-1.0, 0.0,1.0,0.0); // +Y
|
|
706
|
+
if (i==3) return mat3( 1.0,0.0,0.0, 0.0,0.0, 1.0, 0.0,-1.0,0.0); // -Y
|
|
707
|
+
if (i==4) return mat3( 1.0,0.0,0.0, 0.0,1.0,0.0, 0.0,0.0,1.0); // +Z
|
|
708
|
+
return mat3(-1.0,0.0,0.0, 0.0,1.0,0.0, 0.0,0.0,-1.0); // -Z
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// continental elevation bias (meters) from the HPF (shared hpfSample), as a continuous
|
|
712
|
+
// function of world dir -> replaces the old hardcoded sin/cos lobe with the editable field.
|
|
713
|
+
highp float continentalBias(vec3 dir) { return hpfSample(dir).r; } // W7: R = seaBias (meters) -> highp
|
|
714
|
+
|
|
715
|
+
// ---- CONTINUOUS BROAD+MID SHAPE (LOD-uniformity fix, user-directive 2026-05-30). The Proland
|
|
716
|
+
// per-level g_noiseAmp cascade adds a DIFFERENT noise draw at each LOD, so consecutive levels
|
|
717
|
+
// look different (1500km vs 1200km "way different"). The fix: source the broad+regional SHAPE
|
|
718
|
+
// from ONE continuous fBm of WORLD DIRECTION, sampled the same at every LOD -> a finer level is
|
|
719
|
+
// a denser SAMPLE of the SAME field, divergence ~0 BY CONSTRUCTION (CLI continuous-field-proof
|
|
720
|
+
// meanDiv 0.02-0.08 vs cascade 0.52). Because it is a pure function of world dir it is:
|
|
721
|
+
// - LOD-invariant -> identical in zf and zc, so CLOD blend is automatically smooth
|
|
722
|
+
// - C0 across tile AND cube-face boundaries -> seamless by construction (no seam probe needed)
|
|
723
|
+
// The wasm cascade is demoted (g_noiseAmp L0-6 ~0) so it only adds fine sub-silhouette detail.
|
|
724
|
+
// (shash3/snoise3 are defined in the SHARED preamble above so the FS riverMask can use them too.)
|
|
725
|
+
// broad+mid fBm of unit world dir -> metres. ONE field sampled IDENTICALLY at every LOD: a finer
|
|
726
|
+
// LOD is a denser SAMPLE of the SAME function, so consecutive LODs are maximally similar BY
|
|
727
|
+
// CONSTRUCTION (CLI continuous-field-proof meanDivergence 0.02 vs cascade 0.52). Crucially there
|
|
728
|
+
// is NO tile-size/LOD term here -- a per-tile octave fade was tried and REFUTED live (it made the
|
|
729
|
+
// field itself LOD-DEPENDENT: 1500km faded out octaves a 1200km tile kept, so they diverged).
|
|
730
|
+
// 8 fixed octaves span continent->regional->local; baseFreq sets the coarsest continent
|
|
731
|
+
// wavelength; seaBias offset sets land fraction (~0.3). Validated regime A0 5000, gain 0.6.
|
|
732
|
+
// PER-BIOME RELIEF (reliefMul, ridgeMul): the continent BASE octaves (o0-5) are ALWAYS full so
|
|
733
|
+
// the land/sea silhouette + hypsometry + LOD-uniformity are untouched (they were CLI/browser
|
|
734
|
+
// validated). The REGIONAL+FINE octaves (o6-13) are scaled by reliefMul and folded toward RIDGES
|
|
735
|
+
// by ridgeMul, BOTH supplied per-point from the anchor biome (mountains: high relief + ridged;
|
|
736
|
+
// plains/meadow: low relief; desert: medium smooth; swamp/tundra: very low). This makes the
|
|
737
|
+
// SHAPE distinguish biomes (not just color) WITHOUT breaking the broad silhouette or LOD-safety
|
|
738
|
+
// (still a pure fn of world dir -> denser-sample-of-same-field, no per-LOD term).
|
|
739
|
+
// ---- PER-VERTEX micro-relief: the FINE CONTINUATION of the single terrain field, below the
|
|
740
|
+
// scale broadShapeM resolves at vertex rate. Keyed on ABSOLUTE world wavelengths (fixed metres),
|
|
741
|
+
// NOT tile size, so a finer LOD is a DENSER SAMPLE of the SAME fine field -> detail grows
|
|
742
|
+
// monotonically on approach and never "pops" between LODs (the old tileM-relative wavelength was
|
|
743
|
+
// a different draw per level, which popped, so it was disabled). Pure fn of face-local world
|
|
744
|
+
// position (continuous across tiles on a face -> seamless). Default ON: this is what gives a
|
|
745
|
+
// close-up patch real relief instead of sitting flat inside one broad feature.
|
|
746
|
+
// vtxDetail/vhash/vnoise2/vnoise2D MOVED to the common preamble (THC-Normal W1) so composeHeight() can
|
|
747
|
+
// call them; vtxDisplace MOVED to the shared VS/PROBE region. (were here.)
|
|
748
|
+
// anchor elevAmp -> rugged multiplier. MEASURED LIVE (browser-1887): rendered elevAmp clusters
|
|
749
|
+
// ~8.0-8.5 (NOT the full 7.5-10.5 band range), so the old smoothstep(7.8,9.6) damped EVERYWHERE
|
|
750
|
+
// to ~0.2 -> the whole planet read flat. Recalibrate to the actual distribution: map [7.9,8.7]
|
|
751
|
+
// so typical terrain gets meaningful relief (~0.5-1.8) and only genuinely-low belts soften,
|
|
752
|
+
// high belts amplify into mountains. Floor 0.5 keeps plains gently rolling, not dead-flat.
|
|
753
|
+
// elevAmp range is REALLY ~29-40 (browser-4626), not ~8 -- the old smoothstep(7.9,8.7) saturated to
|
|
754
|
+
// 1.8 everywhere (uniform rugged -> uniform bumpiness, no plains/mountain differentiation). Map the
|
|
755
|
+
// real range so plains (low elevAmp) are gently rolling and only the high tail is rugged.
|
|
756
|
+
// ruggedFromElevAmp + faceWarp MOVED to the common preamble (THC-Normal W1, shared by composeHeight).
|
|
757
|
+
|
|
758
|
+
void main() {
|
|
759
|
+
// PER-INSTANCE deform params (one instanced draw over the whole leaf set): the quad origin+size
|
|
760
|
+
// and its face frame come from the instance attributes, replacing the old per-quad uniforms.
|
|
761
|
+
highp vec4 defOffset = iOffset; // W7: face-local metres ~6.4e6 -> highp
|
|
762
|
+
mat3 defLocalToWorld = faceFrame(iFace); // face-local -> world rotation
|
|
763
|
+
// (elevation atlas removed -- terrain height is the GPU fractal broadShapeM, no tile sample.)
|
|
764
|
+
// world direction of this vertex (pre-deform) for the continental mask.
|
|
765
|
+
vec3 dir0 = normalize(defLocalToWorld * vec3(faceWarp(vertex.xy * defOffset.z + defOffset.xy), defRadius)); // W7: faceWarp/defRadius highp -> pre-normalize ~6.4e6 stays highp
|
|
766
|
+
highp vec4 hpf0 = hpfSample(dir0); // (seaBias=r, elevAmp=g, temp=b, humid=a) -- W7: R seaBias metres
|
|
767
|
+
highp float cbias = hpf0.r; // W7: seaBias metres
|
|
768
|
+
// ANCHOR-DRIVEN morphology: the anchor elevAmp belt decides whether this region is flat
|
|
769
|
+
// lowland (floodplain/meadow) or rugged mountain -- the anchor map is the primary spreader.
|
|
770
|
+
float rugged = ruggedFromElevAmp(hpf0.g);
|
|
771
|
+
// per-vertex micro-relief: unique height per vertex from world-continuous face-local pos
|
|
772
|
+
// (fixes the flat 2x2 atlas-texel blocks; gives sub-mesh-cell relief). Only on land.
|
|
773
|
+
highp vec2 faceLocal = faceWarp(vertex.xy * defOffset.z + defOffset.xy); // W7: warped metres ~6.4e6 -> highp
|
|
774
|
+
float vDisp = vtxDisplace(faceLocal, defOffset.z, rugged);
|
|
775
|
+
// CONTINUOUS broad+mid SHAPE (LOD-uniformity fix): one world-dir fBm sampled the SAME at
|
|
776
|
+
// every LOD -> no per-level shape divergence. PER-BIOME RELIEF: scale the regional+fine octaves
|
|
777
|
+
// (continent base untouched -> silhouette/hypso/LOD-uniformity preserved) by an anchor-derived
|
|
778
|
+
// reliefMul + ridgeMul so the SHAPE distinguishes biomes: mountains (high elevAmp) = strong +
|
|
779
|
+
// ridged; meadow/plains = gentle. All pure fn of world dir -> still seam-safe + LOD-invariant.
|
|
780
|
+
float bTemp = hpf0.b, bHum = hpf0.a, bAmp = hpf0.g;
|
|
781
|
+
// MOUNTAIN-BELT GATE on the BAKED elevAmp range (~14.5-18.9): the high tail (~17%) is the
|
|
782
|
+
// mountain belt; the rest is genuine plains so the mountain biome is a differentiated minority.
|
|
783
|
+
float mtn = smoothstep(16.8, 18.6, bAmp); // mountain-belt weight 0..1
|
|
784
|
+
float wetLowFlat = smoothstep(0.66, 0.9, bHum) * (1.0 - mtn); // swamp/wetland -> flat
|
|
785
|
+
float coldFlat = (1.0 - smoothstep(0.18, 0.34, bTemp)); // tundra/ice -> flat
|
|
786
|
+
float reliefMul = clamp(0.45 + 1.25 * mtn - 0.30 * wetLowFlat - 0.25 * coldFlat, 0.15, 1.7);
|
|
787
|
+
float ridgeMul = clamp(mtn * 1.1, 0.0, 1.0); // ridged crests only in mountain belts
|
|
788
|
+
// ISLAND-TYPE VARIETY: small offshore swells get a per-region type (volcanic cone / low atoll)
|
|
789
|
+
// from a world-dir noise so islands are not all scaled continents. Pure fn of world dir.
|
|
790
|
+
float isleZone = smoothstep(50.0, 350.0, cbias) * (1.0 - smoothstep(900.0, 1600.0, cbias));
|
|
791
|
+
if (isleZone > 0.0) {
|
|
792
|
+
float isleType = snoise3(dir0 * 9.0);
|
|
793
|
+
float volcanic = smoothstep(0.25, 0.7, isleType);
|
|
794
|
+
float atoll = smoothstep(0.25, 0.7, -isleType);
|
|
795
|
+
reliefMul = mix(reliefMul, mix(reliefMul, 1.6, volcanic) * (1.0 - 0.7 * atoll), isleZone);
|
|
796
|
+
ridgeMul = mix(ridgeMul, max(ridgeMul, 0.9 * volcanic), isleZone);
|
|
797
|
+
}
|
|
798
|
+
highp float bShape = broadShapeM(dir0, reliefMul, ridgeMul); // W7: metres
|
|
799
|
+
// ONE FRACTAL: the continuous world-dir field (bShape) + the continental swell (cbias) carry the
|
|
800
|
+
// entire silhouette+hypsometry. The old wasm upsample-and-add cascade (zfc.x) was REMOVED -- it
|
|
801
|
+
// was a SECOND shape source that diverged per-LOD (the detail-inversion root). bShape amplitude
|
|
802
|
+
// was retuned (A0 6500, off -900) so this single field hits Earth hypso without the cascade.
|
|
803
|
+
vH = cbias + bShape;
|
|
804
|
+
// shelf/slope bathymetry remap + underwater displacement -- MUST mirror composeHeight exactly
|
|
805
|
+
// (this is the FS-material running value; composeHeight is the geometry/probe height).
|
|
806
|
+
if (vH < 0.0) {
|
|
807
|
+
highp float dSea = -vH;
|
|
808
|
+
vH = -(min(dSea, 500.0) * 0.24 + max(dSea - 500.0, 0.0) * 1.19);
|
|
809
|
+
}
|
|
810
|
+
vH += vDisp;
|
|
811
|
+
// LAKE CARVE: carve a real basin into the elevation on WET LAND so lakes are part of the
|
|
812
|
+
// fractal (no fade-in). Gated to land (vH>0) + wet regions (humid) so it does not pit deserts
|
|
813
|
+
// or deepen the ocean. World-dir field -> identical at every LOD.
|
|
814
|
+
float lakeWetV; float lakeCarveRaw = lakeCarveM(dir0, lakeWetV);
|
|
815
|
+
float lakeGate = smoothstep(0.60, 0.85, hpf0.a) * step(0.0, vH);
|
|
816
|
+
float lakeCarveV = lakeCarveRaw * lakeGate;
|
|
817
|
+
vH += lakeCarveV;
|
|
818
|
+
// FLAT WATER: inside the wet core the surface must read as flat water (user: 'water should be
|
|
819
|
+
// flat'), not a noisy bowl floor. Pin vH toward a constant water level (a few m below the local
|
|
820
|
+
// rim) weighted by the wet mask, and suppress the micro-relief there. The lakeCarve shoulder
|
|
821
|
+
// already erodes/grades the surrounding terrain into the basin (the 'eroded margin').
|
|
822
|
+
float lakeWet = lakeWetV * lakeGate;
|
|
823
|
+
highp float waterPlane = 0.0; highp float floorBefore = 0.0; float haveWater = 0.0; // W7: waterPlane/floorBefore are metres (~13000); their small DIFFERENCE (vWaterDepth) needs highp to avoid cancellation
|
|
824
|
+
if (lakeWet > 0.0) {
|
|
825
|
+
highp float waterLevel = max(vH, 0.0) - 25.0; // flat plane just below the rim
|
|
826
|
+
floorBefore = vH; // carved bowl floor before flattening
|
|
827
|
+
vH = mix(vH, waterLevel, lakeWet);
|
|
828
|
+
waterPlane = waterLevel; haveWater = lakeWet;
|
|
829
|
+
vDisp *= (1.0 - lakeWet); // no micro-bumps on the water surface
|
|
830
|
+
}
|
|
831
|
+
// RIVER + CANYON INCISION into the elevation (user: content makes up part of the elevation as
|
|
832
|
+
// erosion works). Both are pure world-dir carves (LOD-invariant, no fade). River: wet/temperate
|
|
833
|
+
// land. Canyon: arid + elevated (h>~60m) land, opposite climate to rivers. Gated to land
|
|
834
|
+
// (step(0,vH)) so they never punch the ocean or create fake seas. The incision is bounded so it
|
|
835
|
+
// does not drive coastal land below sea level (clamp the post-carve vH floor handled implicitly
|
|
836
|
+
// by the small depths + land gate; coastline hypso re-witnessed in incision-hypso-landfrac-gate).
|
|
837
|
+
float riverWet = smoothstep(0.30, 0.55, hpf0.a) * smoothstep(0.20, 0.34, hpf0.b); // moist, not frozen
|
|
838
|
+
float riverWetMask; float riverCarveV = riverCarveM(dir0, riverWetMask) * riverWet * step(0.0, vH);
|
|
839
|
+
float canyonArid = (1.0 - smoothstep(0.40, 0.58, hpf0.a)) * smoothstep(0.38, 0.56, hpf0.b) * smoothstep(60.0, 200.0, vH);
|
|
840
|
+
float canyonDepMask; float canyonCarveV = canyonCarveM(dir0, canyonDepMask) * canyonArid * step(0.0, vH);
|
|
841
|
+
// NO-SUB-SEA-COAST GUARD (user 2026-06-02 deepen+widen): with the deepened canyon (-1400m + gullies)
|
|
842
|
+
// a gorge on 200m coastal land would punch vH to ~-1200m = fake inland seas. Clamp the TOTAL incision
|
|
843
|
+
// so post-carve land bottoms out at a small floor (-60m: a gorge may reach near sea level but never
|
|
844
|
+
// carves a deep basin below it). Bounded against the PRE-carve vH so deep inland canyons keep full
|
|
845
|
+
// depth while coastal ones are limited by their own available headroom.
|
|
846
|
+
float inciseTot = riverCarveV + canyonCarveV; // both negative (downcut)
|
|
847
|
+
inciseTot = max(inciseTot, min(-60.0 - vH, 0.0)); // land floor only -- ocean keeps real depth (see composeHeight note)
|
|
848
|
+
vH += inciseTot;
|
|
849
|
+
// CLIFF TERRACING: snap the arid+elevated land into flat benches with steep risers (mesa/butte
|
|
850
|
+
// cliff country). Gated by the SAME canyonArid mask so cliffs share canyon regions (a coherent
|
|
851
|
+
// arid badlands look). The snap delta is added to vH; cliffFaceMask (->1 on a riser face) goes to
|
|
852
|
+
// the FS for strata banding + steep-rock material. Applied AFTER carves so canyon walls + cliff
|
|
853
|
+
// risers compose. step(0,vH) keeps it on land.
|
|
854
|
+
float cliffFaceMask; float cliffCarveV = cliffTerraceM(dir0, vH, cliffFaceMask) * canyonArid * step(0.0, vH);
|
|
855
|
+
vH += cliffCarveV;
|
|
856
|
+
// FLAT RIVER WATER (user: 'rivers/lakes should NOT be bumpy'): like lakes, the river thalweg
|
|
857
|
+
// surface must read as flat water, not the bumpy micro-relief floor. Pin vH down to the channel
|
|
858
|
+
// water level + suppress the micro-displacement on the wet line. riverWetMask is the thalweg mask.
|
|
859
|
+
float riverWetLine = riverWetMask * riverWet * step(0.0, vH);
|
|
860
|
+
if (riverWetLine > 0.0) {
|
|
861
|
+
highp float rWaterLevel = vH - 20.0; // W7: metres
|
|
862
|
+
highp float rFloorBefore = vH;
|
|
863
|
+
vH = mix(vH, rWaterLevel, riverWetLine);
|
|
864
|
+
vDisp *= (1.0 - riverWetLine); // no micro-bumps on the river surface
|
|
865
|
+
// record the deeper/stronger of lake|river as the water surface for vWaterDepth
|
|
866
|
+
if (riverWetLine > haveWater) { waterPlane = rWaterLevel; floorBefore = rFloorBefore; haveWater = riverWetLine; }
|
|
867
|
+
}
|
|
868
|
+
// DUNES on the SAND DESERT (very dry + warm + LOW land): rolling dune relief replaces harsh rock
|
|
869
|
+
// here (ref: webgl-dunes). Gated opposite to canyons (which want elevated arid plateaus) -- dunes
|
|
870
|
+
// ride the low desert floor. World-dir field -> LOD-invariant.
|
|
871
|
+
float duneSand = smoothstep(0.62, 0.85, 1.0 - hpf0.a) * smoothstep(0.40, 0.58, hpf0.b) * (1.0 - smoothstep(40.0, 160.0, vH));
|
|
872
|
+
float duneCrest; float duneV = duneFieldM(dir0, duneCrest) * duneSand * step(0.0, vH);
|
|
873
|
+
vH += duneV;
|
|
874
|
+
|
|
875
|
+
// PER-VERTEX SEAMLESS NORMAL = (-dz/dx, -dz/dy, 1) in the tile tangent frame. After the
|
|
876
|
+
// one-fractal collapse the old cascade-atlas finite-difference term is gone (it was identically
|
|
877
|
+
// zero), so the lit normal is built purely from the micro-relief (dEx), broad-shape (bEx) and
|
|
878
|
+
// carve (rcEx) gradients below -- those ARE the one field.
|
|
879
|
+
// Include the per-vertex micro-displacement gradient so the new relief is LIT (not just
|
|
880
|
+
// geometrically displaced). MOIRE-ON-DESCENT FIX (user 2026-06-01h: fine green speckle/moire at
|
|
881
|
+
// closeup, GONE with vtxDetail off): the FD step was ONE MESH CELL (defOffset.z/24), which
|
|
882
|
+
// SHRINKS as tiles get fine on descent -> the micro-relief lit normal captured ever-higher-freq
|
|
883
|
+
// slope cell-to-cell = shimmer/moire (the same shrinking-step trap the broadShape FD warns of).
|
|
884
|
+
// Use a FIXED ~600m world step so the micro-relief SHADING reflects a stable slope at every
|
|
885
|
+
// altitude; the geometry still displaces per-vertex (vH), the normal just stops chasing the cell.
|
|
886
|
+
// W5: the per-vertex lit-normal gradient block (dEx/dEy from vtxDisplaceD; bEx/bEy/rcEx/rcEy/fEx/fEy
|
|
887
|
+
// from broadShapeMD + the cbias/carve central-differences) is DELETED -- it only fed the now-deleted
|
|
888
|
+
// vNrmPV/vMacroSlope assembly. THC's per-pixel Sobel is the sole lit normal. The carve VALUES (h) are
|
|
889
|
+
// computed above (~906-) and untouched; only their gradient finite-differences are gone.
|
|
890
|
+
// W5: the entire per-vertex normal/AO assembly (vMacroSlope, vShadeAO crease+micro AO, slopeGain, the
|
|
891
|
+
// vNrmPV true-gradient sum) is DELETED -- THC is the sole path, so the lit normal is the FS Sobel of
|
|
892
|
+
// heightPool and the rock-gate slope + per-vertex AO are recomputed in the FS from that Sobel normal.
|
|
893
|
+
// The gradient taps (dEx/dEy from vtxDisplaceD, bEx/bEy/rcEx/rcEy/fEx/fEy from broadShapeMD) that fed
|
|
894
|
+
// this block are removed upstream; only the SCALAR height fns (broadShapeM/vtxDisplace) remain, for the
|
|
895
|
+
// procedural h fallback when a leaf has no cached tile.
|
|
896
|
+
|
|
897
|
+
highp float R = defRadius; // W7: ~6.4e6 m -> highp
|
|
898
|
+
// ONE HEIGHT FUNCTION: every vertex gets the procedural composeHeight() (broadShapeM + cbias + carves
|
|
899
|
+
// + vtxDisplace, with lake/river water-plane flattening). vH (FS material/strata, computed above as the
|
|
900
|
+
// water-flattened running value) stays for the FS; the GEOMETRY height h is the single composeHeight.
|
|
901
|
+
// WATER GATE (perf sweep 2026-06-11): on the water pass the geometry height is pinned to sea level
|
|
902
|
+
// (hR=0 below) and the normal is radial (:914), so composeHeight's result is never consumed --
|
|
903
|
+
// skip the 12-oct eval for ~540 water instances/frame. Uniform-coherent branch, zero fidelity change.
|
|
904
|
+
// SINGLE-INSTANCE FD TAPS (2026-06-12, THE AMD ROOT -- witnessed by __flatNormal A/B: forcing the
|
|
905
|
+
// radial normal turned the whole 'rock patch' region back into smooth grass, so vNrm was the broken
|
|
906
|
+
// carrier). FXC inlines composeHeight per CALL SITE and optimizes each copy differently; the center
|
|
907
|
+
// and offset taps then disagree by tens of metres on FLAT ground -> fake slope -> rock material +
|
|
908
|
+
// slope-AO darkness + dead normals (d3d11-only; vulkan compiles one consistent version). Evaluating
|
|
909
|
+
// ALL THREE taps through ONE runtime-bounded loop forces FXC to emit a SINGLE composeHeight instance,
|
|
910
|
+
// so whatever approximations it picks cancel exactly in the differences. uNrmStepM>0.0 keeps the
|
|
911
|
+
// bound non-constant (same defeat class as uOctMax).
|
|
912
|
+
highp vec3 uzFD = dir0;
|
|
913
|
+
vec3 refAxisFD = (abs(uzFD.y) < 0.99) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
|
914
|
+
vec3 uxFD = normalize(cross(refAxisFD, uzFD));
|
|
915
|
+
vec3 uyFD = cross(uzFD, uxFD);
|
|
916
|
+
highp float nStepFD = (uNrmStepM > 0.0) ? uNrmStepM : 150.0;
|
|
917
|
+
highp float hFD0 = 0.0, hFD1 = 0.0, hFD2 = 0.0;
|
|
918
|
+
if (uIsWater < 0.5) {
|
|
919
|
+
int fdIters = (uNrmStepM >= 0.0) ? 3 : 1; // always 3; non-constant so FXC cannot unroll/split
|
|
920
|
+
for (int i = 0; i < fdIters; i++) {
|
|
921
|
+
highp vec3 dd = (i == 0) ? dir0
|
|
922
|
+
: normalize(uzFD + ((i == 1) ? uxFD : uyFD) * (nStepFD / defRadius));
|
|
923
|
+
highp float hi = composeHeight(dd, faceLocal, defOffset.z);
|
|
924
|
+
if (i == 0) hFD0 = hi; else if (i == 1) hFD1 = hi; else hFD2 = hi;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
highp float h = hFD0;
|
|
928
|
+
// OCEAN TOP = A SEPARATE, ELEVATION-BASED SURFACE (user 2026-06-10: 'terrain should extend into
|
|
929
|
+
// the ocean, the ocean top separate and elevation based'). composeHeight keeps carrying the TRUE
|
|
930
|
+
// signed bathymetry (vH -> the FS depth tint/Beer-Lambert keys on it), but the RENDERED surface
|
|
931
|
+
// smooth-clamps to sea level: open water is a flat plane at R (proper waterline + horizon), the
|
|
932
|
+
// seafloor field lives on UNDER it as the depth signal. The smoothstep ramp (not a hard max)
|
|
933
|
+
// also fixes 'the terrain curve creates a hard line at the base': the beach profile eases
|
|
934
|
+
// tangentially into the waterline over the last ~60m instead of meeting it in a crease.
|
|
935
|
+
// SEPARATE WATER SURFACE (user 2026-06-11): the terrain pass renders the TRUE seabed (the old
|
|
936
|
+
// smoothstep(-60,60) sea-level clamp is GONE -- sand/rock bathymetry is real geometry now); the
|
|
937
|
+
// water pass (uIsWater=1, second instanced draw of the same leaves) pins this mesh to sea level
|
|
938
|
+
// and shades it as the animated ocean, alpha-blended over the seabed (depth test keeps it
|
|
939
|
+
// behind land).
|
|
940
|
+
highp float hR = (uIsWater > 0.5) ? 0.0 : h;
|
|
941
|
+
// W8 ANALYTIC LIT NORMAL (P1 data-first): the normal is a property of the height FIELD, so derive it
|
|
942
|
+
// from the field, not from per-pixel screen derivatives. FIXED-WORLD-STEP central difference of
|
|
943
|
+
// composeHeight in the radial tangent frame -- STABLE (does NOT shrink with distance/per-cell -> no
|
|
944
|
+
// shimmer, see :769), full landform relief at ALL distances, no fade. Mirror of the proven nBand
|
|
945
|
+
// pattern but the FULL composeHeight (not macro-only broadShapeLowM). Offset ONLY dir0 (the world-dir
|
|
946
|
+
// gradient = the seam-safe landform relief); hold faceLocal/tileM fixed so the tile-local vtxDisplace
|
|
947
|
+
// term cancels in the difference (it carries no world-continuous gradient and would only add jitter).
|
|
948
|
+
vec3 uzN = dir0; // dir0 is already normalized; radial up
|
|
949
|
+
vec3 refAxisN = (abs(uzN.y) < 0.99) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
|
950
|
+
vec3 uxN = normalize(cross(refAxisN, uzN));
|
|
951
|
+
vec3 uyN = cross(uzN, uxN);
|
|
952
|
+
// FXC FOLD-DEFEAT (2026-06-12, same disease as the uOctMax unroll fix): with a LITERAL 150.0 the
|
|
953
|
+
// d3d11/FXC translator constant-folds/reassociates the tiny FD offset (150/R = 2.4e-5) feeding
|
|
954
|
+
// normalize(uzN + uxN*eps) -- the perturbed-direction taps then diverge wildly from h on FLAT
|
|
955
|
+
// ground (witnessed: probe slope 0.000 + flat wireframe mesh under whole regions the FS painted
|
|
956
|
+
// as rock; the FD normal claimed steep in noise-keyed PATCHES = 'rock without slope angle').
|
|
957
|
+
// A uniform-fed step denies FXC the constant, exactly like uOctMax denies it the loop bound.
|
|
958
|
+
highp float nStep = (uNrmStepM > 0.0) ? uNrmStepM : 150.0; // STABLE world step (m): > finest vtxDisplace octave (~20m, no alias),
|
|
959
|
+
// < carve band (~100m+, resolves canyons/cliffs). NOT pxWorld-scaled.
|
|
960
|
+
if (uIsWater > 0.5) {
|
|
961
|
+
vNrm = uzN; // water surface: radial normal; waves perturb it per-pixel in the FS (skips 2 composeHeight taps)
|
|
962
|
+
} else {
|
|
963
|
+
// taps come from the SINGLE-INSTANCE FD loop above (hFD0/1/2) -- never call composeHeight at
|
|
964
|
+
// separate call sites for the FD again; per-callsite FXC codegen divergence was the AMD root.
|
|
965
|
+
float dHduN = float(hFD1 - h) / nStep;
|
|
966
|
+
float dHdvN = float(hFD2 - h) / nStep;
|
|
967
|
+
vNrm = normalize(uxN * (-dHduN) + uyN * (-dHdvN) + uzN); // n = normalize([-dz/du,-dz/dv,1]) in the (ux,uy,uz) frame
|
|
968
|
+
}
|
|
969
|
+
// DIRECT per-vertex sphere projection (replaces the Proland deformedCorners*alphaPrime blend,
|
|
970
|
+
// which bilinearly interpolated 4 deformed corners -> FLAT quad interior -> faceted at high GRID).
|
|
971
|
+
// dir0 is THIS vertex's world direction (faceWarp'd, defLocalToWorld-mapped); place it on the
|
|
972
|
+
// sphere at radius R+h and project. Every vertex curves -> round at any tessellation.
|
|
973
|
+
// SKIRT: outer-ring verts (vertex.z==1, xy clamped to the true edge) drop radially below the
|
|
974
|
+
// surface to form a near-vertical curtain that hides LOD T-junction cracks without painting a
|
|
975
|
+
// visible flat band (the old overlap ring's artifact). Depth scales with the tile size so the
|
|
976
|
+
// skirt always reaches below a coarser neighbor's surface. Hidden behind the surface from above.
|
|
977
|
+
// water pass: NO skirt (user 2026-06-11) -- the surface is an exact sphere at R, so adjacent
|
|
978
|
+
// LODs agree exactly and there are no T-junction cracks to hide; a skirt would only drape a
|
|
979
|
+
// visible curtain through the transparent shallows.
|
|
980
|
+
highp float skirt = (vertex.z > 0.5 && uIsWater < 0.5) ? max(defOffset.z * 0.12, 60.0) : 0.0; // W7: metres (tile-size scaled) -> highp
|
|
981
|
+
vWorld = dir0 * (R + hR - skirt); // ABSOLUTE world pos (RENDER height: ocean top flat) -> FS lighting/atmosphere
|
|
982
|
+
// TEXTURE DOMAIN WARP -- VS-side, HALVED frequencies (user 2026-06-12 'halve the warp frequency' +
|
|
983
|
+
// 'as performant as possible'): lattice 900/3600/14000 -> 450/1800/7000 (waves 2x larger: ~88km/22km/5.6km
|
|
984
|
+
// at the same +/-1.2/0.6/0.3-tile amplitudes). Moved out of the per-pixel FS (-9 snoise3/pixel); dir0 is
|
|
985
|
+
// the same normalize(vWorld) the FS used, so the field is identical, just vertex-sampled.
|
|
986
|
+
{
|
|
987
|
+
highp vec3 w0 = dir0 * 450.0, w1 = dir0 * 1800.0, w2 = dir0 * 7000.0;
|
|
988
|
+
vTexWarp = vec3(snoise3(w0), snoise3(w0 + vec3(7.3)), snoise3(w0 + vec3(23.9))) * 1.2
|
|
989
|
+
+ vec3(snoise3(w1), snoise3(w1 + vec3(13.7)), snoise3(w1 + vec3(31.1))) * 0.6
|
|
990
|
+
+ vec3(snoise3(w2), snoise3(w2 + vec3(5.1)), snoise3(w2 + vec3(17.9))) * 0.3;
|
|
991
|
+
}
|
|
992
|
+
// CAMERA-RELATIVE PROJECTION (vertex-jitter fix): forming dir0*(R+h) at ~6.4e6m rounds to ~0.5m in
|
|
993
|
+
// fp32 -> vertices quantize and JITTER as the camera moves (the same fp32 cancellation gl-render.js
|
|
994
|
+
// documents for the cull, which the per-vertex draw path did NOT avoid). Build the SMALL camera-
|
|
995
|
+
// relative position directly -- (dir0-defCamDir)*R is the lateral offset computed from a unit-vector
|
|
996
|
+
// difference (precise, no 6.4e6 intermediate), plus the small radial terms -- and project with
|
|
997
|
+
// defViewProjNoEye (no folded translate(-eye)). Result magnitude ~horizon scale, fp32-precise.
|
|
998
|
+
highp vec3 vRel = (dir0 - defCamDir) * R + dir0 * (hR - skirt) - defCamDir * defCamAlt; // W7 highp ISLAND: camera-relative projection (render height; planet-scale fp32 cancellation fix kept intact)
|
|
999
|
+
gl_Position = defViewProjNoEye * vec4(vRel, 1.0);
|
|
1000
|
+
// UNIFIED carve masks -> FS (interpolated; the FS no longer re-evaluates the sharp carve fields
|
|
1001
|
+
// per-pixel). riverWetMask/canyonDepMask/duneCrest are the out-params captured above; lakeWetV
|
|
1002
|
+
// from the lake-carve block. Gated by the SAME climate masks the geometry used.
|
|
1003
|
+
vLakeWet = lakeWetV * lakeGate;
|
|
1004
|
+
vRiverWet = riverWetMask * riverWet * step(0.0, vH);
|
|
1005
|
+
// SUBMERGED DEPTH (user 2026-06-01i: 'carves dont line up with the water'). The inland-water
|
|
1006
|
+
// COLOR must land ONLY where the surface is actually at/below the flat water plane, not over the
|
|
1007
|
+
// whole carve mask (which includes the graded erosion shoulder ABOVE the waterline). vWaterDepth
|
|
1008
|
+
// = metres the water plane sits above the pre-flatten carved floor, >0 ONLY inside the true open
|
|
1009
|
+
// water; the FS gates ALL inland-water shading on this so blue == the flat water, banks stay land.
|
|
1010
|
+
vWaterDepth = max(waterPlane - floorBefore, 0.0) * step(0.001, haveWater);
|
|
1011
|
+
vCanyonDep = canyonDepMask * canyonArid * step(0.0, vH);
|
|
1012
|
+
vCliffFace = cliffFaceMask * canyonArid * step(0.0, vH);
|
|
1013
|
+
vDuneCrest = duneCrest * duneSand;
|
|
1014
|
+
vGrid = vertex.xy; // parametric mesh-cell coord for the wireframe overlay
|
|
1015
|
+
vLevel = defOffset.w; // quad LOD level -> FS patches view
|
|
1016
|
+
vClimate = hpf0; // (seaBias, elevAmp, temp, humid) -> FS reads this, not the HPF texture
|
|
1017
|
+
}
|
|
1018
|
+
#endif
|
|
1019
|
+
|
|
1020
|
+
#ifdef _FRAGMENT_
|
|
1021
|
+
in highp vec3 vWorld; // W7: MUST match the VS highp vWorld (world pos ~6.4e6 m) -- precision-mismatched varyings fail to link
|
|
1022
|
+
in vec3 vTexWarp; // VS-computed texture domain warp (halved freqs); applied once in the splat block
|
|
1023
|
+
in highp float vH; // W7: match VS highp vH (signed metres)
|
|
1024
|
+
in highp vec3 vNrm; // W8: world-space analytic normal from the VS (matches VS highp out). Sole lit normal.
|
|
1025
|
+
in float vLakeWet; // carve masks computed in the VS, interpolated (no per-pixel re-eval -> no moire)
|
|
1026
|
+
in float vRiverWet;
|
|
1027
|
+
in float vWaterDepth; // metres of submerged water (>0 = real open inland water at the flat plane)
|
|
1028
|
+
in float vCanyonDep;
|
|
1029
|
+
in float vCliffFace;
|
|
1030
|
+
in float vDuneCrest;
|
|
1031
|
+
in float vLevel; // quad LOD level (patches view)
|
|
1032
|
+
in vec2 vGrid; // per-quad parametric mesh coord (wireframe overlay)
|
|
1033
|
+
uniform float uWireframe; // 1 = overlay the mesh grid lines (window/cam wireframe toggle)
|
|
1034
|
+
uniform float uFsCheap; // GPU-TIMER VS/FS attribution: 1 = short-circuit the FS to a trivial
|
|
1035
|
+
// constant color immediately after the per-vertex normal is read, so a
|
|
1036
|
+
// timed cheap frame measures VS+raster cost only; (full - cheap) = FS cost.
|
|
1037
|
+
// Set by window.__gpuTimer's measure frame (gl-render). 0 in normal render.
|
|
1038
|
+
in vec4 vClimate; // (seaBias, elevAmp, temp, humid) interpolated -- FS does NOT sample the HPF texture
|
|
1039
|
+
layout(location=0) out vec4 fragColor;
|
|
1040
|
+
|
|
1041
|
+
uniform vec3 sunDir; // world-space sun direction (normalized) -- unit, mediump-safe
|
|
1042
|
+
uniform int displayMode; // 0 = lit, 1 = raw normals, 2 = material albedo (unlit)
|
|
1043
|
+
uniform highp vec3 camWorld; // W7: world-space camera position ~6.4e6 m -> highp
|
|
1044
|
+
uniform highp float terrainR; // W7: sphere radius ~6.4e6 m -> highp
|
|
1045
|
+
uniform highp float oceanTime; // W7: animation time (seconds) grows unbounded -> highp (fp16 would freeze the wave phase)
|
|
1046
|
+
uniform float oceanAmp; // wave amplitude scale (0..1+), drives normal perturbation
|
|
1047
|
+
uniform float oceanChoppy; // wave directional sharpness / count weighting
|
|
1048
|
+
uniform float oceanFoam; // foam amount (0..1): whitecaps on steep wave slopes
|
|
1049
|
+
// CONTINUOUS sampled world position (seamless across tiles), 0 = atlas normal
|
|
1050
|
+
|
|
1051
|
+
// ---- Animated ocean: sum-of-Gerstner-wave NORMAL perturbation in the surface tangent
|
|
1052
|
+
// frame. We don't displace geometry (FS-only v1) -- we synthesize an animated water
|
|
1053
|
+
// normal from a few directional waves and shade it with fresnel + sun glint + a depth
|
|
1054
|
+
// tint. wave dirs are 2D in the local (ux,uy) tangent plane; phase advances with time.
|
|
1055
|
+
// Returns a tangent-space normal perturbation (dx,dy) to add to the flat (0,0,1) normal.
|
|
1056
|
+
vec2 oceanWaveSlope(highp vec2 p, highp float t) { // W7: p = camera-relative wave coord, t = unbounded oceanTime -> highp phase
|
|
1057
|
+
// a handful of directional waves with varied freq/dir/speed (kept cheap)
|
|
1058
|
+
vec2 slope = vec2(0.0);
|
|
1059
|
+
// (dir.xy, wavelength_m, speed, steepness)
|
|
1060
|
+
const int N = 5;
|
|
1061
|
+
vec2 dirs[5]; float wl[5]; float spd[5]; float amp[5];
|
|
1062
|
+
dirs[0]=vec2( 1.0, 0.0); wl[0]=520.0; spd[0]=1.10; amp[0]=1.0;
|
|
1063
|
+
dirs[1]=vec2( 0.6, 0.8); wl[1]=310.0; spd[1]=1.35; amp[1]=0.7;
|
|
1064
|
+
dirs[2]=vec2(-0.4, 0.9); wl[2]=170.0; spd[2]=1.60; amp[2]=0.5;
|
|
1065
|
+
dirs[3]=vec2( 0.9,-0.3); wl[3]= 95.0; spd[3]=2.10; amp[3]=0.35;
|
|
1066
|
+
dirs[4]=vec2(-0.7,-0.6); wl[4]= 47.0; spd[4]=2.80; amp[4]=0.22;
|
|
1067
|
+
for (int i=0;i<N;i++){
|
|
1068
|
+
vec2 d = normalize(dirs[i]);
|
|
1069
|
+
highp float k = 6.2831853 / wl[i]; // W7: highp wave number
|
|
1070
|
+
highp float phase = k*dot(d,p) + t*spd[i]*k*8.0; // W7: highp accumulated phase (unbounded t)
|
|
1071
|
+
// slope contribution: derivative of a sine height field -> cosine, weighted by
|
|
1072
|
+
// amplitude and choppiness. amp[i] tapers the higher-freq waves.
|
|
1073
|
+
float a = amp[i] * oceanAmp * (1.0 + oceanChoppy);
|
|
1074
|
+
slope += d * (cos(phase) * a * 0.06);
|
|
1075
|
+
}
|
|
1076
|
+
return slope;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Procedural height+slope material ramp (placeholder until the OrthoProducer lands):
|
|
1080
|
+
// water / shore / lowland / grass / rock / snow by signed elevation, with rock on steep
|
|
1081
|
+
// slopes. World-continuous (pure function of h + slope) so it has no per-tile seam.
|
|
1082
|
+
// LIVE-ADJUSTABLE biome ramp (full-adjustability via window.__gen.biome). Each color + band edge
|
|
1083
|
+
// is a uniform defaulting to its tuned literal when the JS global is unset (no behaviour change
|
|
1084
|
+
// until edited). bcXxx = band colors; bandEdgesLo=(shore->lowland end, lowland->grass end),
|
|
1085
|
+
// bandEdgesHi=(rock start, rock end), snowEdges=(snow start, snow end), seaDepthM, slopeRock=(lo,hi).
|
|
1086
|
+
uniform vec3 bcDeepSea, bcSea, bcShore, bcLowland, bcGrass, bcRock, bcSnow;
|
|
1087
|
+
uniform vec2 bandEdgesLo; // (lowland-blend end, grass-blend end)
|
|
1088
|
+
uniform vec2 bandEdgesHi; // (rock start, rock end)
|
|
1089
|
+
uniform vec2 snowEdges; // (snow start, snow end)
|
|
1090
|
+
uniform float seaDepthM; // depth (m) over which sea->deepSea
|
|
1091
|
+
uniform vec2 slopeRock; // (lo, hi) slope range that forces rock
|
|
1092
|
+
uniform float uAoAmt; // canyon/cliff ambient-occlusion strength (window.__aoAmt; 1.0 default)
|
|
1093
|
+
uniform float uBiomeBandBias;// elevation/latitude biome-band bias strength (window.__biomeBandBias; 1.0)
|
|
1094
|
+
// REAL-WORLD LOOK overhaul uniforms (2026-06-05 workflow wxb9n2907) -- all window.__gen-overridable.
|
|
1095
|
+
uniform vec3 uOceanDeep; // deep open-ocean color (near-black navy) [0.008,0.025,0.06]
|
|
1096
|
+
uniform vec3 uOceanShallow; // shallow-water turquoise (first metres) [0.07,0.22,0.26]
|
|
1097
|
+
uniform vec3 uOceanK; // per-channel Beer-Lambert extinction (kR>>kG>>kB) [0.030,0.012,0.0045]
|
|
1098
|
+
uniform float uBiomeSat; // biome-palette saturation pull toward luminance (1=full, <1 desaturate) 0.72
|
|
1099
|
+
uniform float uVariationAmt; // intra-biome value mottle amplitude (+/-) 0.08
|
|
1100
|
+
uniform float uHazeMul; // aerial-perspective strength multiplier (1 = full haze, 0 = none)
|
|
1101
|
+
// LIVE A/B ISOLATION TOGGLES (window.__rockBump / __chroma / __strata, default 1). Multiply each material
|
|
1102
|
+
// detail layer so the user can flip one to 0 and see which layer produces the close-up uv scramble.
|
|
1103
|
+
uniform float uFlatNormal; // 1=force the SMOOTH analytic normal (nBand), bypassing the raw cross(dFdx,dFdy) geometric normal that scrambles on steep deck faces (A/B isolation)
|
|
1104
|
+
uniform float uSkyFill; // sky-ambient fill weight (was implicit 1.0) 0.45
|
|
1105
|
+
uniform float uTerminatorGlow;// sunset reddening strength at the grazing terminator 0.5
|
|
1106
|
+
uniform float uNightLights; // night/shadow fill intensity (lift dark areas, not black) 1.0
|
|
1107
|
+
uniform float uNightFloor; // unlit-hemisphere brightness floor (dim, not black) 0.05
|
|
1108
|
+
uniform float uTermWidth; // terminator half-width (wider=softer twilight) 0.25
|
|
1109
|
+
uniform float uExposure; // pre-tonemap exposure (was 1.25) 1.0
|
|
1110
|
+
uniform float uLookSat; // post-ACES saturation Look (>1 = more saturated) 1.15
|
|
1111
|
+
uniform float uLookContrast; // post-ACES contrast Look (>1 = more contrast) 1.08
|
|
1112
|
+
// SURFACE PHOTO-TEXTURES (user 2026-06-10 'use the textures in textures/, calculate normals from
|
|
1113
|
+
// displacement'). Two 1024x1024x4 sampler2DArrays loaded at runtime (gl-render loadSurfaceTextures):
|
|
1114
|
+
// uSurfAlb = sRGB color (RGB) + displacement (A), uSurfNrm = tangent normal xy (RG, 0.5-biased) +
|
|
1115
|
+
// displacement (B). Layers: 0=grass 1=rock 2=sand 3=snow. The normals are Sobel-derived from the
|
|
1116
|
+
// displacement JPGs at load (wrap edges, JS). SUPERSEDES the 2026-06-01e 'no detail texturing'
|
|
1117
|
+
// directive by explicit user request. Triplanar-sampled in the FS, blended by the existing material
|
|
1118
|
+
// gates (slopeRock / snowEdges / climate), height(displacement)-sharpened, distance-faded by pxWorld.
|
|
1119
|
+
uniform sampler2DArray uSurfAlb;
|
|
1120
|
+
uniform sampler2DArray uSurfNrm;
|
|
1121
|
+
uniform float uHasSurfTex; // 1 once the arrays are uploaded (0 = procedural-only fallback)
|
|
1122
|
+
uniform float uTexTileM; // world metres per texture repeat (__texTile, default 2400 -- user: 24m read as noise/rock, '100x bigger')
|
|
1123
|
+
uniform float uTexNrmK; // texture detail-normal strength (__texNrmK, default 0.5; keep <=1: scramble lesson)
|
|
1124
|
+
uniform float uTexMix; // texture albedo blend amount (__texMix, default 0.85; 0 = off)
|
|
1125
|
+
uniform float uTexWarp; // anti-repetition domain-warp amplitude (__texWarp, default 1.0)
|
|
1126
|
+
uniform float uReliefShade; // gentle-slope relief-shading exaggeration (__reliefShade, default 1.7; 1 = off)
|
|
1127
|
+
uniform float uTexPhoto; // raw photo-color fraction (__texPhoto, default 0 = full macro tint; user: patch must match the shade it replaces)
|
|
1128
|
+
uniform float uTexPhotoNear; // near-field MATERIAL-IDENTITY fraction (__texPhotoNear, default 0.45): up close the
|
|
1129
|
+
// photo keeps its OWN hue at the macro's luminance, so ground reads clearly as
|
|
1130
|
+
// grass/rock/sand/snow (user 2026-06-12 'light brown patches that need to be either
|
|
1131
|
+
// grass or sand but is neither' -- the full macro tint painted biome browns/greys onto
|
|
1132
|
+
// every layer). Far field returns to the shade-matched macro (no distance pop).
|
|
1133
|
+
uniform vec4 uSurfMeanL; // per-layer mean linear luminance of the photo color (loader-computed; shade-match divisor)
|
|
1134
|
+
// MATERIAL-BOUNDARY DITHER REVERTED (2026-06-05): the threshold-perturbation approach (matEdgeNoise on
|
|
1135
|
+
// the smoothstep input) produced HARD-EDGED PATCHES + a UV-like grid on uniform grass/snow (user live
|
|
1136
|
+
// eye: 'hard uninteresting lines between rocky/grass', 'grass/snow UV problem') -- perturbing a near-
|
|
1137
|
+
// binary boundary snaps material across wide areas instead of softly interfingering, and the high-freq
|
|
1138
|
+
// octave aliased on bright materials. Reverted to the clean smoothstep boundaries; a non-aliasing
|
|
1139
|
+
// 'interesting boundary' technique (e.g. a wide soft transition material band) is a separate future task.
|
|
1140
|
+
vec3 terrainAlbedo(float h, float slope, float rockSlope, highp vec3 worldPos) { // highp: worldPos feeds normalize(worldPos)*freq noise UVs -- mediump would scramble the lattice at close range
|
|
1141
|
+
vec3 c;
|
|
1142
|
+
if (h < 0.0) {
|
|
1143
|
+
// SEABED CONTINUES AS LAND MATERIAL (user 2026-06-11 'instead of turning terrain into water,
|
|
1144
|
+
// it should continue under water as sand and rock'): below sea level the macro albedo is a
|
|
1145
|
+
// sandy bed on gentle ground and rock on steep faces (same slope gate as land). The water
|
|
1146
|
+
// look (per-channel absorption, fresnel, waves) is composited OVER this in the ocean branch,
|
|
1147
|
+
// so the terrain level information stays readable through shallow water.
|
|
1148
|
+
c = mix(bcShore, bcRock, smoothstep(slopeRock.x, slopeRock.y, rockSlope));
|
|
1149
|
+
} else {
|
|
1150
|
+
c = mix(bcShore, bcLowland, smoothstep(0.0, bandEdgesLo.x, h));
|
|
1151
|
+
c = mix(c, bcGrass, smoothstep(bandEdgesLo.x, bandEdgesLo.y, h));
|
|
1152
|
+
// ROCK IS VERTICALITY-DRIVEN, NOT HEIGHT-SPLATTED (user 2026-06-03: 'rock face based on
|
|
1153
|
+
// verticality not splatted directly on albedo'). The old height-band rock mix
|
|
1154
|
+
// (bcRock by bandEdgesHi) painted tan rock flat across every high plateau/summit regardless
|
|
1155
|
+
// of slope, so flat high ground read as rock. REMOVED. High flat ground now keeps its biome
|
|
1156
|
+
// (and snow by height below); ONLY the slope/verticality term and the steep cliff term turn
|
|
1157
|
+
// rock. Snow still bands by altitude (a flat snowfield is physically correct).
|
|
1158
|
+
c = mix(c, bcSnow, smoothstep(snowEdges.x, snowEdges.y, h));
|
|
1159
|
+
c = mix(c, bcRock, smoothstep(slopeRock.x, slopeRock.y, rockSlope) * step(0.0, h));
|
|
1160
|
+
// OLD PROCEDURAL GREY ROCKFACE DELETED (max-speed sweep 2026-06-10, user 'replace the original
|
|
1161
|
+
// rock completely'): the photo-rock splat owns steep faces; the 3-tap grey fBm fallback is gone.
|
|
1162
|
+
}
|
|
1163
|
+
return c;
|
|
1164
|
+
}
|
|
1165
|
+
// CLIMATE-BIASED albedo: the anchor field carries latitude-driven temp + humidity (now
|
|
1166
|
+
// SHIPPED). Drive Earth-like biome COLOR on land from climate so warm+wet reads lush green,
|
|
1167
|
+
// warm+dry reads arid tan, and cold reads pale/desaturated with an earlier snow line --
|
|
1168
|
+
// instead of a pure height ramp. Sea unchanged. temp,humid in [0,1].
|
|
1169
|
+
// BIOME PALETTE: each biome a DISTINCT recognizable color, blended by soft temp/humidity
|
|
1170
|
+
// thresholds (mirrors wasm/terrain-cli/biome-climate-tune.mjs which CLI-validated 7 distinct
|
|
1171
|
+
// classes, entropy 2.59). World-continuous (pure fn of climate -> seam-safe).
|
|
1172
|
+
vec3 biomeColor(float temp, float humid) {
|
|
1173
|
+
// PHYSICALLY-ANCHORED ALBEDOS (Real-World Look overhaul): real surface albedos are LOW and fairly
|
|
1174
|
+
// DESATURATED (conifer ~0.08, broadleaf ~0.15, grass ~0.22, dry sand ~0.35, snow ~0.85 the ONLY
|
|
1175
|
+
// bright class). The old palette was too bright + too saturated -> the sickly-yellow pastel
|
|
1176
|
+
// watercolour land (defect #1). Re-anchored to muted linear albedos; ICE stays bright.
|
|
1177
|
+
vec3 ICE = vec3(0.86, 0.90, 0.96); // snow/ice -- the only high-albedo class
|
|
1178
|
+
// TUNDRA WAS THE 'FLAT ROCK' (user 2026-06-11, witnessed by A/B: rock gate + texture OFF and the
|
|
1179
|
+
// grey patches remained = MACRO biome color): 0.34/0.36/0.31 is neutral grey, and the grass photo
|
|
1180
|
+
// tinted to it reads exactly as flat rock basins. Recolor to a muted olive-brown that is
|
|
1181
|
+
// unmistakably vegetation -- still duller/colder than MEADOW, no longer rock-grey.
|
|
1182
|
+
vec3 TUNDRA = vec3(0.27, 0.33, 0.18);
|
|
1183
|
+
// FOREST CLASSES LIFTED (user 2026-06-11 'large rocky patches... flat areas should be snow sand
|
|
1184
|
+
// grass' -- same disease as the TUNDRA grey: 0.03-0.13 luminance is DARKER than the rock photo,
|
|
1185
|
+
// and desaturation + grass-photo tinting turns the near-black greens into flat dark patches that
|
|
1186
|
+
// read as rock). Lifted ~2x with the green channel dominant so they are unmistakably vegetation;
|
|
1187
|
+
// still the darkest land classes, canopy still deepest.
|
|
1188
|
+
vec3 TAIGA = vec3(0.11, 0.19, 0.10); // conifer green
|
|
1189
|
+
vec3 FOREST = vec3(0.10, 0.22, 0.08); // broadleaf green
|
|
1190
|
+
vec3 DEEPFOR = vec3(0.07, 0.16, 0.07); // dense canopy (deepest green, no longer near-black)
|
|
1191
|
+
vec3 MEADOW = vec3(0.28, 0.34, 0.15); // muted olive grassland
|
|
1192
|
+
vec3 SAVANNA = vec3(0.46, 0.40, 0.22); // dry gold (desaturated)
|
|
1193
|
+
vec3 STEPPE = vec3(0.42, 0.39, 0.25); // pale dry grass (desaturated)
|
|
1194
|
+
vec3 DESERT = vec3(0.55, 0.45, 0.30); // sand ochre (desaturated, darker)
|
|
1195
|
+
float cold = 1.0 - smoothstep(0.16, 0.34, temp);
|
|
1196
|
+
float warm = smoothstep(0.42, 0.62, temp);
|
|
1197
|
+
float dry = 1.0 - smoothstep(0.34, 0.50, humid);
|
|
1198
|
+
// FOREST vs MEADOW split (user: they must look distinct, not one green). A CRISPER humidity
|
|
1199
|
+
// boundary (0.48->0.56, was 0.46->0.66) so meadow (drier-temperate) and forest (wetter) read
|
|
1200
|
+
// as DISTINCT adjacent regions instead of a long ambiguous blend; very-wet deepens to canopy.
|
|
1201
|
+
float wet = smoothstep(0.48, 0.56, humid);
|
|
1202
|
+
float veryWet = smoothstep(0.62, 0.80, humid);
|
|
1203
|
+
vec3 c = MEADOW; // temperate mid-humidity default
|
|
1204
|
+
c = mix(c, FOREST, wet); // wet -> forest (crisp boundary)
|
|
1205
|
+
c = mix(c, DEEPFOR, veryWet); // very wet -> dense dark canopy
|
|
1206
|
+
c = mix(c, SAVANNA, dry * warm); // dry + warm -> savanna
|
|
1207
|
+
c = mix(c, DESERT, smoothstep(0.60,0.85,1.0-humid) * warm); // very dry + warm -> desert
|
|
1208
|
+
c = mix(c, STEPPE, dry * (1.0-warm) * (1.0-cold)); // dry temperate -> steppe
|
|
1209
|
+
c = mix(c, TAIGA, wet * (1.0 - smoothstep(0.34,0.50,temp))); // cool + wet -> taiga
|
|
1210
|
+
c = mix(c, TUNDRA, cold); // cold -> tundra
|
|
1211
|
+
c = mix(c, ICE, 1.0 - smoothstep(0.10, 0.18, temp)); // very cold -> ice
|
|
1212
|
+
// GLOBAL SATURATION PULL toward luminance (Real-World Look): real terrain is less saturated than a
|
|
1213
|
+
// naive palette; pull ~28% toward grey so biomes read natural, not poster-paint. uBiomeSat<1.
|
|
1214
|
+
float bl = dot(c, vec3(0.2126, 0.7152, 0.0722));
|
|
1215
|
+
c = mix(vec3(bl), c, uBiomeSat);
|
|
1216
|
+
return c;
|
|
1217
|
+
}
|
|
1218
|
+
// DISCRETE biome class -> a flat distinct color (for the biome-MAP diagnostic displayMode 9 +
|
|
1219
|
+
// __biomeAt). Hard argmax of the same climate axes biomeColor blends, so the witness can COUNT
|
|
1220
|
+
// contiguous biome regions instead of reading a continuous gradient. Water/ice keyed on h/temp.
|
|
1221
|
+
// Returns a flat saturated key color per class (NOT the naturalistic biomeColor palette).
|
|
1222
|
+
#ifdef _DEBUGVIEW_
|
|
1223
|
+
vec3 biomeClassColor(float temp, float humid, float h) {
|
|
1224
|
+
if (h < 0.0) return vec3(0.10, 0.30, 0.75); // OCEAN (blue)
|
|
1225
|
+
if (temp < 0.14) return vec3(0.95, 0.97, 1.00); // ICE (white)
|
|
1226
|
+
if (temp < 0.30) return vec3(0.55, 0.60, 0.55); // TUNDRA (grey-green)
|
|
1227
|
+
// SWAMP: warm + very-wet + low ground (h<120m proxy) -> murky teal key color (distinct class).
|
|
1228
|
+
if (temp > 0.50 && humid > 0.66 && h >= 0.0 && h < 120.0) return vec3(0.20, 0.40, 0.30);
|
|
1229
|
+
bool warm = temp > 0.52;
|
|
1230
|
+
if (warm && humid < 0.22) return vec3(0.95, 0.80, 0.30); // DESERT (yellow)
|
|
1231
|
+
if (warm && humid < 0.42) return vec3(0.80, 0.70, 0.20); // SAVANNA (gold)
|
|
1232
|
+
if (humid > 0.66) return warm ? vec3(0.00,0.55,0.10) // RAINFOREST (bright green)
|
|
1233
|
+
: vec3(0.05,0.25,0.12); // TAIGA (dark green)
|
|
1234
|
+
if (humid > 0.45) return vec3(0.20, 0.65, 0.25); // FOREST (green)
|
|
1235
|
+
return vec3(0.55, 0.70, 0.30); // MEADOW/STEPPE (yellow-green)
|
|
1236
|
+
}
|
|
1237
|
+
// RIVER NETWORK: thin continuous water lines on land. A world-dir-continuous ridged-noise
|
|
1238
|
+
// network: riverField = 1 - |fBm(worldDir)| peaks (->1) along the zero-crossing ridge lines of
|
|
1239
|
+
// the fBm, giving a connected branching channel pattern. Thresholded thin near 1.0 -> the river
|
|
1240
|
+
// line. Pure fn of worldPos (vWorld) -> C0 seam-safe across tiles AND faces by construction (no
|
|
1241
|
+
// per-tile state). Gated to land below a snowline + present in wet/temperate regions, scarce in
|
|
1242
|
+
// desert (dry). `px` is a sub-pixel fade so the thin line does not alias/shimmer at altitude.
|
|
1243
|
+
// NOT an elevation incision (FS albedo only) -> zero CLOD/seam/LOD-uniformity risk.
|
|
1244
|
+
float riverMask(vec3 worldPos, float h, float temp, float humid, float px) {
|
|
1245
|
+
if (h <= 0.0) return 0.0;
|
|
1246
|
+
// ONE FRACTAL (user 2026-06-02, same fix as canyon): the FS used its OWN inline loop over
|
|
1247
|
+
// worldPos/terrainR (= dir*(1+h/R), elevation-shifted) -> a different field/position/resolution
|
|
1248
|
+
// than the VS riverCarveM. Call the IDENTICAL riverRidgeField the VS carve uses, at
|
|
1249
|
+
// normalize(worldPos) (== radial dir), so the FS river network coincides with the carved geometry.
|
|
1250
|
+
float ridge = riverRidgeField(normalize(worldPos)); // 0..1, ->1 on the channel network
|
|
1251
|
+
// thin the network to a line. MEASURED ridge distribution (diag-river.mjs nodejs-2037):
|
|
1252
|
+
// p90=0.858 p97=0.903 max=0.985; areaFrac>0.90 = 3.4%, >0.88 = 6%. Center the line band at
|
|
1253
|
+
// ~0.90 (lo 0.88 -> hi 0.935) for ~4-6% believable drainage density -- the old 0.92->0.985
|
|
1254
|
+
// band selected <2% and was invisible. Width grows slightly with px so the line stays >=1px.
|
|
1255
|
+
float wid = clamp(px * 0.0008, 0.0, 0.04);
|
|
1256
|
+
float line = smoothstep(0.875 - wid, 0.935, ridge);
|
|
1257
|
+
// climate gate: rivers in wet/temperate land, sparse in desert (very dry+warm) and on ice.
|
|
1258
|
+
float wetGate = smoothstep(0.30, 0.55, humid); // need some moisture
|
|
1259
|
+
float notFrozen = smoothstep(0.20, 0.34, temp); // not polar ice
|
|
1260
|
+
// NO altFade (user 2026-06-02: 'river field is fading in instead of integrated'). The line WIDTH
|
|
1261
|
+
// already grows with px (wid above) so the channel stays >=1px and AA-safe at every distance
|
|
1262
|
+
// WITHOUT vanishing -> the river network is a permanent part of the field, not a distance overlay.
|
|
1263
|
+
return line * wetGate * notFrozen;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// CANYONS (user-named): deep narrow incised gorges cutting ELEVATED ARID plateaus. Distinct from
|
|
1267
|
+
// rivers (canyons = dry+deep+arid biome; rivers = water+shallow+wet). World-dir-continuous ridged
|
|
1268
|
+
// network like riverMask but NARROWER + a HIGHER threshold (sparser, sharper) and a DIFFERENT
|
|
1269
|
+
// noise phase so canyons and rivers do not coincide. Gated to ELEVATED (h>250m) DRY+WARM regions.
|
|
1270
|
+
// FS-albedo only (no elevation incision) -> zero CLOD/seam risk, AA via pxWorld. Returns line + a
|
|
1271
|
+
// depth term for strata banding. out_depth = how deep into the gorge (0 rim -> 1 floor), for strata.
|
|
1272
|
+
float canyonMask(vec3 worldPos, float h, float temp, float humid, float px, out float depth) {
|
|
1273
|
+
depth = 0.0;
|
|
1274
|
+
// gentle elevation gate: canyons want SOME relief above the lowland, not only rare high
|
|
1275
|
+
// plateaus (h>250 made them NEVER appear -- witnessed canyonFieldFrac 0). Fade in over 60..200m.
|
|
1276
|
+
float elevGate = smoothstep(60.0, 200.0, h);
|
|
1277
|
+
if (elevGate <= 0.0) return 0.0;
|
|
1278
|
+
// ONE FRACTAL (user 2026-06-02: 'canyon field shows two different resolutions, expected the same
|
|
1279
|
+
// fractal'). The FS used its OWN inline loop over worldPos/terrainR (= dir*(1+h/R), elevation-
|
|
1280
|
+
// SHIFTED off the VS sample) -> a different field/position/resolution than the VS carve. Now call
|
|
1281
|
+
// the IDENTICAL canyonRidgeField the VS canyonCarveM uses, at normalize(worldPos) (== the radial
|
|
1282
|
+
// dir), so the FS network coincides EXACTLY with the carved geometry. Same field, no elevation shift.
|
|
1283
|
+
float ridge = canyonRidgeField(normalize(worldPos));
|
|
1284
|
+
float wid = clamp(px * 0.0006, 0.0, 0.03); // narrower than rivers
|
|
1285
|
+
float line = smoothstep(0.875 - wid, 0.94, ridge); // ~river-density threshold so they appear
|
|
1286
|
+
depth = smoothstep(0.875, 0.95, ridge); // deeper toward the channel centre
|
|
1287
|
+
float aridGate = (1.0 - smoothstep(0.40, 0.58, humid)); // DRY (deserts/steppe), opposite of rivers
|
|
1288
|
+
float warmGate = smoothstep(0.38, 0.56, temp); // warm arid plateaus
|
|
1289
|
+
// NO altFade (user: canyon field fading instead of integrated). wid grows with px -> >=1px AA at
|
|
1290
|
+
// every distance without vanishing; the canyon network is permanent, not a distance overlay.
|
|
1291
|
+
return line * aridGate * warmGate * elevGate;
|
|
1292
|
+
}
|
|
1293
|
+
#endif // biomeClassColor/riverMask/canyonMask: DEBUGVIEW-only (called solely from displayMode blocks) -- excluded from render FS cold-compile (FS-2, workflow w4y1bnrqc)
|
|
1294
|
+
|
|
1295
|
+
vec3 terrainAlbedoClimate(float h, float slope, float rockSlope, float temp, float humid, highp vec3 worldPos, float pxWorld) { // highp: worldPos feeds normalize(worldPos)*freq noise UVs (mottle/river/canyon ridge) -- mediump scrambles the lattice up close
|
|
1296
|
+
vec3 c = terrainAlbedo(h, slope, rockSlope, worldPos);
|
|
1297
|
+
if (h < 0.0) {
|
|
1298
|
+
// SEA ICE: near-polar ocean (very cold) freezes to white-blue pack ice. Pure fn of the
|
|
1299
|
+
// anchor temp -> seam-safe; the soft threshold gives an irregular (not hard-zonal) margin.
|
|
1300
|
+
float seaIce = 1.0 - smoothstep(0.12, 0.22, temp);
|
|
1301
|
+
return mix(c, vec3(0.82, 0.88, 0.94), seaIce * 0.9);
|
|
1302
|
+
}
|
|
1303
|
+
// biome weight: full on gentle lowland, fading where rock/snow/steep takes over.
|
|
1304
|
+
float veg = (1.0 - smoothstep(bandEdgesHi.x, bandEdgesHi.y, h)) * (1.0 - smoothstep(slopeRock.x, slopeRock.y, slope));
|
|
1305
|
+
// ELEVATION + LATITUDE BIOME BIAS (user 2026-06-02: 'the hypsometric ramp should influence biome
|
|
1306
|
+
// distribution, pulling biomes out of their anchor areas a bit for better distribution'). On top
|
|
1307
|
+
// of the anchor climate temp/humid, bias the EFFECTIVE temperature DOWN with elevation (lapse rate
|
|
1308
|
+
// ~6.5C/km -> normalize ~ h/4000 over the [0,1] temp scale) and a touch with latitude, so biomes
|
|
1309
|
+
// BAND by height+latitude: high ground -> alpine/tundra/ice, lowland keeps its anchor biome. This
|
|
1310
|
+
// spreads biomes into their physically-expected bands so grass/forest isnt one anchor blob. humid
|
|
1311
|
+
// also rises slightly on windward high ground (orographic) for variety. uBiomeBandBias scales it.
|
|
1312
|
+
float lat = asin(clamp(worldPos.y / max(length(worldPos), 1.0), -1.0, 1.0));
|
|
1313
|
+
float latCool = 0.18 * (abs(lat) / 1.5708); // poles cooler
|
|
1314
|
+
float elevCool = clamp(h / 4500.0, 0.0, 0.55) * uBiomeBandBias; // lapse rate -> alpine bands
|
|
1315
|
+
float tempEff = clamp(temp - elevCool - latCool * uBiomeBandBias, 0.0, 1.0);
|
|
1316
|
+
float humidEff = clamp(humid + clamp(h / 9000.0, 0.0, 0.12) * uBiomeBandBias, 0.0, 1.0);
|
|
1317
|
+
vec3 biome = biomeColor(tempEff, humidEff);
|
|
1318
|
+
// STRONG blend (0.82) so the biome DOMINATES the lowland color -> regions read distinct,
|
|
1319
|
+
// not the faint 0.55 tint that made the whole continent look the same.
|
|
1320
|
+
c = mix(c, biome, veg * 0.82);
|
|
1321
|
+
// INTRA-BIOME VALUE MOTTLE (Real-World Look): real terrain is never one flat color per biome --
|
|
1322
|
+
// soil/moisture/vegetation patchiness mottles albedo. Reuse the snoise3 already evaluated for
|
|
1323
|
+
// cliffRock (same normalize(worldPos)*~1k pattern, no new octave) as a VALUE-only multiplier (NO
|
|
1324
|
+
// hue jitter, low 1200 freq -> does NOT reintroduce the per-pixel swamp moire the pipeline removed).
|
|
1325
|
+
float mot = snoise3(normalize(worldPos) * 120.0); // detail-tex-rockface-canyon-10x: *1200->*120 (~10x larger mottle features, user 2026-06-06)
|
|
1326
|
+
c *= (1.0 + uVariationAmt * mot);
|
|
1327
|
+
// earlier snow/ice on high ground in cold regions.
|
|
1328
|
+
float coldSnow = (1.0 - smoothstep(0.30, 0.75, temp)) * smoothstep(snowEdges.x * 0.5, snowEdges.x, h);
|
|
1329
|
+
c = mix(c, bcSnow, coldSnow * 0.7);
|
|
1330
|
+
// BEACH BAND (user 2026-06-11 'all land at the level of the ocean should be beach -- the grass
|
|
1331
|
+
// must stop and become sand, which continues under the water'): below uBeachTopM the biome/
|
|
1332
|
+
// grass/snow color yields to shore sand (the same bcShore the underwater bed starts from, so the
|
|
1333
|
+
// material is continuous through the waterline). Steep coastal cliffs keep their rock.
|
|
1334
|
+
float beachM = (1.0 - smoothstep(uBeachTopM * 0.6, uBeachTopM, h))
|
|
1335
|
+
* (1.0 - smoothstep(slopeRock.x, slopeRock.y, rockSlope));
|
|
1336
|
+
c = mix(c, bcShore, beachM);
|
|
1337
|
+
// INLAND WATER -- COLOUR GATED BY THE CARVE FIELD (alignment + no-fade fix, browser-2705/2699).
|
|
1338
|
+
// The water colour now keys off the SAME world-dir carve fields that cut the geometry
|
|
1339
|
+
// (lakeCarveM/riverCarveM at this fragment's world dir), NOT an independent h-smoothstep. So the
|
|
1340
|
+
// blue sits EXACTLY in the carved bowl/channel at every LOD (color == depression by construction)
|
|
1341
|
+
// -- this kills both the 'lakes fade on approach' (was gated by h<70 which shifts with relief)
|
|
1342
|
+
// and the 'rivers/lakes out of alignment with their elevation' defects. Climate still gates WHICH
|
|
1343
|
+
// regions get water (wet for lakes/rivers) but no longer the SHAPE (the carve owns the shape).
|
|
1344
|
+
// INLAND WATER + INCISION COLOUR -- UNIFIED: keyed off the carve masks computed ONCE in the VS
|
|
1345
|
+
// and INTERPOLATED here (vLakeWet/vRiverWet/vCanyonDep), NOT re-evaluated per-pixel. This removes
|
|
1346
|
+
// the separate per-pixel ridged-field evaluation that aliased into biome-localized moire (user
|
|
1347
|
+
// 2026-06-01h: 'UV issue only in patches where the biome is like that'). The VS already folded
|
|
1348
|
+
// the climate gates into the masks, so the FS just applies colour.
|
|
1349
|
+
float warmth = smoothstep(0.48, 0.62, temp);
|
|
1350
|
+
// SWAMP: only the WARM + VERY-WET end of a lake basin reads murky olive-teal. The humid gate
|
|
1351
|
+
// (0.66-0.86) was lost in the unify rewrite -> the teal leaked into merely-warm wet land (user:
|
|
1352
|
+
// 'faint teal area, moire'); restored here. The per-pixel mottle snoise3 (freq 900) was the last
|
|
1353
|
+
// per-pixel noise field -> it gave the teal its grain; REMOVED (flat swamp colour, no aliasing).
|
|
1354
|
+
// INLAND WATER ALBEDO IS NOT SET HERE ANYMORE. Lakes and rivers are WATER (user 2026-06-01i:
|
|
1355
|
+
// 'use the same content as the sealevel to draw because its also water'), so they are shaded by
|
|
1356
|
+
// the SAME ocean water branch (fresnel + sun glint + depth tint) post-lighting, gated on the
|
|
1357
|
+
// submerged-depth varying vWaterDepth so the color lands EXACTLY on the flat water plane (lines
|
|
1358
|
+
// up with the carve) instead of being painted over the whole basin including the dry shoulder.
|
|
1359
|
+
// The terrain albedo (rock/biome) stays under the water and shows through where shallow.
|
|
1360
|
+
// CLIFF STRATA BLOCK DELETED (max-speed sweep 2026-06-10): the photo-rock splat owns cliff
|
|
1361
|
+
// appearance; the 2-tap bedding-warp strata fallback is gone (-2 snoise3, ~35 LOC).
|
|
1362
|
+
// DUNES: warm sand on the desert floor, keyed off the interpolated dune-crest mask (vDuneCrest =
|
|
1363
|
+
// crest * duneSand gate from the VS). Crest sun-bleached lighter, trough ochre.
|
|
1364
|
+
vec3 sandLo = vec3(0.78, 0.64, 0.40); // trough ochre sand
|
|
1365
|
+
vec3 sandHi = vec3(0.90, 0.82, 0.62); // sun-bleached crest sand
|
|
1366
|
+
vec3 sandCol = mix(sandLo, sandHi, smoothstep(0.0, 0.6, vDuneCrest));
|
|
1367
|
+
c = mix(c, sandCol, smoothstep(0.0, 0.25, vDuneCrest) * (1.0 - smoothstep(0.30, 0.50, slope)) * 0.9);
|
|
1368
|
+
// PERLIN-EVERYWHERE OVERLAY (user 2026-06-10 'pale + featureless in some areas -- add perlin
|
|
1369
|
+
// everywhere and overlay the other noises'): 3-octave value fbm over ALL materials (biome, rock,
|
|
1370
|
+
// snow, sand) so no area is ever a flat color. World-dir keyed (no camera scroll -- the lesson of
|
|
1371
|
+
// the deleted 2026-06-01 detail texturing), value-only (no hue shift), and each octave fades out
|
|
1372
|
+
// via a pxWorld Nyquist gate before its features go sub-pixel (no orbit speckle, no leopard band).
|
|
1373
|
+
{
|
|
1374
|
+
highp vec3 od = normalize(worldPos);
|
|
1375
|
+
float ov = 0.0, oa = 0.0;
|
|
1376
|
+
float fq = 150.0, am = 1.0; // octaves: ~42km / 8.5km / 1.7km features
|
|
1377
|
+
for (int o = 0; o < 3; o++) {
|
|
1378
|
+
float wl = 40000000.0 / fq; // feature wavelength (m) ~ 2*pi*R / fq
|
|
1379
|
+
float nyq = 1.0 - smoothstep(wl * 0.03, wl * 0.12, pxWorld); // fade before sub-pixel
|
|
1380
|
+
ov += am * nyq * snoise3(od * fq + vec3(float(o) * 7.3));
|
|
1381
|
+
oa += am;
|
|
1382
|
+
fq *= 5.0; am *= 0.6;
|
|
1383
|
+
}
|
|
1384
|
+
// ALBEDO amplitude decoupled from the elevation lever (user 2026-06-11 'normals seem wrongly
|
|
1385
|
+
// calculated, not on the sides of slopes' -- witnessed: at the user-tuned uDetailOverlay 6 the
|
|
1386
|
+
// albedo term was *= 1 +/- 0.54, painting slope-INDEPENDENT dark braids over every material
|
|
1387
|
+
// that read as misplaced shading / rock bands). 0.09 -> 0.02: lever 6 now gives +/-12% value
|
|
1388
|
+
// variation (anti-featureless, as asked) while the +/-180m ELEVATION term keeps the full lever.
|
|
1389
|
+
c *= 1.0 + uDetailOverlay * 0.02 * (ov / max(oa, 1e-3));
|
|
1390
|
+
}
|
|
1391
|
+
return c;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// ---- DETAIL MATERIAL (1-2 texel/pixel from anchor biome weights). The coarse biome+ortho
|
|
1395
|
+
// PER-PIXEL DETAIL TEXTURING REMOVED (user 2026-06-01e: "get rid of any detail texturing, we only
|
|
1396
|
+
// want one fractal"). The old FS grain (detailMaterial/detailNormal/grainH + the dnoise lattice)
|
|
1397
|
+
// was a SECOND high-frequency source on top of the VS height fractal -- it caused the moire and the
|
|
1398
|
+
// scroll/jump (its lattice was keyed on the moving camera). Deleted entirely. The single terrain
|
|
1399
|
+
// fractal is the VS world-dir height field; closeup detail comes from the mesh subdividing into a
|
|
1400
|
+
// denser sample of THAT one field, never a per-pixel fake-relief texture.
|
|
1401
|
+
|
|
1402
|
+
// SURFACE PHOTO-TEXTURE triplanar tap: 3 axis-projected samples of one array layer, blended by
|
|
1403
|
+
// the pow-softened |n| weights (same continuous-projection rationale as the rock bump biplanar --
|
|
1404
|
+
// no hard dominant-axis flip). wt = world pos in tile units (fragment-anchored, fp32-precise).
|
|
1405
|
+
vec4 surfTriTap(sampler2DArray sm, highp vec3 wt, vec3 bw, float layer) {
|
|
1406
|
+
return texture(sm, vec3(wt.y, wt.z, layer)) * bw.x
|
|
1407
|
+
+ texture(sm, vec3(wt.x, wt.z, layer)) * bw.y
|
|
1408
|
+
+ texture(sm, vec3(wt.x, wt.y, layer)) * bw.z;
|
|
1409
|
+
}
|
|
1410
|
+
// WORLD-SPACE triplanar normal perturbation (UDN) -- THE 'math issue causing both' (user 2026-06-11):
|
|
1411
|
+
// the old path triplanar-BLENDED the per-plane RG tangent normals, then applied the blend in the
|
|
1412
|
+
// single radial (ux,uy) frame -- but each projection plane has its own tangent axes, so the blended
|
|
1413
|
+
// RG was rotated arbitrarily vs the frame it was applied in (bumps lit from wrong directions =
|
|
1414
|
+
// statistical darkening + shading off the slope sides), and the apply site SUBTRACTED an
|
|
1415
|
+
// already-negated Sobel normal (double negation = lit from the wrong side outright). Here each
|
|
1416
|
+
// plane's RG perturbs along that plane's own world axes, sign-flipped to push outward, blended by
|
|
1417
|
+
// the same weights -- a world-space delta added to the lit normal. Same 3 taps.
|
|
1418
|
+
vec3 surfTriNrm(sampler2DArray sm, highp vec3 wt, vec3 bw, float layer, vec3 sn) {
|
|
1419
|
+
vec2 px = texture(sm, vec3(wt.y, wt.z, layer)).rg * 2.0 - 1.0; // X plane: in-plane axes (Y,Z)
|
|
1420
|
+
vec2 py = texture(sm, vec3(wt.x, wt.z, layer)).rg * 2.0 - 1.0; // Y plane: in-plane axes (X,Z)
|
|
1421
|
+
vec2 pz = texture(sm, vec3(wt.x, wt.y, layer)).rg * 2.0 - 1.0; // Z plane: in-plane axes (X,Y)
|
|
1422
|
+
return vec3(0.0, px.x, px.y) * (bw.x * sign(sn.x))
|
|
1423
|
+
+ vec3(py.x, 0.0, py.y) * (bw.y * sign(sn.y))
|
|
1424
|
+
+ vec3(pz.x, pz.y, 0.0) * (bw.z * sign(sn.z));
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
void main() {
|
|
1428
|
+
// PER-FRAGMENT tangent frame from vWorld (the sphere position, C0 across quad edges):
|
|
1429
|
+
// uz = up (radial), ux = normalize(Y x uz), uy = uz x ux. Continuous across adjacent quads
|
|
1430
|
+
// -> no per-quad shading-baseline seam.
|
|
1431
|
+
vec3 uz = normalize(vWorld);
|
|
1432
|
+
// AUDIT FIX 2026-06-06 (audit-tangent-frame-degeneracy): the reference axis was a FIXED vec3(0,1,0),
|
|
1433
|
+
// so cross(Y, uz) -> zero-length (NaN normal) wherever uz is parallel to Y -- i.e. the +Y/-Y cube
|
|
1434
|
+
// faces and the poles, exactly 'wrong normals in the wrong places'. Pick the world axis LEAST
|
|
1435
|
+
// parallel to uz so the cross product is always well-conditioned (|ux| ~ 1) everywhere on the sphere.
|
|
1436
|
+
vec3 refAxis = (abs(uz.y) < 0.99) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
|
1437
|
+
vec3 ux = normalize(cross(refAxis, uz));
|
|
1438
|
+
vec3 uy = cross(uz, ux);
|
|
1439
|
+
// ---- WATER-SURFACE PASS (uIsWater=1, the separate ocean surface at sea level). vH carries the
|
|
1440
|
+
// TRUE seabed elevation under this fragment -> depthM = the water column. Shade as animated
|
|
1441
|
+
// water (Gerstner normal + fresnel sky + sun glint + foam) and ALPHA-blend over the already-
|
|
1442
|
+
// rendered seabed: alpha rises with optical depth (Beer-Lambert) + fresnel/foam, so shallows
|
|
1443
|
+
// show the sand through real transparency and deep basins read opaque navy. Early return --
|
|
1444
|
+
// none of the terrain material/atmosphere work below runs for water fragments.
|
|
1445
|
+
if (uIsWater > 0.5) {
|
|
1446
|
+
if (vH > 1.0) discard; // surface under land: depth test culls it anyway; discard kills shoreline shimmer
|
|
1447
|
+
highp float depthM = max(-vH, 0.0);
|
|
1448
|
+
highp vec3 wOriginW = floor(camWorld / 1024.0) * 1024.0; // snapped anchor (fp32 wave-phase fix, same as the old branch)
|
|
1449
|
+
highp vec2 wpW = vec2(dot(vWorld - wOriginW, ux), dot(vWorld - wOriginW, uy));
|
|
1450
|
+
vec2 slopeW = oceanWaveSlope(wpW, oceanTime);
|
|
1451
|
+
highp float wDistW = length(camWorld - vWorld);
|
|
1452
|
+
slopeW *= clamp(1.0 - wDistW / 4000.0, 0.0, 1.0); // sub-pixel wave fade (anti-alias)
|
|
1453
|
+
vec3 wn = normalize(uz - ux * slopeW.x - uy * slopeW.y);
|
|
1454
|
+
vec3 viewW = normalize(camWorld - vWorld);
|
|
1455
|
+
float fres = 0.02 + 0.98 * pow(clamp(1.0 - max(dot(wn, viewW), 0.0), 0.0, 1.0), 5.0);
|
|
1456
|
+
highp float camAltW = length(camWorld) - terrainR;
|
|
1457
|
+
float specFade = clamp(1.0 - camAltW / 150000.0, 0.0, 1.0); // orbit glint fade (kept)
|
|
1458
|
+
vec3 hlW = normalize(sunDir + viewW);
|
|
1459
|
+
float spec = pow(max(dot(wn, hlW), 0.0), 220.0) * specFade;
|
|
1460
|
+
float ndl = max(dot(wn, sunDir), 0.0);
|
|
1461
|
+
vec3 T = exp(-uOceanK * depthM); // per-channel transmittance
|
|
1462
|
+
float Tavg = (T.r + T.g + T.b) * (1.0 / 3.0);
|
|
1463
|
+
vec3 waterBase = mix(uOceanDeep, uOceanShallow, Tavg);
|
|
1464
|
+
vec3 skyColW = vec3(0.30, 0.42, 0.55);
|
|
1465
|
+
vec3 wcol = mix(waterBase * (0.25 + 0.75 * ndl), skyColW, fres * 0.85)
|
|
1466
|
+
+ vec3(1.0, 0.95, 0.85) * spec * ndl;
|
|
1467
|
+
float foamAmt = clamp((length(slopeW) - 0.6) * 1.5, 0.0, 1.0) * oceanFoam;
|
|
1468
|
+
wcol = mix(wcol, vec3(0.9, 0.95, 1.0), foamAmt * (0.3 + 0.7 * ndl));
|
|
1469
|
+
wcol += waterBase * 0.05; // ambient floor (never black)
|
|
1470
|
+
// distance haze: the terrain pass gets the full aerial-perspective march; give the water a
|
|
1471
|
+
// cheap matched fade toward the same sky-haze color so far ocean recedes like far land
|
|
1472
|
+
// (no 8-step march on the largest-area surface -- the perf theme of this pass).
|
|
1473
|
+
float apGW = smoothstep(3000.0, 120000.0, wDistW) * uHazeMul;
|
|
1474
|
+
wcol = mix(wcol, uSkyFill * vec3(0.40, 0.55, 0.78) * 1.6, apGW * 0.7);
|
|
1475
|
+
// day/night + tonemap chain kept consistent with the terrain pass so the two surfaces match.
|
|
1476
|
+
float macroMuW = dot(uz, sunDir);
|
|
1477
|
+
float dayShadeW = mix(uNightFloor, 1.0, smoothstep(-uTermWidth, uTermWidth, macroMuW));
|
|
1478
|
+
vec3 cW = wcol * dayShadeW * uExposure;
|
|
1479
|
+
vec3 mappedW = clamp((cW * (2.51 * cW + 0.03)) / (cW * (2.43 * cW + 0.59) + 0.14), 0.0, 1.0);
|
|
1480
|
+
float lumW = dot(mappedW, vec3(0.2126, 0.7152, 0.0722));
|
|
1481
|
+
mappedW = mix(vec3(lumW), mappedW, uLookSat);
|
|
1482
|
+
mappedW = clamp((mappedW - 0.5) * uLookContrast + 0.5, 0.0, 1.0);
|
|
1483
|
+
// coverage: optically thick water -> opaque; first metres see-through; grazing fresnel and
|
|
1484
|
+
// foam are opaque regardless of depth; feather the exact waterline contact.
|
|
1485
|
+
float alphaW = clamp(1.0 - Tavg, 0.0, 1.0);
|
|
1486
|
+
alphaW = max(alphaW, fres * 0.9);
|
|
1487
|
+
alphaW = max(alphaW, foamAmt * 0.8);
|
|
1488
|
+
// SHALLOW-SURFACE FLOOR (2026-06-11, found by the coast witness after the shelf landed):
|
|
1489
|
+
// the continental shelf keeps water metres-deep for kilometres, where Beer-Lambert alpha
|
|
1490
|
+
// ~0 and nadir fresnel ~0 made the whole shoreline INVISIBLE (coverage 0 at a bisected
|
|
1491
|
+
// waterline pose). Real shallow water still shows its surface (sky reflection + ripple):
|
|
1492
|
+
// floor the opacity once genuinely submerged so shorelines read as water.
|
|
1493
|
+
alphaW = max(alphaW, 0.30 * smoothstep(0.3, 4.0, depthM));
|
|
1494
|
+
alphaW *= smoothstep(0.0, 1.5, depthM);
|
|
1495
|
+
fragColor = vec4(pow(mappedW, vec3(1.0 / 2.2)), alphaW);
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
// W8 SOLE FS LIT NORMAL (P1 data-first, P9 all-distances): vNrm is the per-vertex analytic central-
|
|
1499
|
+
// difference normal of the FULL composeHeight field, computed in the VS at a FIXED STABLE world step.
|
|
1500
|
+
// Full landform relief (broadShapeM fine octaves + carves) at ALL distances, NO per-pixel jitter, NO
|
|
1501
|
+
// fade, NO band-limit. Replaces the cross(dFdx,dFdy) geometric normal (it jumped per-pixel across the
|
|
1502
|
+
// bumpy displaced vWorld = the 'completely messed up' scramble) and the bandFadeN macro-only fade
|
|
1503
|
+
// (which made fine relief go missing). It is a continuous function of dir0 only -- adjacent fragments
|
|
1504
|
+
// get the linearly-interpolated VS value, so it is smooth by construction (no dFdx/dFdy, no fwidth).
|
|
1505
|
+
// uFlatNormal kept as a cheap escape hatch: pure sphere normal (uz) for A/B isolation.
|
|
1506
|
+
vec3 n = (uFlatNormal > 0.5) ? uz : normalize(vNrm);
|
|
1507
|
+
|
|
1508
|
+
// GPU-TIMER VS-ISOLATION: a cheap measurement frame short-circuits the whole per-pixel shade
|
|
1509
|
+
// here (uses vNrmPV + vWorld so the VS outputs are not dead-code-eliminated) -> the timed cheap
|
|
1510
|
+
// frame is VS+raster only; (full - cheap) attributes the per-pixel FS cost. No effect when 0.
|
|
1511
|
+
if (uFsCheap > 0.5) { fragColor = vec4(n * 0.5 + 0.5, 1.0); return; }
|
|
1512
|
+
|
|
1513
|
+
|
|
1514
|
+
// DIAGNOSTIC displayModes (1,5,6,7,8,9,10,11,12) are gated behind _DEBUGVIEW_ so they are
|
|
1515
|
+
// ONLY compiled into the lazily-built DEBUG program (gl-render.js), NOT the hot render program.
|
|
1516
|
+
// They added 7132 chars (25%) of translated HLSL to the render FS (browser-1590) -- pure dead
|
|
1517
|
+
// weight on the lit path, and the heavy ones pull in riverMask/canyonMask/biomeClassColor which
|
|
1518
|
+
// ANGLE then drops from the render program once unreferenced. Cheap albedo modes 2/4 stay live
|
|
1519
|
+
// below (one fragColor=albedo each). The render program forces displayMode 0 (lit) effectively.
|
|
1520
|
+
#ifdef _DEBUGVIEW_
|
|
1521
|
+
// displayMode 1 (Normals) MOVED below the splat (user 2026-06-11 'in the normals view we dont
|
|
1522
|
+
// see the texture normals'): this early return showed the pre-splat geometric normal, so the
|
|
1523
|
+
// displacement-derived texture normals (texDn) were invisible in the debug view. The handler
|
|
1524
|
+
// now lives after nLit/texDn assembly and shows the FINAL lit normal.
|
|
1525
|
+
// DIAG displayMode 5: land/sea map -- RED where vH>0 (land), BLUE where vH<0 (ocean).
|
|
1526
|
+
// Unambiguous test of the elevation sign distribution the render actually samples.
|
|
1527
|
+
if (displayMode == 5) { fragColor = (vH < 0.0) ? vec4(0.1,0.2,0.9,1.0) : vec4(0.9,0.3,0.1,1.0); return; }
|
|
1528
|
+
// DIAG displayMode 6: encode signed vH into RGB. R = vH/8000 (positive), B =
|
|
1529
|
+
// -vH/8000 (negative), G = |vH|/8000. Lets a readback recover the actual elevation
|
|
1530
|
+
// magnitude the render samples (independent of the biome ramp / readTile artifact).
|
|
1531
|
+
// ELEVATION displayMode 6: HYPSOMETRIC ramp (no hard clip / washed-out tops, user 2026-06-02).
|
|
1532
|
+
// Sea = blue (deeper -> darker); land ramps green->tan->brown->grey->white by height over a 0..9km
|
|
1533
|
+
// scale, each band a smoothstep so peaks read as distinct grey/white rock, NOT a saturated white
|
|
1534
|
+
// blob. >9km (shouldn't occur after the massif tame) just stays at the snow-rock top, no blow-out.
|
|
1535
|
+
// ELEVATION displayMode 6: a SCIENTIFIC (non-albedo) data ramp so it reads as an elevation MAP,
|
|
1536
|
+
// not naturalistic terrain colour (user 2026-06-02: 'elevation view looks weird like albedo').
|
|
1537
|
+
// Sea = dark->mid blue by depth; land = turbo-style cyan->green->yellow->orange->red->white by
|
|
1538
|
+
// height (8.5km full scale). Clearly a heatmap, no hard clip.
|
|
1539
|
+
if (displayMode == 6) {
|
|
1540
|
+
if (vH < 0.0) {
|
|
1541
|
+
float dep = clamp(-vH / 6000.0, 0.0, 1.0);
|
|
1542
|
+
fragColor = vec4(mix(vec3(0.10,0.55,0.85), vec3(0.0,0.05,0.30), dep), 1.0); return;
|
|
1543
|
+
}
|
|
1544
|
+
// full scale 11km so even an 8km peak reads RED/MAGENTA, not saturated white; white reserved
|
|
1545
|
+
// for the very rare >10.5km tip -> NO broad white-clipped areas (user 2026-06-02: 'fully white').
|
|
1546
|
+
float t = clamp(vH / 11000.0, 0.0, 1.0);
|
|
1547
|
+
vec3 ec = mix(vec3(0.0,0.80,0.85), vec3(0.10,0.85,0.20), smoothstep(0.0, 0.18, t)); // cyan->green
|
|
1548
|
+
ec = mix(ec, vec3(0.95,0.95,0.10), smoothstep(0.18, 0.38, t)); // -> yellow
|
|
1549
|
+
ec = mix(ec, vec3(0.95,0.50,0.05), smoothstep(0.38, 0.58, t)); // -> orange
|
|
1550
|
+
ec = mix(ec, vec3(0.80,0.10,0.10), smoothstep(0.58, 0.78, t)); // -> red
|
|
1551
|
+
ec = mix(ec, vec3(0.55,0.10,0.40), smoothstep(0.78, 0.93, t)); // -> magenta (very high)
|
|
1552
|
+
ec = mix(ec, vec3(1.0,1.0,1.0), smoothstep(0.95, 1.0, t)); // -> white only at the tip
|
|
1553
|
+
fragColor = vec4(ec, 1.0); return;
|
|
1554
|
+
}
|
|
1555
|
+
#endif // _DEBUGVIEW_ (modes 1,5,6)
|
|
1556
|
+
|
|
1557
|
+
float slope = 1.0 - max(0.0, dot(n, uz)); // 0 = flat, ->1 = steep
|
|
1558
|
+
// Rock classification keys on TRUE slope = 1-dot(n,uz) from the sole band-limited geometric normal.
|
|
1559
|
+
// gslope is the same quantity (kept as a name for the AO/detail-normal terms below).
|
|
1560
|
+
float gslope = slope;
|
|
1561
|
+
// ROCK-FACE BREAKUP NOISE DELETED (user 2026-06-11, 3-way isolation witness: the braided
|
|
1562
|
+
// km-scale rock/grass interfingering at this noise's exact 2.5km wavelength WAS the 'rocky
|
|
1563
|
+
// patches' -- fake texture ridges the real terrain (normals-view smooth) does not have, hence
|
|
1564
|
+
// 'normals dont match elevation'. The breakup compensated for the over-steep pre-14ad9b8 peak
|
|
1565
|
+
// stack; with sane slopes the plain gate is correct.)
|
|
1566
|
+
float rockSlope = clamp(slope, 0.0, 1.0);
|
|
1567
|
+
// world metres spanned by one screen pixel (MOVED up from ~line 1321 so the close-up signal fade is
|
|
1568
|
+
// available to the rock-detail block below). nearFade=1 at the deck (a crag is many px) -> 0 at
|
|
1569
|
+
// altitude (the crag goes sub-pixel) so the entire close-up rock signal fades to zero from orbit and
|
|
1570
|
+
// the macro path is unchanged there (contrast-on-approach-safe, no orbit shimmer).
|
|
1571
|
+
float pxWorld = max(length(fwidth(vWorld)), 0.001);
|
|
1572
|
+
// FADE CEILING 40->180m (live-deck witness 2026-06-05, browser-31: pxWorld is ~5m DIRECTLY below the
|
|
1573
|
+
// eye but climbs PAST 40m across the rest of a wide-FOV near-ground frame, so at 40m the close-up rock
|
|
1574
|
+
// detail + microAO + chromatic colorVar switched OFF across most of the visible deck -- the realism
|
|
1575
|
+
// never reached the FPS range the user actually flies (tens-to-hundreds of m/px). 180m keeps the whole
|
|
1576
|
+
// near-ground swath engaged while still fully fading by orbit (8m floor unchanged -> no orbit shimmer).
|
|
1577
|
+
float nearFade = 1.0 - smoothstep(8.0, 180.0, pxWorld); // macro crease-AO distance fade (vAO only)
|
|
1578
|
+
// PROCEDURAL ROCK DETAIL-NORMAL DELETED (max-speed sweep 2026-06-10): the photo-rock
|
|
1579
|
+
// displacement normal owns rock micro-relief; the 3-tap snoise3D biplanar bump is gone.
|
|
1580
|
+
// microSlope/microCurv keep their macro defaults for the material/AO consumers below.
|
|
1581
|
+
vec3 nLit = n;
|
|
1582
|
+
float microSlope = rockSlope;
|
|
1583
|
+
float microCurv = 0.0;
|
|
1584
|
+
// base biome albedo from the coherent height/slope ramp, biased by anchor CLIMATE
|
|
1585
|
+
// (latitude-driven temp + humidity) so lowland color sorts by biome (lush/arid/cold).
|
|
1586
|
+
vec4 climate = vClimate; // (seaBias, elevAmp, temp=.z, humid=.w) -- INTERPOLATED from the VS,
|
|
1587
|
+
// NOT a per-pixel HPF texture read (that read showed the HPF texel
|
|
1588
|
+
// grid as UV lines/moire up close). Smooth biome transitions now.
|
|
1589
|
+
// (pxWorld is computed up near the slope block now -- moved so nearFade can fade the rock detail.)
|
|
1590
|
+
vec3 albedo = terrainAlbedoClimate(vH, slope, microSlope, climate.z, climate.w, vWorld, pxWorld);
|
|
1591
|
+
// CURVATURE MICRO-AO (close-up realism, workflow w5gywvug1): the concave creases between rock crags
|
|
1592
|
+
// (microCurv < 0) darken -- the contrast that reads as 3D rugged relief instead of flat lit clay.
|
|
1593
|
+
// Gated by microSlope (flat ground untouched); band-limited by the crag bump it derives from; scaled by the
|
|
1594
|
+
// live uAoAmt lever so it stays terraform-tunable. negCurv = how concave the crease is (0 on convex).
|
|
1595
|
+
float negCurv = max(0.0, -microCurv);
|
|
1596
|
+
float aoLever = (uAoAmt > 0.0 ? uAoAmt : 1.0);
|
|
1597
|
+
// LOW-CONTRAST crease AO (user 2026-06-06 flecked-rock fix): the crease darkening was up to 60% (the
|
|
1598
|
+
// dark half of the fleck speckle). Soften to <=30% so creases read as gentle shading, not hard specks.
|
|
1599
|
+
// microCurv already comes from the band-limited (rockBumpGate) crag bump, so this is present at all
|
|
1600
|
+
// distances and fades out naturally as the bump does -- no separate nearFade gate.
|
|
1601
|
+
float microAO = 1.0 - clamp(negCurv * 3.0, 0.0, 0.30) * smoothstep(0.04, 0.2, microSlope) * aoLever;
|
|
1602
|
+
albedo *= microAO;
|
|
1603
|
+
// CLOSE-UP CHROMA VARIATION DELETED (max-speed sweep 2026-06-10): the photo textures carry
|
|
1604
|
+
// near-field color variation now (-2 snoise3/pixel).
|
|
1605
|
+
// ---- SURFACE PHOTO-TEXTURE SPLAT (user 2026-06-10): triplanar grass/rock/sand/snow color +
|
|
1606
|
+
// displacement-derived normal, blended by the SAME gates the procedural materials use (slope ->
|
|
1607
|
+
// rock, snowline+cold -> snow, hot-dry climate / dunes -> sand, else grass) so texture and macro
|
|
1608
|
+
// material always agree. Distance-faded by pxWorld; mips + macro biome color carry the far field.
|
|
1609
|
+
// MIP-OUT, not fade-out (user 2026-06-11 'instead of fading out the textures, mip them out'):
|
|
1610
|
+
// the 15-20km camera-distance fade is GONE -- the splat persists at every distance and the
|
|
1611
|
+
// hardware mip chain carries the far field, where the texture averages to its mean color
|
|
1612
|
+
// (= the macro shade, by the layer-mean shade-match; rock's raw-photo mean = __surfRockMean).
|
|
1613
|
+
vec3 texDn = vec3(0.0); // photo-texture WORLD-SPACE normal perturbation, applied after uReliefShade
|
|
1614
|
+
// SPLAT RUNS UNDERWATER TOO (user 2026-06-11 'continue under water as sand and rock'): the old
|
|
1615
|
+
// vH > -2 gate cut the photo textures at the waterline, leaving the seabed flat-colored.
|
|
1616
|
+
if (uHasSurfTex > 0.5 && uTexMix > 0.001) {
|
|
1617
|
+
// material weights from the existing gates (climate = vClimate: z=temp, w=humid)
|
|
1618
|
+
// SAND GATE = THE MACRO DESERT GATE (user 2026-06-11 'all the grassy areas need to have the
|
|
1619
|
+
// grass texture'): the old humid<0.42 band splatted SAND across savanna/steppe/meadow-edge
|
|
1620
|
+
// regions whose MACRO color is green/gold vegetation (biomeColor only reaches DESERT at
|
|
1621
|
+
// smoothstep(0.60,0.85,1-humid)) -- green ground wore the sand photo. One source of truth:
|
|
1622
|
+
// reuse the exact macro desert ramp, so the sand texture appears precisely where the macro
|
|
1623
|
+
// paints desert; savanna/steppe splat GRASS tinted dry-gold by the layer shade-match.
|
|
1624
|
+
float dryHot = smoothstep(0.60, 0.85, 1.0 - climate.w) * smoothstep(0.42, 0.62, climate.z);
|
|
1625
|
+
// BEACH (user 2026-06-10 'the beaches should be sand'): the shore profile eases over the
|
|
1626
|
+
// first ~60m of elevation, so low gentle land near sea level splats sand regardless of climate.
|
|
1627
|
+
float beach = (1.0 - smoothstep(10.0, 80.0, vH)) * (1.0 - smoothstep(0.22, 0.42, slope));
|
|
1628
|
+
// SAND REGIONS SUPPRESS ROCK (user 2026-06-10 'rock being used instead of sand'): in
|
|
1629
|
+
// deserts/dunes/beaches sand drapes moderate slopes; rock only wins on genuinely steep faces
|
|
1630
|
+
// there (gate shifted toward 0.5-0.7 inside sand regions instead of slopeRock 0.28-0.55).
|
|
1631
|
+
float sandRegion = clamp(max(max(dryHot, beach), smoothstep(0.0, 0.25, vDuneCrest)), 0.0, 1.0);
|
|
1632
|
+
// SPLAT ROCK GATE DECOUPLED from the macro slopeRock (user 2026-06-11 'a lot of grass turning
|
|
1633
|
+
// into rocky patches again'): the user-calibrated global soft blend slopeRock [-0.6,1] puts
|
|
1634
|
+
// ~37% ROCK LAYER weight on perfectly flat ground, and the displacement-sharpened top-2
|
|
1635
|
+
// crossfade then flips whole displacement blobs to the rock PHOTO on grassland. The macro
|
|
1636
|
+
// color blend keeps the calibrated tone; the rock TEXTURE layer needs real slope: floor the
|
|
1637
|
+
// splat gate at 0.18 so flat/gentle land splats grass, rock starts on genuine slopes.
|
|
1638
|
+
float srLo = max(slopeRock.x, 0.18), srHi = max(slopeRock.y, srLo + 0.2);
|
|
1639
|
+
float wRock = smoothstep(mix(srLo, 0.50, sandRegion), mix(srHi, 0.70, sandRegion), rockSlope);
|
|
1640
|
+
float snowHi = smoothstep(snowEdges.x, snowEdges.y, vH);
|
|
1641
|
+
float snowCold = (1.0 - smoothstep(0.30, 0.75, climate.z)) * smoothstep(snowEdges.x * 0.5, snowEdges.x, vH);
|
|
1642
|
+
// POLAR/ICE-BIOME SNOW (user 2026-06-10 'put the snow texture on the snow'): the ICE biome
|
|
1643
|
+
// whitens cold lowland (biomeColor gate 1-smoothstep(0.10,0.18,tempEff)) at ANY elevation, but
|
|
1644
|
+
// wSnow was elevation-gated only -- polar snowfields were splatting the GRASS layer. Match the
|
|
1645
|
+
// effective-temperature ice gate (raw temp minus the elevation/latitude lapse the biome uses).
|
|
1646
|
+
float lat2 = asin(clamp(vWorld.y / max(length(vWorld), 1.0), -1.0, 1.0));
|
|
1647
|
+
float tempEff2 = clamp(climate.z - clamp(vH / 4500.0, 0.0, 0.55) * uBiomeBandBias
|
|
1648
|
+
- 0.18 * (abs(lat2) / 1.5708) * uBiomeBandBias, 0.0, 1.0);
|
|
1649
|
+
float iceClimate = (1.0 - smoothstep(0.10, 0.20, tempEff2)) * step(0.0, vH); // no snow layer on the seabed
|
|
1650
|
+
float wSnow = clamp(snowHi + 0.7 * snowCold + iceClimate, 0.0, 1.0) * (1.0 - 0.6 * wRock);
|
|
1651
|
+
float wSand = sandRegion * (1.0 - wRock) * (1.0 - wSnow) * (1.0 - smoothstep(0.40, 0.60, slope));
|
|
1652
|
+
float wGrass = max(1.0 - wRock - wSnow - wSand, 0.0);
|
|
1653
|
+
vec4 w4 = vec4(wGrass, wRock, wSand, wSnow); // layers 0..3 = grass,rock,sand,snow
|
|
1654
|
+
// BEACH BAND + UNDERWATER in one mask (user 2026-06-11): sand owns everything below the
|
|
1655
|
+
// beach ceiling uBeachTopM -- grass/snow weight folds into sand continuously through h=0 to
|
|
1656
|
+
// the seabed, so no vegetation can ever splat at or below the waterline (structural).
|
|
1657
|
+
float uwM = 1.0 - smoothstep(uBeachTopM * 0.6, uBeachTopM, vH);
|
|
1658
|
+
w4.z += (w4.x + w4.w) * uwM; w4.x *= 1.0 - uwM; w4.w *= 1.0 - uwM;
|
|
1659
|
+
w4 /= (w4.x + w4.y + w4.z + w4.w + 1e-4);
|
|
1660
|
+
// top-2 layer pick: 4-way blending would cost 24 taps; the gates are spatially near-exclusive,
|
|
1661
|
+
// so the two heaviest layers + a displacement-sharpened crossfade carry every transition.
|
|
1662
|
+
float lA = 0.0, wA = w4.x, lB = 0.0, wB = -1.0;
|
|
1663
|
+
if (w4.y > wA) { lB = lA; wB = wA; lA = 1.0; wA = w4.y; } else if (w4.y > wB) { lB = 1.0; wB = w4.y; }
|
|
1664
|
+
if (w4.z > wA) { lB = lA; wB = wA; lA = 2.0; wA = w4.z; } else if (w4.z > wB) { lB = 2.0; wB = w4.z; }
|
|
1665
|
+
if (w4.w > wA) { lB = lA; wB = wA; lA = 3.0; wA = w4.w; } else if (w4.w > wB) { lB = 3.0; wB = w4.w; }
|
|
1666
|
+
// tile-unit coords, FRAGMENT-ANCHORED snap (the rockO fp32 lesson): raw vWorld/tile loses
|
|
1667
|
+
// fp32 precision = visible UV stairs. Snap step = 1024 tiles exactly, so wt jumps by an
|
|
1668
|
+
// integer tile count across a snap boundary = identical wrapped sample (REPEAT), no phase
|
|
1669
|
+
// reset, camera-independent.
|
|
1670
|
+
highp float snapM = uTexTileM * 1024.0;
|
|
1671
|
+
highp vec3 wt = (vWorld - floor(vWorld / snapM) * snapM) / uTexTileM;
|
|
1672
|
+
// DOMAIN WARP anti-repetition (user 2026-06-10 'distort the textures so they dont look
|
|
1673
|
+
// repeated' + 'distort more, waves a bit large'): 2-octave world-dir warp -- ~11km waves at
|
|
1674
|
+
// +/-0.6 tile plus ~2.8km waves at +/-0.3 tile -- so repeats both shift AND shear locally;
|
|
1675
|
+
// no straight tile lattice survives. World-dir keyed -> seam-safe, camera-independent.
|
|
1676
|
+
// uTexWarp (__texWarp) scales the whole warp live.
|
|
1677
|
+
// VS-COMPUTED WARP, APPLIED EXACTLY ONCE (2026-06-12): vTexWarp carries the 3-octave halved-
|
|
1678
|
+
// frequency warp from the vertex shader (-9 snoise3/pixel). This is the ONLY place the warp
|
|
1679
|
+
// touches texture coordinates; every layer tap (albedo A/B, normal A/B, all four materials)
|
|
1680
|
+
// samples through this shared wt, so the warp cannot layer or double-apply.
|
|
1681
|
+
wt += vTexWarp * uTexWarp;
|
|
1682
|
+
vec3 tw = abs(n); tw = tw * tw; tw /= (tw.x + tw.y + tw.z + 1e-4);
|
|
1683
|
+
vec4 albA = surfTriTap(uSurfAlb, wt, tw, lA);
|
|
1684
|
+
vec3 nrmA = surfTriNrm(uSurfNrm, wt, tw, lA, n);
|
|
1685
|
+
float bAB = clamp(wA / max(wA + wB, 1e-4), 0.0, 1.0);
|
|
1686
|
+
vec4 texAlb = albA; vec3 texNrm = nrmA;
|
|
1687
|
+
if (wB > 0.02) { // second layer only where a real transition exists (saves 6 taps elsewhere)
|
|
1688
|
+
vec4 albB = surfTriTap(uSurfAlb, wt, tw, lB);
|
|
1689
|
+
vec3 nrmB = surfTriNrm(uSurfNrm, wt, tw, lB, n);
|
|
1690
|
+
// displacement-sharpened transition -- WEIGHT-DOMINANT (user 2026-06-10 'large patches
|
|
1691
|
+
// of rock texture in mountains, not slope-keyed'): the old (hA-hB)*4 let the texture's
|
|
1692
|
+
// 2.4km displacement features decide the material outright wherever the gate weights sat
|
|
1693
|
+
// mid-range, so whole displacement blobs flipped to rock. The slope/snow/climate weight
|
|
1694
|
+
// now dominates (x3) and displacement only crisps the edge (x0.8) within the true
|
|
1695
|
+
// transition band -- material placement is the gates', texture only shapes the seam.
|
|
1696
|
+
// displacement term 0.8 -> 0.3 (user 2026-06-11 'still see bowls of rock texture'):
|
|
1697
|
+
// 0.8 still let the displacement photo's bowl-shaped blobs flip whole patches to rock
|
|
1698
|
+
// inside the transition band; 0.3 only feathers the seam edge.
|
|
1699
|
+
float bSharp = clamp((bAB * 2.0 - 1.0) * 3.0 + (albA.a - albB.a) * 0.3 + 0.5, 0.0, 1.0);
|
|
1700
|
+
texAlb = mix(albB, albA, bSharp);
|
|
1701
|
+
texNrm = mix(nrmB, nrmA, bSharp);
|
|
1702
|
+
}
|
|
1703
|
+
float k = uTexMix;
|
|
1704
|
+
// macro-tinted detail (user 2026-06-10 'the textured patch must be tinted to the same shade
|
|
1705
|
+
// as the spot its replacing'): the texture contributes STRUCTURE + relative chroma only,
|
|
1706
|
+
// luminance-normalized onto the macro biome/climate color, so the splat never shifts the
|
|
1707
|
+
// shade of the ground it covers. uTexPhoto (default 0) can blend raw photo color back in.
|
|
1708
|
+
vec3 texC = texAlb.rgb;
|
|
1709
|
+
// LAYER-MEAN shade-match (user 2026-06-11 'dont see grass/snow textures' + 'terrain gets
|
|
1710
|
+
// darker'): dividing by the PER-PIXEL luminance cancelled all texture structure, and the
|
|
1711
|
+
// raw-photo blend that replaced it shifted the shade (the photos are darker than the macro).
|
|
1712
|
+
// Dividing by the layer's MEAN luminance (loader-computed uSurfMeanL) keeps every per-pixel
|
|
1713
|
+
// deviation visible while the patch average lands exactly on the macro shade.
|
|
1714
|
+
float mA = uSurfMeanL[int(lA + 0.5)];
|
|
1715
|
+
float texL = wB > 0.02 ? mix(uSurfMeanL[int(lB + 0.5)], mA, bAB) : mA;
|
|
1716
|
+
vec3 detail = texC * (albedo / max(texL, 0.02));
|
|
1717
|
+
// ROCK SHOWS THE TRUE PHOTO (user 2026-06-10 'we still see the original rock texture --
|
|
1718
|
+
// replace completely'): tinting rock to the macro shade just reproduced the old grey/tan,
|
|
1719
|
+
// so the rock layer takes the raw photo color; grass/sand/snow stay shade-matched.
|
|
1720
|
+
// raw-photo rock is NEAR-FIELD only (user 2026-06-11 'elevation and color mismatching'):
|
|
1721
|
+
// with mip-out the raw photo (mean lum .18, dark grey) was carrying to every distance, so
|
|
1722
|
+
// far mountains went dark instead of the macro tan. Fade photoF out as the texture detail
|
|
1723
|
+
// goes sub-pixel (pxWorld 60->240m); the far field returns to the shade-matched mip average.
|
|
1724
|
+
float nearTex = 1.0 - smoothstep(60.0, 240.0, pxWorld);
|
|
1725
|
+
float photoF = max(uTexPhoto, max(smoothstep(0.25, 0.6, w4.y) * 0.5, uTexPhotoNear) * nearTex);
|
|
1726
|
+
// MATERIAL IDENTITY (user 2026-06-12): the photo's OWN hue at the MACRO's luminance -- the
|
|
1727
|
+
// ground reads unambiguously as its material (grass green / sand tan / rock grey) without the
|
|
1728
|
+
// raw photo's darkness ('terrain gets darker' lesson) and without the macro's biome-brown
|
|
1729
|
+
// repaint ('neither grass nor sand' defect). Layer-mean normalized like `detail`.
|
|
1730
|
+
vec3 texIdent = texC * (dot(albedo, vec3(0.2126, 0.7152, 0.0722)) / max(texL, 0.02));
|
|
1731
|
+
albedo = clamp(mix(albedo, mix(detail, texIdent, photoF), k), 0.0, 1.0);
|
|
1732
|
+
// displacement-normal relief: WORLD-SPACE UDN perturbation from surfTriNrm (each projection
|
|
1733
|
+
// plane's tangent axes, not the radial frame). Amplitude capped low (scramble lesson d262b5e);
|
|
1734
|
+
// applied AFTER the uReliefShade exaggeration below so the exaggeration never amplifies it.
|
|
1735
|
+
texDn = texNrm * (uTexNrmK * k);
|
|
1736
|
+
}
|
|
1737
|
+
#ifdef _DEBUGVIEW_
|
|
1738
|
+
// DIAG displayMode 7: raw river field -> blue where the river line fires, grey ridge field
|
|
1739
|
+
// elsewhere. Lets a witness SEE the drainage network + camera together to tune frequency
|
|
1740
|
+
// (not guess from screenshots). Pure diagnostic; no effect on the lit path.
|
|
1741
|
+
if (displayMode == 7) {
|
|
1742
|
+
float rv = riverMask(vWorld, vH, climate.z, climate.w, pxWorld);
|
|
1743
|
+
fragColor = vec4(mix(vec3(0.15), vec3(0.1,0.4,0.9), rv), 1.0); return;
|
|
1744
|
+
}
|
|
1745
|
+
// DIAG displayMode 8: RIVER GATING -- the ACTUAL integrated river the geometry+lit path uses (user
|
|
1746
|
+
// 2026-06-02: 'river gating is empty'). The old version re-evaluated riverMask per-pixel AND a dead
|
|
1747
|
+
// lake-suppression gated on humid>0.62 (most land is ~0.39) so it read ~0 = empty. Now it shows the
|
|
1748
|
+
// VS-integrated varyings directly: BLUE = vRiverWet (the flowing-water line the geometry carved +
|
|
1749
|
+
// the lit path composites), GREEN = vLakeWet (lake water), so it matches what actually renders. The
|
|
1750
|
+
// raw per-pixel network is still in mode 7.
|
|
1751
|
+
if (displayMode == 8) {
|
|
1752
|
+
vec3 col = vec3(0.12);
|
|
1753
|
+
col = mix(col, vec3(0.10, 0.45, 0.95), clamp(vRiverWet, 0.0, 1.0)); // river line (integrated)
|
|
1754
|
+
col = mix(col, vec3(0.15, 0.85, 0.55), clamp(vLakeWet, 0.0, 1.0)); // lake water
|
|
1755
|
+
fragColor = vec4(col, 1.0); return;
|
|
1756
|
+
}
|
|
1757
|
+
// DIAG displayMode 9: DISCRETE BIOME MAP -> each fragment flat-colored by its classified
|
|
1758
|
+
// biome (ocean/ice/tundra/desert/savanna/rainforest/taiga/forest/meadow). Lets a witness
|
|
1759
|
+
// COUNT contiguous biome regions + confirm logical placement (deserts in subtropics, ice at
|
|
1760
|
+
// poles, rainforest at equator), instead of eyeballing the blended lit palette.
|
|
1761
|
+
if (displayMode == 9) {
|
|
1762
|
+
fragColor = vec4(biomeClassColor(climate.z, climate.w, vH), 1.0); return;
|
|
1763
|
+
}
|
|
1764
|
+
// DIAG displayMode 10: CANYON field -> red where the gorge network fires (arid elevated only),
|
|
1765
|
+
// grey ridge field elsewhere. Lets a witness SEE the canyon network density independent of
|
|
1766
|
+
// landing the nadir exactly on a thin gorge line.
|
|
1767
|
+
if (displayMode == 10) {
|
|
1768
|
+
// ONE FRACTAL: canyonMask now samples the SAME canyonRidgeField as the VS carve (unified), so
|
|
1769
|
+
// the per-pixel network coincides with the geometry -- show the single full-res field (no more
|
|
1770
|
+
// overlaying canyonMask + vCanyonDep at two sample rates, which read as 'two resolutions').
|
|
1771
|
+
float cd; float cv = canyonMask(vWorld, vH, climate.z, climate.w, pxWorld, cd);
|
|
1772
|
+
// grey background -> orange canyon line by mask, deepening toward the gorge floor (cd).
|
|
1773
|
+
vec3 col = mix(vec3(0.15), mix(vec3(0.75,0.40,0.15), vec3(0.9,0.15,0.05), cd), cv);
|
|
1774
|
+
fragColor = vec4(col, 1.0); return;
|
|
1775
|
+
}
|
|
1776
|
+
// DIAG displayMode 11: CLIFF validation view -> RED = cliff/escarpment riser faces (vCliffFace),
|
|
1777
|
+
// GREEN = canyon walls (vCanyonDep on steep slope), so the user can SEE where cliffs are placed
|
|
1778
|
+
// independent of lighting/strata. Grey elsewhere. The witness for the cliff terrace placement.
|
|
1779
|
+
if (displayMode == 11) {
|
|
1780
|
+
float cliffR = vCliffFace;
|
|
1781
|
+
float canyonG = vCanyonDep * smoothstep(0.30, 0.55, slope);
|
|
1782
|
+
fragColor = vec4(mix(vec3(0.12), vec3(1.0, 0.2, 0.1), cliffR)
|
|
1783
|
+
+ vec3(0.0, 0.8, 0.2) * canyonG, 1.0); return;
|
|
1784
|
+
}
|
|
1785
|
+
// DIAG displayMode 12: PATCHES -> each LOD LEVEL a DISTINCT colour (user: validate the dense LOD
|
|
1786
|
+
// is always centered under the camera). Hue cycles by level via a golden-ratio rotation so any two
|
|
1787
|
+
// adjacent levels are far apart in hue; the finest (highest-level) patches under the camera read as
|
|
1788
|
+
// the hottest band, and the rings must be CONCENTRIC + CENTERED on the camera nadir. A thin dark
|
|
1789
|
+
// cell-grid (vGrid) overlays so individual quads are visible. Pure per-instance vLevel -> no field.
|
|
1790
|
+
if (displayMode == 12) {
|
|
1791
|
+
float h = fract(vLevel * 0.61803398875); // golden-ratio hue per level (well-spread)
|
|
1792
|
+
// HSV->RGB at full sat/val for h, mid-V so text/grid reads.
|
|
1793
|
+
vec3 rgb = clamp(abs(fract(h + vec3(0.0, 0.6666667, 0.3333333)) * 6.0 - 3.0) - 1.0, 0.0, 1.0);
|
|
1794
|
+
vec3 col = rgb * 0.85 + 0.1;
|
|
1795
|
+
// quad-cell grid overlay (same fwidth-AA lines as wireframe) so patch boundaries are crisp.
|
|
1796
|
+
vec2 g = vGrid * 24.0; vec2 gf = abs(fract(g) - 0.5); vec2 gw = fwidth(g) * 1.2;
|
|
1797
|
+
float line = 1.0 - min(smoothstep(0.0, gw.x, gf.x), smoothstep(0.0, gw.y, gf.y));
|
|
1798
|
+
col = mix(col, vec3(0.0), clamp(line, 0.0, 1.0) * 0.55);
|
|
1799
|
+
fragColor = vec4(col, 1.0); return;
|
|
1800
|
+
}
|
|
1801
|
+
#endif // _DEBUGVIEW_ (modes 7-12)
|
|
1802
|
+
// Material = the biome height/slope/climate ramp (provably 0 edges). The ortho atlas + per-pixel
|
|
1803
|
+
// detail texturing are both REMOVED (GPU one-fractal): closeup detail comes from the mesh
|
|
1804
|
+
// subdividing into a denser sample of the single VS height fractal, never a tile sample or grain.
|
|
1805
|
+
if (displayMode == 4) { fragColor = vec4(albedo, 1.0); return; } // biome ramp
|
|
1806
|
+
if (displayMode == 2) { fragColor = vec4(albedo, 1.0); return; }
|
|
1807
|
+
|
|
1808
|
+
// ---- Bruneton-style atmospheric lighting (ported analytic single-scatter) ----
|
|
1809
|
+
// Map the terrain point + camera into atmosphere space (km, surface=ATM_BOTTOM).
|
|
1810
|
+
highp vec3 pAtm = atmPos(vWorld, terrainR); // W7: km-scale (~6360) atmosphere coords -> highp for the AP segment precision
|
|
1811
|
+
highp vec3 camAtm = atmPos(camWorld, terrainR);
|
|
1812
|
+
// GENTLE-SLOPE RELIEF SHADING (user 2026-06-10 'even gentle slopes shaded so terrain elevation
|
|
1813
|
+
// is obvious'): exaggerate the tangential tilt of the LIT normal only -- the material gates keep
|
|
1814
|
+
// the true geometric n, so placement is unchanged; lighting contrast on rolling ground increases.
|
|
1815
|
+
if (vH > -2.0 && uReliefShade > 1.0) nLit = normalize(uz + (nLit - uz) * uReliefShade);
|
|
1816
|
+
// photo-texture detail normal at its calibrated amplitude, OUTSIDE the relief exaggeration.
|
|
1817
|
+
// ADDED in world space (the old `- ux*dn.x - uy*dn.y` subtracted an already-negated Sobel
|
|
1818
|
+
// normal in a frame the triplanar RG was never expressed in -- the fundamental both-at-once
|
|
1819
|
+
// math bug: bumps lit from wrong directions everywhere the splat is active).
|
|
1820
|
+
nLit = normalize(nLit + texDn);
|
|
1821
|
+
vec3 nAtm = nLit; // relief-exaggerated normal for lighting (macro n still drives material)
|
|
1822
|
+
#ifdef _DEBUGVIEW_
|
|
1823
|
+
// Normals view shows the FINAL lit normal (geometry + relief exaggeration + texture detail
|
|
1824
|
+
// normal) -- the normal the lighting actually uses, texture normals included.
|
|
1825
|
+
if (displayMode == 1) { fragColor = vec4(nAtm * 0.5 + 0.5, 1.0); return; }
|
|
1826
|
+
#endif
|
|
1827
|
+
|
|
1828
|
+
// ANIMATED OCEAN BRANCH REMOVED (user 2026-06-11 'the ocean should be a separate surface'):
|
|
1829
|
+
// sub-sea fragments are the real SEABED now (sand/rock, lit as land below); the animated water
|
|
1830
|
+
// shading moved to the uIsWater=1 water-surface pass (early return at the top of main), alpha-
|
|
1831
|
+
// blended over this seabed. Sea ice still keys the seabed albedo near the poles.
|
|
1832
|
+
|
|
1833
|
+
vec3 skyIrr;
|
|
1834
|
+
vec3 sunIrr = atm_sunSkyIrradiance(pAtm, nAtm, sunDir, skyIrr);
|
|
1835
|
+
// The Rayleigh sky irradiance is strongly blue; at full strength it tints the lit
|
|
1836
|
+
// CONTINENTS blue-grey from orbit (witnessed: lit mean [83,95,112] vs warm albedo
|
|
1837
|
+
// [90,83,74] at 2000km -- the blue is the sky-irradiance term, NOT aerial inscatter).
|
|
1838
|
+
// Partially DESATURATE skyIrr toward its luminance so it still brightens shadowed/
|
|
1839
|
+
// ambient regions without washing the land's true color blue. Land albedo dominates;
|
|
1840
|
+
// a modest blue ambient remains (physically the sky does add some blue fill).
|
|
1841
|
+
float skyL = dot(skyIrr, vec3(0.2126, 0.7152, 0.0722));
|
|
1842
|
+
vec3 skyIrrBalanced = mix(vec3(skyL), skyIrr, 0.35); // 0 = grey, 1 = full blue sky
|
|
1843
|
+
// RELIEF RECOVERY (Real-World Look): the bright flat sky fill washed N.sun ridge/valley contrast
|
|
1844
|
+
// to ~flat. CUT the fill (uSkyFill, was implicit 1.0) + cool-tint it so shadows read sky-blue, so
|
|
1845
|
+
// the direct sun becomes the dominant key and relief self-shading actually shows.
|
|
1846
|
+
skyIrrBalanced *= uSkyFill * vec3(0.85, 0.92, 1.10);
|
|
1847
|
+
// Lambert BRDF (albedo/PI) under sun + (balanced) sky irradiance. Keep an ambient
|
|
1848
|
+
// FLOOR so night/shadow faces are never pure black (PRD lit-terrain-dark-black).
|
|
1849
|
+
// CURVATURE / SLOPE AMBIENT-OCCLUSION (canyon floors + cliff bases): incised/concave fragments
|
|
1850
|
+
// see less sky, so darken the AMBIENT (sky + floor) there for contact-shadow depth. Keyed on the
|
|
1851
|
+
// interpolated canyon gorge depth (vCanyonDep ->1 in the gorge core) so floors darken while rims
|
|
1852
|
+
// stay bright. Direct SUN is NOT occluded (avoids double-darkening already-shaded faces). uAoAmt
|
|
1853
|
+
// lever. Pure fn of the carve varying -> no extra sampling, seam-safe.
|
|
1854
|
+
// OBJECT-SPACE AO (2026-06-05): augment the gorge-depth term with a cheap analytic sky-occlusion
|
|
1855
|
+
// estimate so cliff BASES and steep concave faces -- not just canyon floors -- pick up contact
|
|
1856
|
+
// shadow. A true N-sweep horizon-angle integral over the fractal (karim.naaji.fr/lsao) is the
|
|
1857
|
+
// research-best but per-pixel fractal sweeps would re-add the close-up VS/FS cost the pipeline is
|
|
1858
|
+
// sensitive to; instead approximate the sky-view factor from data already in hand: (a) the gorge
|
|
1859
|
+
// depth varying (deep incision -> walls occlude sky), and (b) surface STEEPNESS (a near-vertical
|
|
1860
|
+
// face sees only a hemisphere's edge of sky -> ~half occlusion at slope->1). Both darken AMBIENT
|
|
1861
|
+
// only (never the direct sun, avoiding double-shade). uAoAmt lever. Pure fn of varyings+normal ->
|
|
1862
|
+
// no extra sampling, seam-safe, zero FPS cost vs the old single-term version.
|
|
1863
|
+
float aoAmt = (uAoAmt > 0.0 ? uAoAmt : 1.0);
|
|
1864
|
+
float gorgeAO = 0.0; // canyon AO decoupled (user 2026-06-10: canyons impose ELEVATION only, no material/AO keying)
|
|
1865
|
+
float slopeAO = smoothstep(0.45, 0.95, slope) * 0.40; // steep concave faces see less sky (0.30->0.40, deeper valleys)
|
|
1866
|
+
float cliffAO = 1.0 - min(gorgeAO + slopeAO, 0.75) * aoAmt; // cap so faces never go black
|
|
1867
|
+
// STRONGER NORMAL-DRIVEN SHADING (user 2026-06-03: 'normals arent affecting the lit view properly').
|
|
1868
|
+
// The lit relief was too subtle (witnessed litON SD 6.4 vs flat 5.9 = only ~8% contrast) because
|
|
1869
|
+
// sunIrr (the N.sun direct term) was diluted by the flat sky ambient + the 0.06 albedo floor. Cut
|
|
1870
|
+
// the ambient floor (0.06->0.03) so shaded slopes go genuinely darker, and BOOST the directional
|
|
1871
|
+
// sun term (x1.6) so the N.sun slope contrast reads clearly. The (1/PI) Lambert norm + ACES tonemap
|
|
1872
|
+
// keep it from blowing out; a small floor still prevents pure-black shadow faces.
|
|
1873
|
+
// sunIrr boost 1.6 -> 1.25 + ambient floor 0.03 -> 0.04 (user 2026-06-03 screenshot: the 1.6 boost
|
|
1874
|
+
// BLEW OUT high/bright rock to flat white, losing relief detail). 1.25 keeps the slope contrast
|
|
1875
|
+
// strong (N.sun still drives the shading) without clipping the highlights to white on lit rock.
|
|
1876
|
+
// AMBIENT FLOOR lifted 0.025->0.05 (live-deck witness mut-1780681995401: with the analytic+fs normal ON
|
|
1877
|
+
// HALF the deck frame crushed to NEAR-BLACK -- mean 151->91, n898k->434k -- so relief read as black
|
|
1878
|
+
// HOLES not legible rugged texture). A genuine shadowed rock face is dark grey-blue (skylight), never
|
|
1879
|
+
// pure black; the higher floor keeps micro-relief readable as texture while the direct sun still drives
|
|
1880
|
+
// the ridge/valley key contrast. Tied to cliffAO so true contact-shadow concavities still darken.
|
|
1881
|
+
// PER-VERTEX SHADING (DEFECT 2): fold the interpolated vShadeAO crease/micro-relief occlusion into
|
|
1882
|
+
// the ambient term so geometry reads relief even where the FS detail-normal is faint. nearFade-gated
|
|
1883
|
+
// (full at the deck, ->1 i.e. no effect at orbit) so the macro/orbit look is unchanged. Combines
|
|
1884
|
+
// multiplicatively with cliffAO -- both are sky-occlusion factors that should compose.
|
|
1885
|
+
// W5: vShadeAO (per-vertex, from the deleted VS gradient) RECOMPUTED here from the Sobel slope (user
|
|
1886
|
+
// 2026-06-07 'recompute AO from Sobel'). creaseAO = slope*0.45 (steep landform -> valley/crease
|
|
1887
|
+
// occlusion); microAO from the fine per-pixel gslope. uVertexAO lever + the 0.45 floor preserved.
|
|
1888
|
+
float fsShadeAO = clamp(1.0 - (slope * 0.45 + gslope * 0.35) * uVertexAO, 0.45, 1.0);
|
|
1889
|
+
// FADE-IN FIX (user 'a layer fades in at close distance'): vAO was mix(1.0, fsShadeAO, nearFade) where
|
|
1890
|
+
// nearFade rises 0->1 as pxWorld shrinks 180->8m on approach -> the Sobel crease-AO darkening animated
|
|
1891
|
+
// IN as the camera neared = the visible 'layer fading in'. fsShadeAO is a per-pixel SOBEL-slope quantity
|
|
1892
|
+
// (not sub-pixel noise) so it does NOT alias and needs no distance gate -- present at all distances, no
|
|
1893
|
+
// pop. This is the nearFade gate the P5 unification missed on the shading-AO path. nearFade now unused.
|
|
1894
|
+
float vAO = fsShadeAO; // crease-AO present at all distances (P9, no fade-in pop)
|
|
1895
|
+
float skyAO = cliffAO * vAO;
|
|
1896
|
+
// SHADOW-FACE FILL (user 2026-06-09: 'shadows are still full black'). On a day-side face pointing AWAY
|
|
1897
|
+
// from the sun, sunIrr->0 so ONLY this floor lights it. The old floor (albedo*0.05*cliffAO*vAO) was
|
|
1898
|
+
// crushed to ~0 on dark-albedo rock AND on steep/concave faces (cliffAO*vAO -> 0 exactly where shadows
|
|
1899
|
+
// are) -> black shadows. FIX: (a) an ALBEDO-INDEPENDENT additive sky term so even dark rock never goes
|
|
1900
|
+
// black, (b) DROP the cliffAO/vAO multipliers from the floor (they belong on the sky KEY, not the
|
|
1901
|
+
// guaranteed floor), (c) lift the albedo-tinted part to 0.22. Result: shadowed faces settle to a dim
|
|
1902
|
+
// cool-grey, never black; the direct sun still drives the lit/shadow contrast above this floor.
|
|
1903
|
+
// The 0.22 flat lift (no AO) flooded shadowed ANGLED faces to a uniform cool-grey, washing out the
|
|
1904
|
+
// relief (user 2026-06-10: 'angled faces turned to rock/grey'). Restore directional shading: scale the
|
|
1905
|
+
// albedo-tinted part by a SOFTENED sky exposure (mix(0.45,1.0,skyAO) -- never crushes to 0 like the old
|
|
1906
|
+
// cliffAO*vAO did, but lets faces that see less sky read darker = the relief returns), and lower the lift
|
|
1907
|
+
// 0.22->0.14. The albedo-INDEPENDENT flat sky floor stays as the never-black guarantee (the 9cfa643 win).
|
|
1908
|
+
vec3 ambientFloor = albedo * (0.14 * mix(0.45, 1.0, skyAO)) + vec3(0.020, 0.026, 0.038);
|
|
1909
|
+
// sky AO no longer SQUARED (was cliffAO*cliffAO): the squared sky term over-crushed shadowed micro-
|
|
1910
|
+
// relief to black at the deck. Single cliffAO still darkens concavities for depth, but shadowed
|
|
1911
|
+
// faces retain sky fill so the analytic normal reads as rugged texture, not black blotches.
|
|
1912
|
+
// (the old vH<-2 ocean bypass is gone -- the seabed is land now, lit like land; the water
|
|
1913
|
+
// surface is its own pass.)
|
|
1914
|
+
vec3 lit = albedo * (sunIrr * 1.25 + skyIrrBalanced * skyAO) * (1.0/ATM_PI) + ambientFloor;
|
|
1915
|
+
|
|
1916
|
+
// AERIAL PERSPECTIVE (analytic single-scatter, re-added 2026-06-05 for the space->ground depth
|
|
1917
|
+
// cue -- the single biggest missing realism term for the seamless descent; ref Hillaire-2020,
|
|
1918
|
+
// LUT-free inline form). The OLD removed version was a flat 15km horizon haze band the user
|
|
1919
|
+
// disliked; this is physically-grounded instead: integrate Rayleigh+Mie in-scatter along the
|
|
1920
|
+
// EXACT camera->fragment segment and attenuate the lit color by the segment transmittance, so
|
|
1921
|
+
// the haze is PROPORTIONAL to how much atmosphere the view ray actually crossed. DISTANCE-GATED
|
|
1922
|
+
// hard at both ends: ~zero within a few km (FPS-ground stays crisp, no over-haze the user hated)
|
|
1923
|
+
// and ramping in only across tens-of-km+ (the depth cue that makes distant ridges recede and the
|
|
1924
|
+
// space->ground transition read as real). N=8 march (cheaper than the sky pass's 16; the surface
|
|
1925
|
+
// term tolerates it), single draw, reuses atm_* -> zero assets.
|
|
1926
|
+
vec3 color = lit;
|
|
1927
|
+
{
|
|
1928
|
+
highp vec3 camA = atmPos(camWorld, terrainR); // W7: km-scale -> highp
|
|
1929
|
+
highp vec3 segKm = pAtm - camA; // W7: camera->fragment in km (cancellation of two km-scale points)
|
|
1930
|
+
highp float dKm = length(segKm);
|
|
1931
|
+
// gate: 0 below ~3km path, ramping to full by ~120km. keeps ground crisp, distance hazed.
|
|
1932
|
+
float apGate = smoothstep(3.0, 120.0, dKm);
|
|
1933
|
+
if (apGate > 0.002) {
|
|
1934
|
+
vec3 vRay = segKm / max(dKm, 1e-4);
|
|
1935
|
+
const int APN = 8;
|
|
1936
|
+
float apdt = dKm / float(APN);
|
|
1937
|
+
vec3 inscatR = vec3(0.0), inscatM = vec3(0.0);
|
|
1938
|
+
float odR = 0.0, odM = 0.0; // optical depth camera->sample
|
|
1939
|
+
for (int i = 0; i < APN; i++) {
|
|
1940
|
+
highp vec3 p = camA + vRay * (apdt * (float(i) + 0.5)); // W7: km-scale march point -> highp
|
|
1941
|
+
highp float pr = length(p);
|
|
1942
|
+
float dR = atm_rayleighDensity(pr) * apdt;
|
|
1943
|
+
float dM = atm_mieDensity(pr) * apdt;
|
|
1944
|
+
odR += dR; odM += dM;
|
|
1945
|
+
vec3 tView = exp(-(ATM_RAYLEIGH * odR + ATM_MIE_EXT * odM));
|
|
1946
|
+
vec3 tSun = atm_transmittanceToSun(p, sunDir);
|
|
1947
|
+
vec3 t = tView * tSun;
|
|
1948
|
+
inscatR += t * dR;
|
|
1949
|
+
inscatM += t * dM;
|
|
1950
|
+
}
|
|
1951
|
+
float nu = dot(vRay, sunDir);
|
|
1952
|
+
vec3 apTrans = exp(-(ATM_RAYLEIGH * odR + ATM_MIE_EXT * odM));
|
|
1953
|
+
vec3 apInscat = ATM_SOLAR_IRRADIANCE * (
|
|
1954
|
+
inscatR * ATM_RAYLEIGH * atm_rayleighPhase(nu) +
|
|
1955
|
+
inscatM * ATM_MIE * atm_miePhase(ATM_MIE_G, nu));
|
|
1956
|
+
// HORIZON HAZE FLOOR (Real-World Look): real aerial perspective fades distant terrain toward
|
|
1957
|
+
// a pale blue-grey sky, NEVER to black. When apTrans collapses (long limb path) the physical
|
|
1958
|
+
// single-scatter inscatter can dip in low-phase directions, leaving a black silhouette band.
|
|
1959
|
+
// Floor the fill with a multiple-scatter-style skylight ambient (1-apTrans) so the far ridge
|
|
1960
|
+
// dissolves into haze instead of going dark. Tied to uSkyFill so it's terraformable.
|
|
1961
|
+
vec3 skyHaze = uSkyFill * vec3(0.40, 0.55, 0.78) * (1.0 - apTrans);
|
|
1962
|
+
apInscat = max(apInscat, skyHaze);
|
|
1963
|
+
// attenuate the surface by transmittance and add the in-scatter, both faded by the gate
|
|
1964
|
+
// so the near field is untouched (apGate~0 -> mix back to the un-hazed lit color).
|
|
1965
|
+
vec3 hazed = lit * apTrans + apInscat;
|
|
1966
|
+
// TERMINATOR SUNSET REDDENING (Real-World Look): add a warm Rayleigh-out rim where the sun
|
|
1967
|
+
// GRAZES the limb (strictly gated on 1-|N.sun| near 0 so it never bleeds onto the full day
|
|
1968
|
+
// side). Rides the existing AP inscatter -- zero extra march. uTerminatorGlow lever.
|
|
1969
|
+
// peak at the terminator (N.sun~0), fall off toward both day-center and night-center, and
|
|
1970
|
+
// SQUARE it so the warm band is tight at the day/night line instead of a fat ring round the
|
|
1971
|
+
// whole limb. Warm amber (not pure red) so it reads as sunset haze, not a bruise.
|
|
1972
|
+
float gz = 1.0 - abs(dot(normalize(vWorld), sunDir));
|
|
1973
|
+
float graze = smoothstep(0.55, 1.0, gz); graze *= graze;
|
|
1974
|
+
// only on the LIT side of the terminator (mu>0) -- past the line the surface is night and
|
|
1975
|
+
// additive amber on near-black extinct land reads as a scorched maroon rim. Day-gate kills it.
|
|
1976
|
+
float termDay = smoothstep(-0.02, 0.18, dot(normalize(vWorld), sunDir));
|
|
1977
|
+
hazed += uTerminatorGlow * graze * termDay * vec3(1.0, 0.55, 0.34) * apGate;
|
|
1978
|
+
color = mix(lit, hazed, apGate * uHazeMul); // uHazeMul lever (2026-06-10 'pale hazy': full-strength haze milked the midground)
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
// RIVERS post-lighting (witnessed browser-2115/2118: the river-blue in ALBEDO is multiplied
|
|
1982
|
+
// by the warm sun irradiance (sunIrr.b is low) so land rivers lose their blue in the lit
|
|
1983
|
+
// result -- 15k blue albedo px -> 0 lit px, even with the sun HIGH (sunDotUp 0.85). Ocean
|
|
1984
|
+
// sidesteps this by bypassing the irradiance multiply (vH<0 branch). So rivers, like open
|
|
1985
|
+
// water, must be composited AFTER lighting: blend the post-lit color toward a sun-scaled
|
|
1986
|
+
// water-blue on the river line. ndlSun keeps the river shaded by the sun so it is not flat.
|
|
1987
|
+
// INLAND WATER = REAL WATER, SHADED LIKE THE OCEAN (user 2026-06-01i: 'it should use the same
|
|
1988
|
+
// content as the sealevel to draw because its also water'). Where vWaterDepth>0 (the flat water
|
|
1989
|
+
// plane sits above the carved floor -> genuinely submerged) we run the SAME water model as the
|
|
1990
|
+
// ocean branch: fresnel sky reflection + sun glint + shallow->deep depth tint, composited AFTER
|
|
1991
|
+
// lighting (the warm sun-irradiance multiply would otherwise kill the blue, the reason the ocean
|
|
1992
|
+
// bypasses it via vH<0). Gating on vWaterDepth (NOT the whole carve mask) makes the water LINE UP
|
|
1993
|
+
// with the flat carved surface -- the graded erosion banks above the waterline stay dry land.
|
|
1994
|
+
if (vH >= 0.0 && vWaterDepth > 0.0) {
|
|
1995
|
+
// submergence 0..1 over ~0..40m -> shoreline (thin water, terrain shows through) to deep.
|
|
1996
|
+
float sub = clamp(vWaterDepth / 40.0, 0.0, 1.0);
|
|
1997
|
+
// wave-perturbed water normal (small inland ripple), same tangent frame as the ground.
|
|
1998
|
+
highp vec3 wOriginL = floor(camWorld / 1024.0) * 1024.0; // W7: ~6.4e6 m snapped anchor -> highp
|
|
1999
|
+
highp vec2 wpL = vec2(dot(vWorld - wOriginL, ux), dot(vWorld - wOriginL, uy)); // W7: highp camera-relative wave coord
|
|
2000
|
+
vec2 slopeL = oceanWaveSlope(wpL, oceanTime) * 0.5; // calmer than open ocean
|
|
2001
|
+
highp float wDistL = length(camWorld - vWorld); // W7: camera->fragment cancellation -> highp
|
|
2002
|
+
slopeL *= clamp(1.0 - wDistL / 4000.0, 0.0, 1.0); // fade ripple at distance (anti-alias)
|
|
2003
|
+
vec3 wnL = normalize(uz - ux * slopeL.x - uy * slopeL.y);
|
|
2004
|
+
vec3 viewL = normalize(camWorld - vWorld);
|
|
2005
|
+
float f0L = 0.02;
|
|
2006
|
+
float fresL = f0L + (1.0 - f0L) * pow(clamp(1.0 - max(dot(wnL, viewL), 0.0), 0.0, 1.0), 5.0);
|
|
2007
|
+
vec3 hlL = normalize(sunDir + viewL);
|
|
2008
|
+
float specL = pow(max(dot(wnL, hlL), 0.0), 220.0);
|
|
2009
|
+
float ndlL = max(dot(wnL, sunDir), 0.0);
|
|
2010
|
+
// depth tint: a touch greener than the ocean (freshwater/sediment), shallow -> deep.
|
|
2011
|
+
vec3 shallowL = vec3(0.10, 0.30, 0.36);
|
|
2012
|
+
vec3 deepL = vec3(0.02, 0.10, 0.20);
|
|
2013
|
+
// congruent with the ocean: per-channel Beer-Lambert by true submerged depth (freshwater
|
|
2014
|
+
// slightly murkier), so lakes/rivers and the ocean read as one water system.
|
|
2015
|
+
vec3 TL = exp(-uOceanK * vWaterDepth * 1.6);
|
|
2016
|
+
vec3 waterBaseL = mix(deepL, shallowL, TL);
|
|
2017
|
+
vec3 skyColL = vec3(0.30, 0.42, 0.55);
|
|
2018
|
+
vec3 waterLitL = waterBaseL * (0.25 + 0.75 * ndlL);
|
|
2019
|
+
vec3 inlandWater = mix(waterLitL, skyColL, fresL * 0.85) + vec3(1.0, 0.95, 0.85) * specL * ndlL;
|
|
2020
|
+
inlandWater += waterBaseL * 0.05; // ambient floor (never black)
|
|
2021
|
+
// blend over the lit ground: full water in deep parts, terrain shows through at the shoreline.
|
|
2022
|
+
float waterCover = sub; // 0 at waterline -> 1 deep
|
|
2023
|
+
color = mix(color, inlandWater, waterCover);
|
|
2024
|
+
}
|
|
2025
|
+
// WIREFRAME OVERLAY (uWireframe=1): draw the per-quad mesh-cell grid lines so the LOD
|
|
2026
|
+
// tessellation is visible. Uses the parametric vGrid coord (0..1 per tile, GRID cells) with an
|
|
2027
|
+
// fwidth-based anti-aliased line at each cell boundary -> crisp wireframe at any distance,
|
|
2028
|
+
// independent of the triangle topology (WebGL2 has no glPolygonMode). Drawn pre-tonemap so the
|
|
2029
|
+
// lines tonemap with the surface.
|
|
2030
|
+
if (uWireframe > 0.5) {
|
|
2031
|
+
vec2 g = vGrid * 16.0; // GRID cells per tile edge (gridMeshSize 16; was 24, FPS lever)
|
|
2032
|
+
vec2 gf = abs(fract(g) - 0.5); // distance to nearest cell line in cell units
|
|
2033
|
+
vec2 gw = fwidth(g) * 1.2; // line half-width in cell units (AA)
|
|
2034
|
+
float line = 1.0 - min(smoothstep(0.0, gw.x, gf.x), smoothstep(0.0, gw.y, gf.y));
|
|
2035
|
+
color = mix(color, vec3(0.05, 1.0, 0.4), clamp(line, 0.0, 1.0) * 0.85);
|
|
2036
|
+
}
|
|
2037
|
+
// MACRO GLOBE SHADING (user 2026-06-03: at the DEFAULT ORBIT view the planet read as a FLAT MAP --
|
|
2038
|
+
// a luma scan across the disc was ~uniform, no terminator/limb darkening, because the OCEAN bypasses
|
|
2039
|
+
// the sun term and the high exposure pushed land into the tonemap's flat bright shoulder). Apply the
|
|
2040
|
+
// SPHERE sun angle to EVERY fragment (land + ocean) so the globe reads 3D: bright at the sub-solar
|
|
2041
|
+
// point, falling to dark across the day side into the terminator. dayShade = smoothstep over the
|
|
2042
|
+
// surface-normal . sun, with a small ambient floor (0.10) so the night/terminator isn't pure black.
|
|
2043
|
+
float macroMu = dot(normalize(vWorld), sunDir);
|
|
2044
|
+
// SOFT FLOORED TERMINATOR (Real-World Look): a wider, smoother twilight band (uTermWidth) with a
|
|
2045
|
+
// night floor (uNightFloor) so framed night longitudes keep faint detail instead of crushing to
|
|
2046
|
+
// black (defect #4). The old hard 0.10..-0.12,0.55 band was too steep + too dark.
|
|
2047
|
+
float dayShade = mix(uNightFloor, 1.0, smoothstep(-uTermWidth, uTermWidth, macroMu));
|
|
2048
|
+
// LIMB DARKENING: grazing-view fragments (the disc EDGE from orbit) darken, giving the globe its
|
|
2049
|
+
// rounded 3D form instead of a flat disc. viewGraze = surface-normal . view-dir, ->1 facing the
|
|
2050
|
+
// camera (disc centre), ->0 at the limb. Only bites near the limb so it does not dim the main face.
|
|
2051
|
+
vec3 viewDir = normalize(camWorld - vWorld);
|
|
2052
|
+
float viewGraze = max(dot(normalize(vWorld), viewDir), 0.0);
|
|
2053
|
+
float limb = 0.45 + 0.55 * smoothstep(0.0, 0.45, viewGraze); // 0.45 at the limb -> 1.0 facing camera
|
|
2054
|
+
// NIGHT / SHADOW FILL (user 2026-06-09: 'we want night lights in the SHADOW areas, not city lights'):
|
|
2055
|
+
// a UNIFORM dim fill that lifts the unlit hemisphere out of pure black -- a cool ambient night light,
|
|
2056
|
+
// no clusters, no warm dots, no land gate (it lifts every dark fragment, ocean + land). uNightLights
|
|
2057
|
+
// is the live intensity dial (0 = off, back to near-black night). The day-lit side (dayShade->1) is
|
|
2058
|
+
// unaffected because the fill is weighted by (1-dayShade). Raised from the old earthshine 0.012/0.018/
|
|
2059
|
+
// 0.032 (still read as black) to a clearly-visible dim blue so framed night terrain stays legible.
|
|
2060
|
+
vec3 nightFill = vec3(0.06, 0.075, 0.11) * uNightLights;
|
|
2061
|
+
vec3 color2 = (color * dayShade + nightFill * (1.0 - dayShade)) * limb;
|
|
2062
|
+
// HDR -> SDR: ACES tonemap. Exposure 1.7 -> 1.25 so the N.sun + dayShade gradient SPREADS across the
|
|
2063
|
+
// tonemap's linear range instead of clipping flat to the bright shoulder (the orbit-flatness root).
|
|
2064
|
+
vec3 c = color2 * uExposure; // exposure 1.25->uExposure(1.0): stop the bright ACES-shoulder wash
|
|
2065
|
+
vec3 mapped = clamp((c*(2.51*c+0.03))/(c*(2.43*c+0.59)+0.14), 0.0, 1.0);
|
|
2066
|
+
// POST-ACES LOOK (Real-World Look): restore saturation + contrast DELIBERATELY in display-linear so
|
|
2067
|
+
// the result is darker-but-saturated, not bright-but-pastel. Pull saturation up (uLookSat) + an
|
|
2068
|
+
// S-curve contrast about mid-grey (uLookContrast), then gamma. Live-tunable.
|
|
2069
|
+
float lum = dot(mapped, vec3(0.2126, 0.7152, 0.0722));
|
|
2070
|
+
mapped = mix(vec3(lum), mapped, uLookSat);
|
|
2071
|
+
mapped = clamp((mapped - 0.5) * uLookContrast + 0.5, 0.0, 1.0);
|
|
2072
|
+
fragColor = vec4(pow(mapped, vec3(1.0/2.2)), 1.0);
|
|
2073
|
+
}
|
|
2074
|
+
#endif
|
|
2075
|
+
|
|
2076
|
+
// ---- HEIGHT PROBE (collision): compute the EXACT rendered terrain height for one world dir,
|
|
2077
|
+
// reusing the same hpfSample + broadShapeM the mesh VS uses, so the free-fly collision floor
|
|
2078
|
+
// can never diverge from the rendered surface (user-chosen: read px from the GPU, not a CPU
|
|
2079
|
+
// mirror). Paired with a 1-point VS in proland-gl-render.js; writes height (metres) to R32F.
|
|
2080
|
+
#ifdef _PROBE_
|
|
2081
|
+
uniform vec3 probeDir; // world direction under the camera (normalized)
|
|
2082
|
+
out vec4 probeOut;
|
|
2083
|
+
void main(){
|
|
2084
|
+
vec3 dir0 = normalize(probeDir);
|
|
2085
|
+
// THC-Normal W1/W7: the collision height is now the SAME composeHeight() the geometry bake uses,
|
|
2086
|
+
// so sampleGroundM returns EXACTLY the rendered surface -- no parallel mirror to drift (was a
|
|
2087
|
+
// hand-copied carve sequence that OMITTED vtxDisplace = the camera-stops-short gap). Reconstruct
|
|
2088
|
+
// the face-local metres from the probe world dir (max-axis cube projection -> warped face metres)
|
|
2089
|
+
// so vtxDisplace samples the identical micro-relief field the VS does. tileM = a deck-leaf size so
|
|
2090
|
+
// the Nyquist fade keeps every octave the close-up geometry shows (collision == visible surface).
|
|
2091
|
+
int face; vec2 uv; hpfFaceUV(dir0, face, uv);
|
|
2092
|
+
// hpfFaceUV INVERTS the cube projection that built dir0, so (uv*2-1)*defRadius IS ALREADY the
|
|
2093
|
+
// warped face-local metres the mesh VS passes to composeHeight (terrain.glsl:667: faceWarp(...)).
|
|
2094
|
+
// The old line wrapped this in faceWarp() a SECOND time = a double-warp -> the probe sampled a
|
|
2095
|
+
// SHIFTED vtxDisplace noise-lattice position than the VS = a per-probe micro-relief height delta
|
|
2096
|
+
// (collision-elev-facelocal-parity, workflow wb4syopmo). Drop the extra warp: probe lattice == VS lattice.
|
|
2097
|
+
highp vec2 faceLocal = (uv * 2.0 - 1.0) * defRadius; // W7: warped face-local metres ~6.4e6 -> highp (matches VS:667)
|
|
2098
|
+
// tileM is INERT in vtxDisplace (terrain.glsl:495-501 has no tileM term; PURE per-patch-step fix) so
|
|
2099
|
+
// composeHeight is bit-identical for any tileM -> the hardcoded value below is NOT a divergence (verified).
|
|
2100
|
+
highp float h = composeHeight(dir0, faceLocal, 64.0); // W7: metres (tileM inert; collision == rendered surface)
|
|
2101
|
+
// RAW height out (2026-06-11): the old smoothstep(-60,60) flat-ocean clamp made the probe a
|
|
2102
|
+
// CLAMPED GAUGE -- sampleGroundM could never report real bathymetry (witnessed: a 2000-dir
|
|
2103
|
+
// sweep found 'no ocean below -100m' on a planet with km-deep basins), silently corrupting
|
|
2104
|
+
// every depth-based witness. Collision still floats on the water: BOTH movement consumers
|
|
2105
|
+
// clamp at use (planet.html Math.max(0, sampleGroundM(...)) at the move-step and ground-track
|
|
2106
|
+
// sites), which is where a USE-specific policy belongs -- the instrument now reports truth.
|
|
2107
|
+
probeOut = vec4(h, 0.0, 0.0, 1.0);
|
|
2108
|
+
}
|
|
2109
|
+
#endif
|