render3d: the ground's maps read through a page table of fine tiles over a coarse map (shaders)
terpage.glsl is the reference block (tpSlot, tpUV, terHeight, terHeightSmooth, terNormalXZ, terOrtho, tpOrthoRes, tpOrthoLod), pasted by section into terrain.vert, terrain.frag, tersun.frag, model.vert, grass.vert and grass.mesh. u_tp_on = 0 reads the old samplers with the old coordinates and filtering; on, a resident tile is read at level 0 from u_tp_h / u_tp_nrm / u_tp_ortho, anything else from the coarse map now bound under the old names. The B-splines use the FULL map's texel and take every tap through the page, so a tile edge stays one surface; blurred photograph reads (lod 1-2.5) stay on u_ortho with the level moved down by the coarse map's ratio. The fragment stages drop their unused u_height. SPIR-V rebuilt: terrain programs carry 23 samplers (21 in the fragment stage), up from 19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
329439cf61
commit
eee6a0906e
66 changed files with 837 additions and 267 deletions
|
|
@ -19,11 +19,91 @@ uniform float u_time;
|
|||
uniform sampler2D u_ts_height;
|
||||
// the baked terrain normal: x and z in RG16F, y rebuilt (a terrain normal always points up)
|
||||
uniform sampler2D u_ter_normal;
|
||||
vec3 terNormal(vec2 uv) { vec2 xz = textureLod(u_ter_normal, uv, 0.0).rg; return vec3(xz.x, sqrt(max(1.0 - dot(xz, xz), 0.0)), xz.y); }
|
||||
uniform vec2 u_ts_origin;
|
||||
uniform float u_ts_half;
|
||||
uniform sampler2D u_ortho;
|
||||
uniform float u_ortho_on;
|
||||
// terpage.glsl, pasted (the reference copy and its rules are there)
|
||||
// ---- the page table ----------------------------------------------------------------------
|
||||
#ifndef TP_HELPERS
|
||||
#define TP_HELPERS
|
||||
uniform sampler2D u_tp_page; // R32F, a texel a tile: slot + 1, 0 = not resident
|
||||
uniform float u_tp_on; // 1 while paging; 0 reads the whole maps exactly as before
|
||||
uniform vec4 u_tp_dims; // tiles a side, height texels a tile, photo texels a tile, full height res
|
||||
// the layer holding a full-map uv, or -1: paging off, off the map, or not resident
|
||||
float tpSlot(vec2 uv) {
|
||||
if (u_tp_on < 0.5 || uv.x < 0.0 || uv.y < 0.0 || uv.x >= 1.0 || uv.y >= 1.0) return -1.0;
|
||||
ivec2 t = min(ivec2(floor(uv * u_tp_dims.x)), ivec2(int(u_tp_dims.x) - 1));
|
||||
return texelFetch(u_tp_page, t, 0).r - 1.0;
|
||||
}
|
||||
// a full-map uv inside its tile's layer of k texels a side, past the one-texel border
|
||||
vec2 tpUV(vec2 uv, float k) { return (1.0 + fract(uv * u_tp_dims.x) * k) / (k + 2.0); }
|
||||
#endif
|
||||
// ---- height (vertex and mesh stages) -----------------------------------------------------
|
||||
#ifndef TP_HEIGHT
|
||||
#define TP_HEIGHT
|
||||
#ifndef TP_HMAP
|
||||
#define TP_HMAP u_ts_height
|
||||
#endif
|
||||
uniform sampler2DArray u_tp_h;
|
||||
// bilinear height, fine where a tile is resident. Outside a fragment stage texture() IS level 0
|
||||
// (glslang emits the same explicit-lod sample), so this is the old read bit for bit.
|
||||
float terHeight(vec2 uv) {
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_h, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).r;
|
||||
return textureLod(TP_HMAP, uv, 0.0).r;
|
||||
}
|
||||
// The B-spline through four bilinear taps (heightSmooth). Paging on, the texel is the FULL
|
||||
// map's - the coarse map's own size would halve the surface's detail - and each tap finds its
|
||||
// own tile, so a patch straddling two tiles, or a tile and the coarse map, stays one surface.
|
||||
float terHeightSmooth(vec2 uv) {
|
||||
vec2 res = u_tp_on > 0.5 ? vec2(u_tp_dims.w) : vec2(textureSize(TP_HMAP, 0));
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
vec2 w0 = (1.0 - f) * (1.0 - f) * (1.0 - f) / 6.0;
|
||||
vec2 w1 = (4.0 - 6.0 * f * f + 3.0 * f * f * f) / 6.0;
|
||||
vec2 w3 = f * f * f / 6.0;
|
||||
vec2 w2 = 1.0 - w0 - w1 - w3;
|
||||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res;
|
||||
vec2 o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (terHeight(vec2(o0.x, o0.y)) * s0.x + terHeight(vec2(o1.x, o0.y)) * s1.x) * s0.y
|
||||
+ (terHeight(vec2(o0.x, o1.y)) * s0.x + terHeight(vec2(o1.x, o1.y)) * s1.x) * s1.y;
|
||||
}
|
||||
#endif
|
||||
// ---- the baked normal (x, z; y is rebuilt by the caller) ----------------------------------
|
||||
#ifndef TP_NORMAL
|
||||
#define TP_NORMAL
|
||||
uniform sampler2DArray u_tp_nrm;
|
||||
vec2 terNormalXZ(vec2 uv) {
|
||||
#ifdef TP_FRAGMENT
|
||||
if (u_tp_on < 0.5) return texture(u_ter_normal, uv).rg;
|
||||
// taken before the per-pixel branch: a neighbour on another tile must not decide the level
|
||||
vec2 dx = dFdx(uv), dy = dFdy(uv);
|
||||
#endif
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_nrm, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).rg;
|
||||
#ifdef TP_FRAGMENT
|
||||
return textureGrad(u_ter_normal, uv, dx, dy).rg;
|
||||
#else
|
||||
return textureLod(u_ter_normal, uv, 0.0).rg;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// ---- the photograph's measures (declares no sampler: a stage reading u_ortho blurred needs no tile)
|
||||
#ifndef TP_ORTHO_LOD
|
||||
#define TP_ORTHO_LOD
|
||||
// the FULL photograph's texels a side, for filters that weigh by the texel (orthoSmooth)
|
||||
vec2 tpOrthoRes() { return u_tp_on > 0.5 ? vec2(u_tp_dims.z * u_tp_dims.x) : vec2(textureSize(u_ortho, 0)); }
|
||||
// A blurred read (textureLod 1..2.5) names a level of the FULL photograph; on the coarse map
|
||||
// the same detail is that many levels lower, so the ground's far colour does not soften.
|
||||
float tpOrthoLod(float lod) {
|
||||
if (u_tp_on < 0.5) return lod;
|
||||
return max(lod - log2(u_tp_dims.z * u_tp_dims.x / float(textureSize(u_ortho, 0).x)), 0.0);
|
||||
}
|
||||
#endif
|
||||
vec3 terNormal(vec2 uv) { vec2 xz = terNormalXZ(uv); return vec3(xz.x, sqrt(max(1.0 - dot(xz, xz), 0.0)), xz.y); }
|
||||
uniform float u_lake_level;
|
||||
uniform float u_sea_level;
|
||||
uniform vec4 u_lake;
|
||||
|
|
@ -51,22 +131,6 @@ float bladeHash(ivec2 cell, int j, int k) {
|
|||
uint h = pcg(uint(cell.x + 32768) * 73856093u ^ uint(cell.y + 32768) * 19349663u ^ uint(j) * 83492791u ^ uint(k) * 2654435761u);
|
||||
return float(h) * (1.0 / 4294967295.0);
|
||||
}
|
||||
// no implicit level of detail outside a fragment shader: every lookup names level 0
|
||||
float heightSmooth(sampler2D tex, vec2 uv) {
|
||||
vec2 res = vec2(textureSize(tex, 0));
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
vec2 w0 = (1.0 - f) * (1.0 - f) * (1.0 - f) / 6.0;
|
||||
vec2 w1 = (4.0 - 6.0 * f * f + 3.0 * f * f * f) / 6.0;
|
||||
vec2 w3 = f * f * f / 6.0;
|
||||
vec2 w2 = 1.0 - w0 - w1 - w3;
|
||||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res;
|
||||
vec2 o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (textureLod(tex, vec2(o0.x, o0.y), 0.0).r * s0.x + textureLod(tex, vec2(o1.x, o0.y), 0.0).r * s1.x) * s0.y
|
||||
+ (textureLod(tex, vec2(o0.x, o1.y), 0.0).r * s0.x + textureLod(tex, vec2(o1.x, o1.y), 0.0).r * s1.x) * s1.y;
|
||||
}
|
||||
|
||||
void main() {
|
||||
int ti = int(gl_WorkGroupID.y);
|
||||
|
|
@ -95,7 +159,7 @@ void main() {
|
|||
float life = 1.0 - smoothstep(0.8, 1.0, fj / max(count, 1.0));
|
||||
vec2 huv = (xz - u_ts_origin) / (2.0 * u_ts_half) + 0.5;
|
||||
if (huv.x < 0.0 || huv.x > 1.0 || huv.y < 0.0 || huv.y > 1.0) continue;
|
||||
vec4 ht = textureLod(u_ts_height, huv, 0.0);
|
||||
vec4 ht = vec4(terHeight(huv)); // only .r is read
|
||||
vec4 croot = u_vp * vec4(xz.x, ht.r, xz.y, 1.0);
|
||||
if (croot.w < -1.0 || abs(croot.x) > croot.w * 1.25 + 1.5 || abs(croot.y) > croot.w * 1.4 + 1.5) continue;
|
||||
vec3 gn = terNormal(huv);
|
||||
|
|
@ -104,12 +168,12 @@ void main() {
|
|||
if (u_lake.z > 0.0) { vec2 q = (xz - u_lake.xy) / u_lake.zw; if (dot(q, q) < 1.0) wl = max(wl, u_lake_level); }
|
||||
float ok = (1.0 - smoothstep(0.30, 0.55, 1.0 - gn.y)) * smoothstep(0.0, 0.6, ht.r - wl - 0.15) * smoothstep(u_snow_line - 80.0, u_snow_line - 200.0, ht.r);
|
||||
if (u_ortho_on > 0.5) {
|
||||
vec3 oc = textureLod(u_ortho, huv, 1.5).rgb;
|
||||
vec3 oc = textureLod(u_ortho, huv, tpOrthoLod(1.5)).rgb;
|
||||
ok *= 0.25 + 0.75 * smoothstep(0.0, 0.02, oc.g - max(oc.r, oc.b));
|
||||
}
|
||||
if (h4 > ok) continue;
|
||||
bool far = dist > 300.0;
|
||||
float h = far ? ht.r : heightSmooth(u_ts_height, huv);
|
||||
float h = far ? ht.r : terHeightSmooth(huv);
|
||||
if (far) h += 0.03;
|
||||
if ((u_dbg & 1) != 0) h += 0.3;
|
||||
float seed = hv.x * 0.7 + hv.y * 0.3;
|
||||
|
|
|
|||
|
|
@ -17,11 +17,91 @@ uniform float u_time;
|
|||
uniform sampler2D u_ts_height;
|
||||
// the baked terrain normal: x and z in RG16F, y rebuilt (a terrain normal always points up)
|
||||
uniform sampler2D u_ter_normal;
|
||||
vec3 terNormal(vec2 uv) { vec2 xz = textureLod(u_ter_normal, uv, 0.0).rg; return vec3(xz.x, sqrt(max(1.0 - dot(xz, xz), 0.0)), xz.y); }
|
||||
uniform vec2 u_ts_origin;
|
||||
uniform float u_ts_half;
|
||||
uniform sampler2D u_ortho;
|
||||
uniform float u_ortho_on;
|
||||
// terpage.glsl, pasted (the reference copy and its rules are there)
|
||||
// ---- the page table ----------------------------------------------------------------------
|
||||
#ifndef TP_HELPERS
|
||||
#define TP_HELPERS
|
||||
uniform sampler2D u_tp_page; // R32F, a texel a tile: slot + 1, 0 = not resident
|
||||
uniform float u_tp_on; // 1 while paging; 0 reads the whole maps exactly as before
|
||||
uniform vec4 u_tp_dims; // tiles a side, height texels a tile, photo texels a tile, full height res
|
||||
// the layer holding a full-map uv, or -1: paging off, off the map, or not resident
|
||||
float tpSlot(vec2 uv) {
|
||||
if (u_tp_on < 0.5 || uv.x < 0.0 || uv.y < 0.0 || uv.x >= 1.0 || uv.y >= 1.0) return -1.0;
|
||||
ivec2 t = min(ivec2(floor(uv * u_tp_dims.x)), ivec2(int(u_tp_dims.x) - 1));
|
||||
return texelFetch(u_tp_page, t, 0).r - 1.0;
|
||||
}
|
||||
// a full-map uv inside its tile's layer of k texels a side, past the one-texel border
|
||||
vec2 tpUV(vec2 uv, float k) { return (1.0 + fract(uv * u_tp_dims.x) * k) / (k + 2.0); }
|
||||
#endif
|
||||
// ---- height (vertex and mesh stages) -----------------------------------------------------
|
||||
#ifndef TP_HEIGHT
|
||||
#define TP_HEIGHT
|
||||
#ifndef TP_HMAP
|
||||
#define TP_HMAP u_ts_height
|
||||
#endif
|
||||
uniform sampler2DArray u_tp_h;
|
||||
// bilinear height, fine where a tile is resident. Outside a fragment stage texture() IS level 0
|
||||
// (glslang emits the same explicit-lod sample), so this is the old read bit for bit.
|
||||
float terHeight(vec2 uv) {
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_h, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).r;
|
||||
return textureLod(TP_HMAP, uv, 0.0).r;
|
||||
}
|
||||
// The B-spline through four bilinear taps (heightSmooth). Paging on, the texel is the FULL
|
||||
// map's - the coarse map's own size would halve the surface's detail - and each tap finds its
|
||||
// own tile, so a patch straddling two tiles, or a tile and the coarse map, stays one surface.
|
||||
float terHeightSmooth(vec2 uv) {
|
||||
vec2 res = u_tp_on > 0.5 ? vec2(u_tp_dims.w) : vec2(textureSize(TP_HMAP, 0));
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
vec2 w0 = (1.0 - f) * (1.0 - f) * (1.0 - f) / 6.0;
|
||||
vec2 w1 = (4.0 - 6.0 * f * f + 3.0 * f * f * f) / 6.0;
|
||||
vec2 w3 = f * f * f / 6.0;
|
||||
vec2 w2 = 1.0 - w0 - w1 - w3;
|
||||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res;
|
||||
vec2 o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (terHeight(vec2(o0.x, o0.y)) * s0.x + terHeight(vec2(o1.x, o0.y)) * s1.x) * s0.y
|
||||
+ (terHeight(vec2(o0.x, o1.y)) * s0.x + terHeight(vec2(o1.x, o1.y)) * s1.x) * s1.y;
|
||||
}
|
||||
#endif
|
||||
// ---- the baked normal (x, z; y is rebuilt by the caller) ----------------------------------
|
||||
#ifndef TP_NORMAL
|
||||
#define TP_NORMAL
|
||||
uniform sampler2DArray u_tp_nrm;
|
||||
vec2 terNormalXZ(vec2 uv) {
|
||||
#ifdef TP_FRAGMENT
|
||||
if (u_tp_on < 0.5) return texture(u_ter_normal, uv).rg;
|
||||
// taken before the per-pixel branch: a neighbour on another tile must not decide the level
|
||||
vec2 dx = dFdx(uv), dy = dFdy(uv);
|
||||
#endif
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_nrm, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).rg;
|
||||
#ifdef TP_FRAGMENT
|
||||
return textureGrad(u_ter_normal, uv, dx, dy).rg;
|
||||
#else
|
||||
return textureLod(u_ter_normal, uv, 0.0).rg;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// ---- the photograph's measures (declares no sampler: a stage reading u_ortho blurred needs no tile)
|
||||
#ifndef TP_ORTHO_LOD
|
||||
#define TP_ORTHO_LOD
|
||||
// the FULL photograph's texels a side, for filters that weigh by the texel (orthoSmooth)
|
||||
vec2 tpOrthoRes() { return u_tp_on > 0.5 ? vec2(u_tp_dims.z * u_tp_dims.x) : vec2(textureSize(u_ortho, 0)); }
|
||||
// A blurred read (textureLod 1..2.5) names a level of the FULL photograph; on the coarse map
|
||||
// the same detail is that many levels lower, so the ground's far colour does not soften.
|
||||
float tpOrthoLod(float lod) {
|
||||
if (u_tp_on < 0.5) return lod;
|
||||
return max(lod - log2(u_tp_dims.z * u_tp_dims.x / float(textureSize(u_ortho, 0).x)), 0.0);
|
||||
}
|
||||
#endif
|
||||
vec3 terNormal(vec2 uv) { vec2 xz = terNormalXZ(uv); return vec3(xz.x, sqrt(max(1.0 - dot(xz, xz), 0.0)), xz.y); }
|
||||
uniform float u_lake_level;
|
||||
uniform float u_sea_level; // the sea (terrain_sea); the lake's level when there is no separate sea
|
||||
uniform vec4 u_lake; // the carved lake: centre x/z, half extents (z = 0: none)
|
||||
|
|
@ -58,21 +138,6 @@ float bladeHash(ivec2 cell, int j, int k) {
|
|||
uint h = pcg(uint(cell.x + 32768) * 73856093u ^ uint(cell.y + 32768) * 19349663u ^ uint(j) * 83492791u ^ uint(k) * 2654435761u);
|
||||
return float(h) * (1.0 / 4294967295.0);
|
||||
}
|
||||
float heightSmooth(sampler2D tex, vec2 uv) {
|
||||
vec2 res = vec2(textureSize(tex, 0));
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
vec2 w0 = (1.0 - f) * (1.0 - f) * (1.0 - f) / 6.0;
|
||||
vec2 w1 = (4.0 - 6.0 * f * f + 3.0 * f * f * f) / 6.0;
|
||||
vec2 w3 = f * f * f / 6.0;
|
||||
vec2 w2 = 1.0 - w0 - w1 - w3;
|
||||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res;
|
||||
vec2 o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (texture(tex, vec2(o0.x, o0.y)).r * s0.x + texture(tex, vec2(o1.x, o0.y)).r * s1.x) * s0.y
|
||||
+ (texture(tex, vec2(o0.x, o1.y)).r * s0.x + texture(tex, vec2(o1.x, o1.y)).r * s1.x) * s1.y;
|
||||
}
|
||||
void cull() { gl_Position = vec4(0.0, 0.0, 2.0, 1.0); v_wpos = vec3(0.0); v_nrm = vec3(0.0, 1.0, 0.0); v_uv = vec2(0.0); v_seed = 0.0; v_rot = vec2(0.0, 1.0); v_hull = 1.0; v_quake = 0.0; v_tint = vec3(1.0); }
|
||||
// lighting.glsl's regionTint(wpos, 0.35) times model.frag's patchiness, at the root: three fbm
|
||||
// fields a blade pixel used to pay for, when the whole blade stands on one spot
|
||||
|
|
@ -133,7 +198,7 @@ void main() {
|
|||
// the ground under it
|
||||
vec2 huv = (xz - u_ts_origin) / (2.0 * u_ts_half) + 0.5;
|
||||
if (huv.x < 0.0 || huv.x > 1.0 || huv.y < 0.0 || huv.y > 1.0) { cull(); return; }
|
||||
vec4 ht = texture(u_ts_height, huv);
|
||||
vec4 ht = vec4(terHeight(huv)); // only .r is read
|
||||
vec4 croot = u_vp * vec4(xz.x, ht.r, xz.y, 1.0);
|
||||
if (croot.w < -1.0 || abs(croot.x) > croot.w * 1.25 + 1.5 || abs(croot.y) > croot.w * 1.4 + 1.5) { cull(); return; }
|
||||
vec3 gn = terNormal(huv);
|
||||
|
|
@ -144,7 +209,7 @@ void main() {
|
|||
if (u_lake.z > 0.0) { vec2 q = (xz - u_lake.xy) / u_lake.zw; if (dot(q, q) < 1.0) wl = max(wl, u_lake_level); }
|
||||
float ok = (1.0 - smoothstep(0.30, 0.55, 1.0 - gn.y)) * smoothstep(0.0, 0.6, ht.r - wl - 0.15) * smoothstep(u_snow_line - 80.0, u_snow_line - 200.0, ht.r);
|
||||
if (u_ortho_on > 0.5) {
|
||||
vec3 oc = textureLod(u_ortho, huv, 1.5).rgb;
|
||||
vec3 oc = textureLod(u_ortho, huv, tpOrthoLod(1.5)).rgb;
|
||||
// Is this ground vegetated, by the photograph? The test used to be green DOMINANCE -
|
||||
// g - max(r, b) - which is a test for lush green and nothing else. A dry alpine meadow
|
||||
// is yellow-green: its red is as high as its green, so the whole meadow scored zero and
|
||||
|
|
@ -159,7 +224,7 @@ void main() {
|
|||
// the root on the drawn surface: the CDLOD mesh follows the B-spline to within
|
||||
// centimetres near the camera, so the smooth sample is the drawn height
|
||||
bool far = dist > 300.0;
|
||||
float h = far ? ht.r : heightSmooth(u_ts_height, huv);
|
||||
float h = far ? ht.r : terHeightSmooth(huv);
|
||||
if (far) h += 0.03;
|
||||
if ((u_dbg & 1) != 0) h += 0.3;
|
||||
// the blade: sized so that coverage stays level as the spacing grows
|
||||
|
|
|
|||
|
|
@ -24,8 +24,41 @@ uniform float u_ground;
|
|||
uniform sampler2D u_ts_height;
|
||||
uniform vec2 u_ts_origin;
|
||||
uniform float u_ts_half;
|
||||
float heightSmooth(sampler2D tex, vec2 uv) {
|
||||
vec2 res = vec2(textureSize(tex, 0));
|
||||
// terpage.glsl, pasted (the reference copy and its rules are there)
|
||||
// ---- the page table ----------------------------------------------------------------------
|
||||
#ifndef TP_HELPERS
|
||||
#define TP_HELPERS
|
||||
uniform sampler2D u_tp_page; // R32F, a texel a tile: slot + 1, 0 = not resident
|
||||
uniform float u_tp_on; // 1 while paging; 0 reads the whole maps exactly as before
|
||||
uniform vec4 u_tp_dims; // tiles a side, height texels a tile, photo texels a tile, full height res
|
||||
// the layer holding a full-map uv, or -1: paging off, off the map, or not resident
|
||||
float tpSlot(vec2 uv) {
|
||||
if (u_tp_on < 0.5 || uv.x < 0.0 || uv.y < 0.0 || uv.x >= 1.0 || uv.y >= 1.0) return -1.0;
|
||||
ivec2 t = min(ivec2(floor(uv * u_tp_dims.x)), ivec2(int(u_tp_dims.x) - 1));
|
||||
return texelFetch(u_tp_page, t, 0).r - 1.0;
|
||||
}
|
||||
// a full-map uv inside its tile's layer of k texels a side, past the one-texel border
|
||||
vec2 tpUV(vec2 uv, float k) { return (1.0 + fract(uv * u_tp_dims.x) * k) / (k + 2.0); }
|
||||
#endif
|
||||
// ---- height (vertex and mesh stages) -----------------------------------------------------
|
||||
#ifndef TP_HEIGHT
|
||||
#define TP_HEIGHT
|
||||
#ifndef TP_HMAP
|
||||
#define TP_HMAP u_ts_height
|
||||
#endif
|
||||
uniform sampler2DArray u_tp_h;
|
||||
// bilinear height, fine where a tile is resident. Outside a fragment stage texture() IS level 0
|
||||
// (glslang emits the same explicit-lod sample), so this is the old read bit for bit.
|
||||
float terHeight(vec2 uv) {
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_h, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).r;
|
||||
return textureLod(TP_HMAP, uv, 0.0).r;
|
||||
}
|
||||
// The B-spline through four bilinear taps (heightSmooth). Paging on, the texel is the FULL
|
||||
// map's - the coarse map's own size would halve the surface's detail - and each tap finds its
|
||||
// own tile, so a patch straddling two tiles, or a tile and the coarse map, stays one surface.
|
||||
float terHeightSmooth(vec2 uv) {
|
||||
vec2 res = u_tp_on > 0.5 ? vec2(u_tp_dims.w) : vec2(textureSize(TP_HMAP, 0));
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
|
|
@ -36,9 +69,10 @@ float heightSmooth(sampler2D tex, vec2 uv) {
|
|||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res;
|
||||
vec2 o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (texture(tex, vec2(o0.x, o0.y)).r * s0.x + texture(tex, vec2(o1.x, o0.y)).r * s1.x) * s0.y
|
||||
+ (texture(tex, vec2(o0.x, o1.y)).r * s0.x + texture(tex, vec2(o1.x, o1.y)).r * s1.x) * s1.y;
|
||||
return (terHeight(vec2(o0.x, o0.y)) * s0.x + terHeight(vec2(o1.x, o0.y)) * s1.x) * s0.y
|
||||
+ (terHeight(vec2(o0.x, o1.y)) * s0.x + terHeight(vec2(o1.x, o1.y)) * s1.x) * s1.y;
|
||||
}
|
||||
#endif
|
||||
out vec3 v_wpos;
|
||||
// The foliage prepass (depth.frag) and the lit pass after it compile this same source
|
||||
// into two programs and compare depths for equality: the position must come out
|
||||
|
|
@ -149,7 +183,7 @@ void main() {
|
|||
vec3 w = p + i_pos.xyz;
|
||||
if (u_ground > 0.5) {
|
||||
vec2 huv = (i_pos.xz - u_ts_origin) / (2.0 * u_ts_half) + 0.5;
|
||||
w.y = heightSmooth(u_ts_height, huv) - 0.03 + p.y;
|
||||
w.y = terHeightSmooth(huv) - 0.03 + p.y;
|
||||
}
|
||||
v_wpos = w;
|
||||
v_nrm = n;
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
121
packages/ludic.render3d/shaders/terpage.glsl
Normal file
121
packages/ludic.render3d/shaders/terpage.glsl
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// terpage.glsl - the terrain maps read through a page table of fine tiles, over a coarse map.
|
||||
//
|
||||
// THE REFERENCE COPY. Nothing prepends this file: a stage is assembled from its variant's
|
||||
// defines, wind.glsl, and for a fragment stage noise.glsl and lighting.glsl, and there is no
|
||||
// #include - so each shader that reads the ground carries the sections it needs, pasted from
|
||||
// here, and a change here is a change pasted into every copy (grep TP_HELPERS). Each section
|
||||
// has its own guard, so a stage that ends up with two copies still compiles, and a stage only
|
||||
// declares the arrays it reads: glslang keeps every declared sampler in the program's layout.
|
||||
//
|
||||
// Before a paste: TP_FRAGMENT in a fragment stage (its fallbacks keep their implicit level of
|
||||
// detail), and TP_HMAP when the stage's height sampler is not u_ts_height (terrain's u_height).
|
||||
// The ground normal needs u_ter_normal declared first, the photograph u_ortho.
|
||||
//
|
||||
// The contract with the runtime: u_tp_page is n x n R32F, slot + 1 for a
|
||||
// resident tile and 0 for none; each array layer is a tile with a one-texel border taken from
|
||||
// its neighbours, so linear filtering and the B-spline's taps cross a tile edge seamlessly.
|
||||
// While paging is on, u_ts_height / u_height, u_ter_normal and u_ortho hold a COARSE whole-map
|
||||
// level; while it is off they hold today's full maps and every helper here is today's read.
|
||||
|
||||
// ---- the page table ----------------------------------------------------------------------
|
||||
#ifndef TP_HELPERS
|
||||
#define TP_HELPERS
|
||||
uniform sampler2D u_tp_page; // R32F, a texel a tile: slot + 1, 0 = not resident
|
||||
uniform float u_tp_on; // 1 while paging; 0 reads the whole maps exactly as before
|
||||
uniform vec4 u_tp_dims; // tiles a side, height texels a tile, photo texels a tile, full height res
|
||||
// the layer holding a full-map uv, or -1: paging off, off the map, or not resident
|
||||
float tpSlot(vec2 uv) {
|
||||
if (u_tp_on < 0.5 || uv.x < 0.0 || uv.y < 0.0 || uv.x >= 1.0 || uv.y >= 1.0) return -1.0;
|
||||
ivec2 t = min(ivec2(floor(uv * u_tp_dims.x)), ivec2(int(u_tp_dims.x) - 1));
|
||||
return texelFetch(u_tp_page, t, 0).r - 1.0;
|
||||
}
|
||||
// a full-map uv inside its tile's layer of k texels a side, past the one-texel border
|
||||
vec2 tpUV(vec2 uv, float k) { return (1.0 + fract(uv * u_tp_dims.x) * k) / (k + 2.0); }
|
||||
#endif
|
||||
|
||||
// ---- height (vertex and mesh stages) -----------------------------------------------------
|
||||
#ifndef TP_HEIGHT
|
||||
#define TP_HEIGHT
|
||||
#ifndef TP_HMAP
|
||||
#define TP_HMAP u_ts_height
|
||||
#endif
|
||||
uniform sampler2DArray u_tp_h;
|
||||
// bilinear height, fine where a tile is resident. Outside a fragment stage texture() IS level 0
|
||||
// (glslang emits the same explicit-lod sample), so this is the old read bit for bit.
|
||||
float terHeight(vec2 uv) {
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_h, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).r;
|
||||
return textureLod(TP_HMAP, uv, 0.0).r;
|
||||
}
|
||||
// The B-spline through four bilinear taps (heightSmooth). Paging on, the texel is the FULL
|
||||
// map's - the coarse map's own size would halve the surface's detail - and each tap finds its
|
||||
// own tile, so a patch straddling two tiles, or a tile and the coarse map, stays one surface.
|
||||
float terHeightSmooth(vec2 uv) {
|
||||
vec2 res = u_tp_on > 0.5 ? vec2(u_tp_dims.w) : vec2(textureSize(TP_HMAP, 0));
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
vec2 w0 = (1.0 - f) * (1.0 - f) * (1.0 - f) / 6.0;
|
||||
vec2 w1 = (4.0 - 6.0 * f * f + 3.0 * f * f * f) / 6.0;
|
||||
vec2 w3 = f * f * f / 6.0;
|
||||
vec2 w2 = 1.0 - w0 - w1 - w3;
|
||||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res;
|
||||
vec2 o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (terHeight(vec2(o0.x, o0.y)) * s0.x + terHeight(vec2(o1.x, o0.y)) * s1.x) * s0.y
|
||||
+ (terHeight(vec2(o0.x, o1.y)) * s0.x + terHeight(vec2(o1.x, o1.y)) * s1.x) * s1.y;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---- the baked normal (x, z; y is rebuilt by the caller) ----------------------------------
|
||||
#ifndef TP_NORMAL
|
||||
#define TP_NORMAL
|
||||
uniform sampler2DArray u_tp_nrm;
|
||||
vec2 terNormalXZ(vec2 uv) {
|
||||
#ifdef TP_FRAGMENT
|
||||
if (u_tp_on < 0.5) return texture(u_ter_normal, uv).rg;
|
||||
// taken before the per-pixel branch: a neighbour on another tile must not decide the level
|
||||
vec2 dx = dFdx(uv), dy = dFdy(uv);
|
||||
#endif
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_nrm, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).rg;
|
||||
#ifdef TP_FRAGMENT
|
||||
return textureGrad(u_ter_normal, uv, dx, dy).rg;
|
||||
#else
|
||||
return textureLod(u_ter_normal, uv, 0.0).rg;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---- the photograph ----------------------------------------------------------------------
|
||||
#ifndef TP_ORTHO
|
||||
#define TP_ORTHO
|
||||
uniform sampler2DArray u_tp_ortho;
|
||||
// level-0 colour, fine where a tile is resident; the coarse map (with its mips) elsewhere
|
||||
vec3 terOrtho(vec2 uv) {
|
||||
#ifdef TP_FRAGMENT
|
||||
if (u_tp_on < 0.5) return texture(u_ortho, uv).rgb;
|
||||
vec2 dx = dFdx(uv), dy = dFdy(uv);
|
||||
#endif
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_ortho, vec3(tpUV(uv, u_tp_dims.z), s), 0.0).rgb;
|
||||
#ifdef TP_FRAGMENT
|
||||
return textureGrad(u_ortho, uv, dx, dy).rgb;
|
||||
#else
|
||||
return textureLod(u_ortho, uv, 0.0).rgb;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---- the photograph's measures (declares no sampler: a stage reading u_ortho blurred needs no tile)
|
||||
#ifndef TP_ORTHO_LOD
|
||||
#define TP_ORTHO_LOD
|
||||
// the FULL photograph's texels a side, for filters that weigh by the texel (orthoSmooth)
|
||||
vec2 tpOrthoRes() { return u_tp_on > 0.5 ? vec2(u_tp_dims.z * u_tp_dims.x) : vec2(textureSize(u_ortho, 0)); }
|
||||
// A blurred read (textureLod 1..2.5) names a level of the FULL photograph; on the coarse map
|
||||
// the same detail is that many levels lower, so the ground's far colour does not soften.
|
||||
float tpOrthoLod(float lod) {
|
||||
if (u_tp_on < 0.5) return lod;
|
||||
return max(lod - log2(u_tp_dims.z * u_tp_dims.x / float(textureSize(u_ortho, 0).x)), 0.0);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -6,16 +6,84 @@ out vec4 o_color;
|
|||
#define ridged(p, o) 0.3
|
||||
#define gnoise(p) 0.1
|
||||
#endif
|
||||
uniform sampler2D u_height;
|
||||
// no u_height here: the vertex stage reads the ground, and every declared sampler counts
|
||||
// against the stage's limit (MoltenVK) whether it is read or not
|
||||
// the baked terrain normal: x and z in RG16F, y rebuilt (a terrain normal always points up)
|
||||
uniform sampler2D u_ter_normal;
|
||||
vec3 terNormal(vec2 uv) { vec2 xz = texture(u_ter_normal, uv).rg; return vec3(xz.x, sqrt(max(1.0 - dot(xz, xz), 0.0)), xz.y); }
|
||||
uniform float u_half;
|
||||
uniform float u_texel; // height-map texel size in uv
|
||||
uniform sampler2D u_grass_d; uniform sampler2D u_grass_n; uniform sampler2D u_grass_a;
|
||||
uniform sampler2D u_ortho;
|
||||
uniform sampler2D u_sunshadow; // the sun visibility this pixel already has (tersun.frag)
|
||||
uniform float u_ortho_on;
|
||||
#define TP_FRAGMENT
|
||||
// terpage.glsl, pasted (the reference copy and its rules are there)
|
||||
// ---- the page table ----------------------------------------------------------------------
|
||||
#ifndef TP_HELPERS
|
||||
#define TP_HELPERS
|
||||
uniform sampler2D u_tp_page; // R32F, a texel a tile: slot + 1, 0 = not resident
|
||||
uniform float u_tp_on; // 1 while paging; 0 reads the whole maps exactly as before
|
||||
uniform vec4 u_tp_dims; // tiles a side, height texels a tile, photo texels a tile, full height res
|
||||
// the layer holding a full-map uv, or -1: paging off, off the map, or not resident
|
||||
float tpSlot(vec2 uv) {
|
||||
if (u_tp_on < 0.5 || uv.x < 0.0 || uv.y < 0.0 || uv.x >= 1.0 || uv.y >= 1.0) return -1.0;
|
||||
ivec2 t = min(ivec2(floor(uv * u_tp_dims.x)), ivec2(int(u_tp_dims.x) - 1));
|
||||
return texelFetch(u_tp_page, t, 0).r - 1.0;
|
||||
}
|
||||
// a full-map uv inside its tile's layer of k texels a side, past the one-texel border
|
||||
vec2 tpUV(vec2 uv, float k) { return (1.0 + fract(uv * u_tp_dims.x) * k) / (k + 2.0); }
|
||||
#endif
|
||||
// ---- the baked normal (x, z; y is rebuilt by the caller) ----------------------------------
|
||||
#ifndef TP_NORMAL
|
||||
#define TP_NORMAL
|
||||
uniform sampler2DArray u_tp_nrm;
|
||||
vec2 terNormalXZ(vec2 uv) {
|
||||
#ifdef TP_FRAGMENT
|
||||
if (u_tp_on < 0.5) return texture(u_ter_normal, uv).rg;
|
||||
// taken before the per-pixel branch: a neighbour on another tile must not decide the level
|
||||
vec2 dx = dFdx(uv), dy = dFdy(uv);
|
||||
#endif
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_nrm, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).rg;
|
||||
#ifdef TP_FRAGMENT
|
||||
return textureGrad(u_ter_normal, uv, dx, dy).rg;
|
||||
#else
|
||||
return textureLod(u_ter_normal, uv, 0.0).rg;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// ---- the photograph ----------------------------------------------------------------------
|
||||
#ifndef TP_ORTHO
|
||||
#define TP_ORTHO
|
||||
uniform sampler2DArray u_tp_ortho;
|
||||
// level-0 colour, fine where a tile is resident; the coarse map (with its mips) elsewhere
|
||||
vec3 terOrtho(vec2 uv) {
|
||||
#ifdef TP_FRAGMENT
|
||||
if (u_tp_on < 0.5) return texture(u_ortho, uv).rgb;
|
||||
vec2 dx = dFdx(uv), dy = dFdy(uv);
|
||||
#endif
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_ortho, vec3(tpUV(uv, u_tp_dims.z), s), 0.0).rgb;
|
||||
#ifdef TP_FRAGMENT
|
||||
return textureGrad(u_ortho, uv, dx, dy).rgb;
|
||||
#else
|
||||
return textureLod(u_ortho, uv, 0.0).rgb;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// ---- the photograph's measures (declares no sampler: a stage reading u_ortho blurred needs no tile)
|
||||
#ifndef TP_ORTHO_LOD
|
||||
#define TP_ORTHO_LOD
|
||||
// the FULL photograph's texels a side, for filters that weigh by the texel (orthoSmooth)
|
||||
vec2 tpOrthoRes() { return u_tp_on > 0.5 ? vec2(u_tp_dims.z * u_tp_dims.x) : vec2(textureSize(u_ortho, 0)); }
|
||||
// A blurred read (textureLod 1..2.5) names a level of the FULL photograph; on the coarse map
|
||||
// the same detail is that many levels lower, so the ground's far colour does not soften.
|
||||
float tpOrthoLod(float lod) {
|
||||
if (u_tp_on < 0.5) return lod;
|
||||
return max(lod - log2(u_tp_dims.z * u_tp_dims.x / float(textureSize(u_ortho, 0).x)), 0.0);
|
||||
}
|
||||
#endif
|
||||
vec3 terNormal(vec2 uv) { vec2 xz = terNormalXZ(uv); return vec3(xz.x, sqrt(max(1.0 - dot(xz, xz), 0.0)), xz.y); }
|
||||
uniform sampler2D u_rock_d; uniform sampler2D u_rock_n; uniform sampler2D u_rock_a;
|
||||
uniform sampler2D u_snow_d; uniform sampler2D u_carpet; // the clump cards baked straight down (alpha = coverage)
|
||||
uniform float u_carpet_on;
|
||||
|
|
@ -44,7 +112,7 @@ uniform vec2 u_origin; // world offset of the terrain grid
|
|||
// applied inside sunShadow() for every receiver in the scene.
|
||||
// bicubic (B-spline) sample through four bilinear taps: the 10 m photo pixels stop reading as squares
|
||||
vec3 orthoSmooth(vec2 uv) {
|
||||
vec2 res = vec2(textureSize(u_ortho, 0));
|
||||
vec2 res = tpOrthoRes(); // the full photograph's texel, whichever map is bound
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
|
|
@ -52,8 +120,8 @@ vec3 orthoSmooth(vec2 uv) {
|
|||
vec2 w3 = f * f * f / 6.0, w2 = 1.0 - w0 - w1 - w3;
|
||||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res, o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (texture(u_ortho, vec2(o0.x, o0.y)).rgb * s0.x + texture(u_ortho, vec2(o1.x, o0.y)).rgb * s1.x) * s0.y
|
||||
+ (texture(u_ortho, vec2(o0.x, o1.y)).rgb * s0.x + texture(u_ortho, vec2(o1.x, o1.y)).rgb * s1.x) * s1.y;
|
||||
return (terOrtho(vec2(o0.x, o0.y)) * s0.x + terOrtho(vec2(o1.x, o0.y)) * s1.x) * s0.y
|
||||
+ (terOrtho(vec2(o0.x, o1.y)) * s0.x + terOrtho(vec2(o1.x, o1.y)) * s1.x) * s1.y;
|
||||
}
|
||||
uniform mat4 u_view;
|
||||
|
||||
|
|
@ -210,7 +278,7 @@ vec3 dbg_n; vec3 dbg_alb; float dbg_shadow; vec3 dbg_mat;
|
|||
float fbmC(bool cheap, vec2 q, int o) { return cheap ? 0.0 : fbm(q, o); }
|
||||
float ridgedC(bool cheap, vec2 q, int o) { return cheap ? 0.35 : ridged(q, o); }
|
||||
float gnoiseC(bool cheap, vec2 q) { return cheap ? 0.0 : gnoise(q); }
|
||||
vec3 orthoC(bool cheap, vec2 uv) { return cheap ? textureLod(u_ortho, uv, 1.0).rgb : orthoSmooth(uv); }
|
||||
vec3 orthoC(bool cheap, vec2 uv) { return cheap ? textureLod(u_ortho, uv, tpOrthoLod(1.0)).rgb : orthoSmooth(uv); }
|
||||
vec4 carpetC(bool cheap, vec2 uv, vec2 dx, vec2 dy) { return cheap ? textureGrad(u_carpet, uv, dx, dy) : sampleCarpet(uv, dx, dy); }
|
||||
void matC(bool cheap, sampler2D d, sampler2D nm, sampler2D am, vec2 uv, vec2 dx, vec2 dy, out vec3 alb, out vec3 nrm, out vec3 arm) {
|
||||
if (cheap) samplePlain(d, nm, am, uv, dx, dy, alb, nrm, arm); else sampleMat(d, nm, am, uv, dx, dy, alb, nrm, arm);
|
||||
|
|
@ -267,14 +335,14 @@ vec3 groundShade(vec3 p, vec3 N, float slope, float dist, float viewDepth, bool
|
|||
if (orthoOn) {
|
||||
oc = orthoC(cheap, v_huv);
|
||||
// (classified from a ~40 m blur: thresholding the raw 10 m pixels drew hard squares)
|
||||
vec3 ocf = textureLod(u_ortho, v_huv, 2.0).rgb;
|
||||
vec3 ocf = textureLod(u_ortho, v_huv, tpOrthoLod(2.0)).rgb;
|
||||
float gx = ocf.g - max(ocf.r, ocf.b);
|
||||
// (the photograph is sampled linear: sRGB 72 is 0.06, 92 is 0.11)
|
||||
forestW = smoothstep(0.12, 0.06, max(ocf.r, max(ocf.g, ocf.b))) * smoothstep(0.004, 0.012, gx) * smoothstep(waterLine(p.xz) + 0.8, waterLine(p.xz) + 1.8, p.y);
|
||||
// Classify from a ~60 m blur, never from the pixels: the survey's 10 m pixels carry
|
||||
// a foot trail as a broken line of bare ground, and thresholding them painted it
|
||||
// across the meadow as tan dashes (and, on the CPU, lined boulders up along it).
|
||||
vec3 ocl = textureLod(u_ortho, v_huv, 2.5).rgb;
|
||||
vec3 ocl = textureLod(u_ortho, v_huv, tpOrthoLod(2.5)).rgb;
|
||||
float mxc = max(ocl.r, max(ocl.g, ocl.b)), mnc = min(ocl.r, min(ocl.g, ocl.b));
|
||||
float greenEx = ocl.g - max(ocl.r, ocl.b);
|
||||
screeC = smoothstep(0.008, -0.002, greenEx) * smoothstep(0.06, 0.12, mxc) * (1.0 - smoothstep(0.55, 0.75, mxc)) * smoothstep(waterLine(p.xz) + 0.2, waterLine(p.xz) + 1.2, p.y);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,42 @@ uniform vec3 u_cam_pos;
|
|||
uniform vec3 u_node; // x0, z0, size (m)
|
||||
uniform vec2 u_morph; // distance where the morph starts, and where it is complete
|
||||
uniform float u_grid; // cells per patch side
|
||||
float heightSmooth(sampler2D tex, vec2 uv) {
|
||||
vec2 res = vec2(textureSize(tex, 0));
|
||||
#define TP_HMAP u_height
|
||||
// terpage.glsl, pasted (the reference copy and its rules are there)
|
||||
// ---- the page table ----------------------------------------------------------------------
|
||||
#ifndef TP_HELPERS
|
||||
#define TP_HELPERS
|
||||
uniform sampler2D u_tp_page; // R32F, a texel a tile: slot + 1, 0 = not resident
|
||||
uniform float u_tp_on; // 1 while paging; 0 reads the whole maps exactly as before
|
||||
uniform vec4 u_tp_dims; // tiles a side, height texels a tile, photo texels a tile, full height res
|
||||
// the layer holding a full-map uv, or -1: paging off, off the map, or not resident
|
||||
float tpSlot(vec2 uv) {
|
||||
if (u_tp_on < 0.5 || uv.x < 0.0 || uv.y < 0.0 || uv.x >= 1.0 || uv.y >= 1.0) return -1.0;
|
||||
ivec2 t = min(ivec2(floor(uv * u_tp_dims.x)), ivec2(int(u_tp_dims.x) - 1));
|
||||
return texelFetch(u_tp_page, t, 0).r - 1.0;
|
||||
}
|
||||
// a full-map uv inside its tile's layer of k texels a side, past the one-texel border
|
||||
vec2 tpUV(vec2 uv, float k) { return (1.0 + fract(uv * u_tp_dims.x) * k) / (k + 2.0); }
|
||||
#endif
|
||||
// ---- height (vertex and mesh stages) -----------------------------------------------------
|
||||
#ifndef TP_HEIGHT
|
||||
#define TP_HEIGHT
|
||||
#ifndef TP_HMAP
|
||||
#define TP_HMAP u_ts_height
|
||||
#endif
|
||||
uniform sampler2DArray u_tp_h;
|
||||
// bilinear height, fine where a tile is resident. Outside a fragment stage texture() IS level 0
|
||||
// (glslang emits the same explicit-lod sample), so this is the old read bit for bit.
|
||||
float terHeight(vec2 uv) {
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_h, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).r;
|
||||
return textureLod(TP_HMAP, uv, 0.0).r;
|
||||
}
|
||||
// The B-spline through four bilinear taps (heightSmooth). Paging on, the texel is the FULL
|
||||
// map's - the coarse map's own size would halve the surface's detail - and each tap finds its
|
||||
// own tile, so a patch straddling two tiles, or a tile and the coarse map, stays one surface.
|
||||
float terHeightSmooth(vec2 uv) {
|
||||
vec2 res = u_tp_on > 0.5 ? vec2(u_tp_dims.w) : vec2(textureSize(TP_HMAP, 0));
|
||||
vec2 t = uv * res - 0.5;
|
||||
vec2 f = fract(t);
|
||||
vec2 i = floor(t);
|
||||
|
|
@ -26,23 +60,24 @@ float heightSmooth(sampler2D tex, vec2 uv) {
|
|||
vec2 s0 = w0 + w1, s1 = w2 + w3;
|
||||
vec2 o0 = (i - 1.0 + w1 / s0 + 0.5) / res;
|
||||
vec2 o1 = (i + 1.0 + w3 / s1 + 0.5) / res;
|
||||
return (texture(tex, vec2(o0.x, o0.y)).r * s0.x + texture(tex, vec2(o1.x, o0.y)).r * s1.x) * s0.y
|
||||
+ (texture(tex, vec2(o0.x, o1.y)).r * s0.x + texture(tex, vec2(o1.x, o1.y)).r * s1.x) * s1.y;
|
||||
return (terHeight(vec2(o0.x, o0.y)) * s0.x + terHeight(vec2(o1.x, o0.y)) * s1.x) * s0.y
|
||||
+ (terHeight(vec2(o0.x, o1.y)) * s0.x + terHeight(vec2(o1.x, o1.y)) * s1.x) * s1.y;
|
||||
}
|
||||
#endif
|
||||
out vec3 v_wpos;
|
||||
out vec2 v_huv;
|
||||
void main() {
|
||||
vec2 grid = a_xz * u_grid;
|
||||
vec2 xz = u_node.xy + a_xz * u_node.z;
|
||||
vec2 huv = (xz - u_origin) / (2.0 * u_half) + 0.5;
|
||||
float h0 = texture(u_height, huv).r;
|
||||
float h0 = terHeight(huv);
|
||||
float d = distance(vec3(xz.x, h0, xz.y), u_cam_pos);
|
||||
float k = clamp((d - u_morph.x) / max(u_morph.y - u_morph.x, 1.0), 0.0, 1.0);
|
||||
vec2 frac2 = fract(grid * 0.5) * 2.0; // 1 on odd vertices
|
||||
grid -= frac2 * k;
|
||||
xz = u_node.xy + grid / u_grid * u_node.z;
|
||||
huv = (xz - u_origin) / (2.0 * u_half) + 0.5;
|
||||
float h = heightSmooth(u_height, huv);
|
||||
float h = terHeightSmooth(huv);
|
||||
vec3 p = vec3(xz.x, h, xz.y);
|
||||
v_wpos = p;
|
||||
v_huv = huv;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,47 @@
|
|||
in vec3 v_wpos;
|
||||
in vec2 v_huv;
|
||||
out float o_sh;
|
||||
uniform sampler2D u_height;
|
||||
// no u_height here: the vertex stage reads the ground, and every declared sampler counts
|
||||
// against the stage's limit (MoltenVK) whether it is read or not
|
||||
// the baked terrain normal: x and z in RG16F, y rebuilt (a terrain normal always points up)
|
||||
uniform sampler2D u_ter_normal;
|
||||
vec3 terNormal(vec2 uv) { vec2 xz = texture(u_ter_normal, uv).rg; return vec3(xz.x, sqrt(max(1.0 - dot(xz, xz), 0.0)), xz.y); }
|
||||
#define TP_FRAGMENT
|
||||
// terpage.glsl, pasted (the reference copy and its rules are there)
|
||||
// ---- the page table ----------------------------------------------------------------------
|
||||
#ifndef TP_HELPERS
|
||||
#define TP_HELPERS
|
||||
uniform sampler2D u_tp_page; // R32F, a texel a tile: slot + 1, 0 = not resident
|
||||
uniform float u_tp_on; // 1 while paging; 0 reads the whole maps exactly as before
|
||||
uniform vec4 u_tp_dims; // tiles a side, height texels a tile, photo texels a tile, full height res
|
||||
// the layer holding a full-map uv, or -1: paging off, off the map, or not resident
|
||||
float tpSlot(vec2 uv) {
|
||||
if (u_tp_on < 0.5 || uv.x < 0.0 || uv.y < 0.0 || uv.x >= 1.0 || uv.y >= 1.0) return -1.0;
|
||||
ivec2 t = min(ivec2(floor(uv * u_tp_dims.x)), ivec2(int(u_tp_dims.x) - 1));
|
||||
return texelFetch(u_tp_page, t, 0).r - 1.0;
|
||||
}
|
||||
// a full-map uv inside its tile's layer of k texels a side, past the one-texel border
|
||||
vec2 tpUV(vec2 uv, float k) { return (1.0 + fract(uv * u_tp_dims.x) * k) / (k + 2.0); }
|
||||
#endif
|
||||
// ---- the baked normal (x, z; y is rebuilt by the caller) ----------------------------------
|
||||
#ifndef TP_NORMAL
|
||||
#define TP_NORMAL
|
||||
uniform sampler2DArray u_tp_nrm;
|
||||
vec2 terNormalXZ(vec2 uv) {
|
||||
#ifdef TP_FRAGMENT
|
||||
if (u_tp_on < 0.5) return texture(u_ter_normal, uv).rg;
|
||||
// taken before the per-pixel branch: a neighbour on another tile must not decide the level
|
||||
vec2 dx = dFdx(uv), dy = dFdy(uv);
|
||||
#endif
|
||||
float s = tpSlot(uv);
|
||||
if (s >= 0.0) return textureLod(u_tp_nrm, vec3(tpUV(uv, u_tp_dims.y), s), 0.0).rg;
|
||||
#ifdef TP_FRAGMENT
|
||||
return textureGrad(u_ter_normal, uv, dx, dy).rg;
|
||||
#else
|
||||
return textureLod(u_ter_normal, uv, 0.0).rg;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
vec3 terNormal(vec2 uv) { vec2 xz = terNormalXZ(uv); return vec3(xz.x, sqrt(max(1.0 - dot(xz, xz), 0.0)), xz.y); }
|
||||
uniform mat4 u_view;
|
||||
uniform float u_far_split;
|
||||
uniform float u_far_band;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue