perf(render3d): the terrain's height and normal in R32F + RG16F, 128 MB where one RGBA32F was 256

The first generation pass's R32F height is kept as ter_height_tex (not copied into an RGBA32F),
so every reader of the height - placement, the read-back, physics, selftest16's 9 mm - sees the
same 32 bits. ternormal.frag writes the baked normal's x and z into ter_normal_tex (RG16F), and
the six places that read it (terrain.frag twice, tersun.frag, grass.vert, grass.mesh,
grass_cull.comp, which takes a third texture at binding 6) rebuild y. SPIR-V regenerated.

Maroon Lake's play, headless Vulkan: 2793 -> 2647 MB. The camp's frame: 0.066% of pixels differ by
more than 8 (mean 0.024/255), isolated grass blades at the slope gate. ludic-dev test 307/307.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 02:31:18 +03:00
parent f1049953c4
commit 1675eb451f
26 changed files with 77 additions and 36 deletions

View file

@ -7,6 +7,9 @@ out vec4 o_color;
#define gnoise(p) 0.1
#endif
uniform sampler2D u_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 = 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;
@ -76,7 +79,7 @@ float heightSmooth(sampler2D tex, vec2 uv) {
}
// baked at generation (ternormal.frag) into the height texture's GBA
vec3 terrainNormal(vec2 uv) {
return normalize(texture(u_height, uv).gba);
return terNormal(uv);
}
// stochastic (triangle-grid) sampling: three randomly offset / rotated taps blended by
@ -223,7 +226,7 @@ void triC(bool cheap, sampler2D d, sampler2D nm, sampler2D am, vec3 p, vec3 dpx,
uniform float u_far_split;
uniform float u_far_band;
float sunInline(vec3 p, float dist, float viewDepth) {
vec3 Ng = normalize(texture(u_height, v_huv).gba);
vec3 Ng = terNormal(v_huv);
if (dist > u_far_split + u_far_band) return sunShadowCheap(p, Ng, viewDepth);
if (dist < u_far_split - u_far_band) return sunShadow(p, Ng, viewDepth);
return mix(sunShadow(p, Ng, viewDepth), sunShadowCheap(p, Ng, viewDepth),