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

@ -1,5 +1,5 @@
// Second generation pass: copy the height into R and bake the B-spline surface normal into
// GBA, once, at texel resolution. terrain.frag used to differentiate the bicubic height
// Second generation pass: bake the B-spline surface normal, once, at texel resolution: x and z
// into an RG16F beside the R32F height (y is rebuilt where it is read). terrain.frag used to differentiate the bicubic height
// per pixel — four bicubic reads, sixteen taps — for a quantity that never changes.
in vec2 v_uv;
out vec4 o;
@ -28,5 +28,5 @@ void main() {
float hd = heightSmooth(u_src, v_uv - vec2(0, step));
float hu = heightSmooth(u_src, v_uv + vec2(0, step));
vec3 n = normalize(vec3(hl - hr, 2.0 * world, hd - hu));
o = vec4(texture(u_src, v_uv).r, n);
o = vec4(n.x, n.z, 0.0, 0.0); // into RG16F: x and z, y rebuilt when read (always up)
}