diff --git a/changes/render3d-height-format.md b/changes/render3d-height-format.md new file mode 100644 index 00000000..66a91673 --- /dev/null +++ b/changes/render3d-height-format.md @@ -0,0 +1,8 @@ +bump: patch +type: perf +**The terrain's height field takes half the GPU memory.** The baked height and surface normal +shared one 4096^2 RGBA32F (256 MB); the height now stays alone in its R32F - the same 32 bits +every placement, the CPU read-back and physics read - and the normal goes beside it as x and z in +an RG16F (`ter_normal_tex`, bound as `u_ter_normal`), y rebuilt where it is read: 128 MB. The grass +cull takes it as a third texture (binding 6). In Maroon Lake's play: 2793 -> 2647 MB; the camp's +frame differs in 0.066% of pixels by more than 8, single grass blades at the slope gate. diff --git a/packages/ludic.render3d/env.ludic b/packages/ludic.render3d/env.ludic index bbeedfdb..9bb094de 100644 --- a/packages/ludic.render3d/env.ludic +++ b/packages/ludic.render3d/env.ludic @@ -734,6 +734,7 @@ export state Render3dState { ter_force_near: bool = false ter_skip: bool = false ter_height_tex: int = 0 + ter_normal_tex: int = 0 # the baked surface normal, x and z in RG16F (y rebuilt when read) ter_heights: floats = null # CPU copy, float bits, TERRAIN_RES^2 ter_reflect: bool = false # drawing the reflection: the mid mesh is plenty ter_prog: int = 0 diff --git a/packages/ludic.render3d/grass_gpu.ludic b/packages/ludic.render3d/grass_gpu.ludic index 1c200b1f..da9f026a 100644 --- a/packages/ludic.render3d/grass_gpu.ludic +++ b/packages/ludic.render3d/grass_gpu.ludic @@ -30,7 +30,7 @@ function gg_init(render3d_st: mut Render3dState) -> void { if not gpu_has_compute(render3d_st) or render3d_st.grass_prog == 0 { return } if r3d_env_has(render3d_st, "R3D_GRASS_GPU") and r3d_env(render3d_st, "R3D_GRASS_GPU") == "0" { return } render3d_st.gg_reset = gpu_compute(render3d_st, "grass_reset", 1) - render3d_st.gg_cull = gpu_compute_tex(render3d_st, "grass_cull", 3, 2) + render3d_st.gg_cull = gpu_compute_tex(render3d_st, "grass_cull", 3, 3) render3d_st.gg_prog = r3d_program(render3d_st, "grass_inst.vert", "model.frag", "#define FOLIAGE\n#define BLADE\n#define GBLADE\n#define GINST\n") if render3d_st.gg_reset == 0 or render3d_st.gg_cull == 0 or render3d_st.gg_prog == 0 { return } render3d_st.gg_tiles_buf = new []int @@ -105,7 +105,7 @@ function gg_cull_frame(render3d_st: mut Render3dState) -> void { # made once: a words() a frame is a calloc nothing frees if render3d_st.gg_rb == null { render3d_st.gg_rb = words(4); render3d_st.gg_cb = words(1); render3d_st.gg_bufs = words(3) - render3d_st.gg_texs = words(2); render3d_st.gg_pr = words(48) + render3d_st.gg_texs = words(3); render3d_st.gg_pr = words(48) } let rb = render3d_st.gg_rb rb[0] = GG_BANDS; rb[1] = 0; rb[2] = 0; rb[3] = 0 @@ -120,7 +120,7 @@ function gg_cull_frame(render3d_st: mut Render3dState) -> void { let bufs = render3d_st.gg_bufs bufs[0] = tb; bufs[1] = render3d_st.gg_out; bufs[2] = render3d_st.gg_cmds let texs = render3d_st.gg_texs - texs[0] = render3d_st.ter_height_tex; texs[1] = render3d_st.ter_ortho_tex + texs[0] = render3d_st.ter_height_tex; texs[1] = render3d_st.ter_ortho_tex; texs[2] = render3d_st.ter_normal_tex gpu_dispatch_tex(render3d_st, render3d_st.gg_cull, data_of(pr), 192, bufs, texs, (render3d_st.gg_max + 63) / 64, render3d_st.gg_n) } diff --git a/packages/ludic.render3d/shaders/grass.mesh b/packages/ludic.render3d/shaders/grass.mesh index b172254d..6f2c6305 100644 --- a/packages/ludic.render3d/shaders/grass.mesh +++ b/packages/ludic.render3d/shaders/grass.mesh @@ -17,6 +17,9 @@ uniform mat4 u_vp; uniform vec3 u_cam_pos; 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; @@ -95,7 +98,7 @@ void main() { vec4 ht = textureLod(u_ts_height, huv, 0.0); 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 = normalize(ht.gba); + vec3 gn = terNormal(huv); float h3 = bladeHash(ci, j, 2), h4 = bladeHash(ci, j, 3); float wl = u_sea_level; 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); } diff --git a/packages/ludic.render3d/shaders/grass.vert b/packages/ludic.render3d/shaders/grass.vert index 32f75c16..e434b24b 100644 --- a/packages/ludic.render3d/shaders/grass.vert +++ b/packages/ludic.render3d/shaders/grass.vert @@ -15,6 +15,9 @@ uniform mat4 u_vp; uniform vec3 u_cam_pos; 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; @@ -133,7 +136,7 @@ void main() { vec4 ht = texture(u_ts_height, huv); 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 = normalize(ht.gba); + vec3 gn = terNormal(huv); float h3 = bladeHash(ci, j, 2), h4 = bladeHash(ci, j, 3); // No blades under water: the sea's line everywhere, and the lake's inside its outline. One // line for both left a lake above the sea with grass on its bed or a valley with none. diff --git a/packages/ludic.render3d/shaders/grass_cull.comp b/packages/ludic.render3d/shaders/grass_cull.comp index 4f4567ad..e8cde392 100644 --- a/packages/ludic.render3d/shaders/grass_cull.comp +++ b/packages/ludic.render3d/shaders/grass_cull.comp @@ -25,8 +25,9 @@ layout(set = 0, binding = 0) uniform Params { layout(set = 0, binding = 1) readonly buffer Tiles { vec4 tiles[]; }; // corner x/z, indices per cell, cells per side layout(set = 0, binding = 2) buffer Out { vec4 outv[]; }; // 4 per blade layout(set = 0, binding = 3) buffer Cmds { uint cmds[]; }; // VkDrawIndexedIndirectCommand per band -layout(set = 0, binding = 4) uniform sampler2D u_height; // height, normal (terrain's u_ts_height) +layout(set = 0, binding = 4) uniform sampler2D u_height; // the height (terrain's u_ts_height) layout(set = 0, binding = 5) uniform sampler2D u_ortho; // the photograph +layout(set = 0, binding = 6) uniform sampler2D u_ter_normal; // the normal: x and z, y rebuilt const float CELL = 4.0; // grass.ludic GRASS_CELL @@ -92,7 +93,8 @@ void main() { // the frustum, on a sphere round the blade (it stands at most 0.9 m tall) vec3 root = vec3(xz.x, ht.r, xz.y); for (int k = 0; k < 4; k++) { if (dot(pr.planes[k].xyz, root) + pr.planes[k].w < -1.0) return; } - vec3 gn = normalize(ht.gba); + vec2 gxz = textureLod(u_ter_normal, huv, 0.0).rg; + vec3 gn = vec3(gxz.x, sqrt(max(1.0 - dot(gxz, gxz), 0.0)), gxz.y); float h3 = bladeHash(ci, j, 2), h4 = bladeHash(ci, j, 3); float wl = pr.lev.y; if (pr.lake.z > 0.0) { vec2 q = (xz - pr.lake.xy) / pr.lake.zw; if (dot(q, q) < 1.0) wl = max(wl, pr.lev.x); } diff --git a/packages/ludic.render3d/shaders/spv/1f95f31b.frag.spv b/packages/ludic.render3d/shaders/spv/1f95f31b.frag.spv index d8fef303..4038a9f5 100644 Binary files a/packages/ludic.render3d/shaders/spv/1f95f31b.frag.spv and b/packages/ludic.render3d/shaders/spv/1f95f31b.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/2e2908c8.frag.spv b/packages/ludic.render3d/shaders/spv/2e2908c8.frag.spv index 2a3f5b41..04901728 100644 Binary files a/packages/ludic.render3d/shaders/spv/2e2908c8.frag.spv and b/packages/ludic.render3d/shaders/spv/2e2908c8.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/5e4589a1.frag.spv b/packages/ludic.render3d/shaders/spv/5e4589a1.frag.spv index af676aef..caa5d86d 100644 Binary files a/packages/ludic.render3d/shaders/spv/5e4589a1.frag.spv and b/packages/ludic.render3d/shaders/spv/5e4589a1.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/a0f2dbac.frag.spv b/packages/ludic.render3d/shaders/spv/a0f2dbac.frag.spv index 3a316009..0e8c230b 100644 Binary files a/packages/ludic.render3d/shaders/spv/a0f2dbac.frag.spv and b/packages/ludic.render3d/shaders/spv/a0f2dbac.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/a45356c7.frag.spv b/packages/ludic.render3d/shaders/spv/a45356c7.frag.spv index b3696e9c..822a138b 100644 Binary files a/packages/ludic.render3d/shaders/spv/a45356c7.frag.spv and b/packages/ludic.render3d/shaders/spv/a45356c7.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/b8cff226.frag.spv b/packages/ludic.render3d/shaders/spv/b8cff226.frag.spv index a7862f95..b58da43a 100644 Binary files a/packages/ludic.render3d/shaders/spv/b8cff226.frag.spv and b/packages/ludic.render3d/shaders/spv/b8cff226.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/b8cff226.vert.spv b/packages/ludic.render3d/shaders/spv/b8cff226.vert.spv index 12eafb74..0b1e8255 100644 Binary files a/packages/ludic.render3d/shaders/spv/b8cff226.vert.spv and b/packages/ludic.render3d/shaders/spv/b8cff226.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/bf3151c8.frag.spv b/packages/ludic.render3d/shaders/spv/bf3151c8.frag.spv index 19ee5f63..a7e4de8e 100644 Binary files a/packages/ludic.render3d/shaders/spv/bf3151c8.frag.spv and b/packages/ludic.render3d/shaders/spv/bf3151c8.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/cb13f3c6.frag.spv b/packages/ludic.render3d/shaders/spv/cb13f3c6.frag.spv index a7862f95..b58da43a 100644 Binary files a/packages/ludic.render3d/shaders/spv/cb13f3c6.frag.spv and b/packages/ludic.render3d/shaders/spv/cb13f3c6.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/cb13f3c6.vert.spv b/packages/ludic.render3d/shaders/spv/cb13f3c6.vert.spv index 4858c099..85453269 100644 Binary files a/packages/ludic.render3d/shaders/spv/cb13f3c6.vert.spv and b/packages/ludic.render3d/shaders/spv/cb13f3c6.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv b/packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv index 39de29f8..0bd47374 100644 Binary files a/packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv and b/packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/d48d6a48.vert.spv b/packages/ludic.render3d/shaders/spv/d48d6a48.vert.spv index 0749c351..abcd08f3 100644 Binary files a/packages/ludic.render3d/shaders/spv/d48d6a48.vert.spv and b/packages/ludic.render3d/shaders/spv/d48d6a48.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/f2952361.frag.spv b/packages/ludic.render3d/shaders/spv/f2952361.frag.spv index 1baabb14..54173211 100644 Binary files a/packages/ludic.render3d/shaders/spv/f2952361.frag.spv and b/packages/ludic.render3d/shaders/spv/f2952361.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/f4382918.frag.spv b/packages/ludic.render3d/shaders/spv/f4382918.frag.spv index 9f268a77..194f3f0b 100644 Binary files a/packages/ludic.render3d/shaders/spv/f4382918.frag.spv and b/packages/ludic.render3d/shaders/spv/f4382918.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/grass_cull.comp.spv b/packages/ludic.render3d/shaders/spv/grass_cull.comp.spv index 4b9c9260..46bcf895 100644 Binary files a/packages/ludic.render3d/shaders/spv/grass_cull.comp.spv and b/packages/ludic.render3d/shaders/spv/grass_cull.comp.spv differ diff --git a/packages/ludic.render3d/shaders/spv/manifest.txt b/packages/ludic.render3d/shaders/spv/manifest.txt index 78000865..3d92618e 100644 --- a/packages/ludic.render3d/shaders/spv/manifest.txt +++ b/packages/ludic.render3d/shaders/spv/manifest.txt @@ -918,8 +918,9 @@ T d48d6a48 u_nrm 7 T d48d6a48 u_ortho 8 T d48d6a48 u_prefilter 9 T d48d6a48 u_shadow 10 -T d48d6a48 u_tershadow 11 -T d48d6a48 u_ts_height 12 +T d48d6a48 u_ter_normal 11 +T d48d6a48 u_tershadow 12 +T d48d6a48 u_ts_height 13 P cb13f3c6 grass.vert model.frag #define FOLIAGE;#define BLADE;#define GBLADE; B cb13f3c6 vert 0 324 U cb13f3c6 vert u_view 0 mat4 1 0 @@ -1000,8 +1001,9 @@ T cb13f3c6 u_nrm 7 T cb13f3c6 u_ortho 8 T cb13f3c6 u_prefilter 9 T cb13f3c6 u_shadow 10 -T cb13f3c6 u_tershadow 11 -T cb13f3c6 u_ts_height 12 +T cb13f3c6 u_ter_normal 11 +T cb13f3c6 u_tershadow 12 +T cb13f3c6 u_ts_height 13 P b8cff226 grass.vert model.frag #define FOLIAGE;#define BLADE;#define GBLADE;#define TILES; B b8cff226 vert 0 4420 U b8cff226 vert u_view 0 mat4 1 0 @@ -1083,8 +1085,9 @@ T b8cff226 u_nrm 7 T b8cff226 u_ortho 8 T b8cff226 u_prefilter 9 T b8cff226 u_shadow 10 -T b8cff226 u_tershadow 11 -T b8cff226 u_ts_height 12 +T b8cff226 u_ter_normal 11 +T b8cff226 u_tershadow 12 +T b8cff226 u_ts_height 13 P 3733fc38 impostor.vert impostor.frag B 3733fc38 vert 0 232 U 3733fc38 vert u_view 0 mat4 1 0 @@ -3020,8 +3023,9 @@ T 2e2908c8 u_rock_n 13 T 2e2908c8 u_shadow 14 T 2e2908c8 u_snow_d 15 T 2e2908c8 u_sunshadow 16 -T 2e2908c8 u_tershadow 17 -T 2e2908c8 u_ts_height 18 +T 2e2908c8 u_ter_normal 17 +T 2e2908c8 u_tershadow 18 +T 2e2908c8 u_ts_height 19 P 1f95f31b terrain.vert terrain.frag #define FAR_ONLY; B 1f95f31b vert 0 204 U 1f95f31b vert u_half 0 float 1 0 @@ -3097,8 +3101,9 @@ T 1f95f31b u_rock_n 13 T 1f95f31b u_shadow 14 T 1f95f31b u_snow_d 15 T 1f95f31b u_sunshadow 16 -T 1f95f31b u_tershadow 17 -T 1f95f31b u_ts_height 18 +T 1f95f31b u_ter_normal 17 +T 1f95f31b u_tershadow 18 +T 1f95f31b u_ts_height 19 P f4382918 terrain.vert terrain.frag #define NEAR_ONLY; B f4382918 vert 0 204 U f4382918 vert u_half 0 float 1 0 @@ -3174,8 +3179,9 @@ T f4382918 u_rock_n 13 T f4382918 u_shadow 14 T f4382918 u_snow_d 15 T f4382918 u_sunshadow 16 -T f4382918 u_tershadow 17 -T f4382918 u_ts_height 18 +T f4382918 u_ter_normal 17 +T f4382918 u_tershadow 18 +T f4382918 u_ts_height 19 P f2952361 terrain.vert terrain.frag #define SUN_INLINE; B f2952361 vert 0 204 U f2952361 vert u_half 0 float 1 0 @@ -3251,8 +3257,9 @@ T f2952361 u_rock_n 13 T f2952361 u_shadow 14 T f2952361 u_snow_d 15 T f2952361 u_sunshadow 16 -T f2952361 u_tershadow 17 -T f2952361 u_ts_height 18 +T f2952361 u_ter_normal 17 +T f2952361 u_tershadow 18 +T f2952361 u_ts_height 19 P a0f2dbac terrain.vert terrain.frag #define SUN_INLINE;#define FAR_ONLY; B a0f2dbac vert 0 204 U a0f2dbac vert u_half 0 float 1 0 @@ -3328,8 +3335,9 @@ T a0f2dbac u_rock_n 13 T a0f2dbac u_shadow 14 T a0f2dbac u_snow_d 15 T a0f2dbac u_sunshadow 16 -T a0f2dbac u_tershadow 17 -T a0f2dbac u_ts_height 18 +T a0f2dbac u_ter_normal 17 +T a0f2dbac u_tershadow 18 +T a0f2dbac u_ts_height 19 P 5e4589a1 terrain.vert terrain.frag #define SUN_INLINE;#define NEAR_ONLY; B 5e4589a1 vert 0 204 U 5e4589a1 vert u_half 0 float 1 0 @@ -3405,8 +3413,9 @@ T 5e4589a1 u_rock_n 13 T 5e4589a1 u_shadow 14 T 5e4589a1 u_snow_d 15 T 5e4589a1 u_sunshadow 16 -T 5e4589a1 u_tershadow 17 -T 5e4589a1 u_ts_height 18 +T 5e4589a1 u_ter_normal 17 +T 5e4589a1 u_tershadow 18 +T 5e4589a1 u_ts_height 19 P bf3151c8 terrain.vert tersun.frag B bf3151c8 vert 0 204 U bf3151c8 vert u_half 0 float 1 0 @@ -3462,8 +3471,9 @@ T bf3151c8 u_height 3 T bf3151c8 u_irradiance 4 T bf3151c8 u_prefilter 5 T bf3151c8 u_shadow 6 -T bf3151c8 u_tershadow 7 -T bf3151c8 u_ts_height 8 +T bf3151c8 u_ter_normal 7 +T bf3151c8 u_tershadow 8 +T bf3151c8 u_ts_height 9 P 46e2325c water.vert water.frag B 46e2325c vert 0 152 U 46e2325c vert u_view 0 mat4 1 0 diff --git a/packages/ludic.render3d/shaders/ternormal.frag b/packages/ludic.render3d/shaders/ternormal.frag index 25355982..583fd3a7 100644 --- a/packages/ludic.render3d/shaders/ternormal.frag +++ b/packages/ludic.render3d/shaders/ternormal.frag @@ -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) } diff --git a/packages/ludic.render3d/shaders/terrain.frag b/packages/ludic.render3d/shaders/terrain.frag index a2d2cb2a..e6816fe9 100644 --- a/packages/ludic.render3d/shaders/terrain.frag +++ b/packages/ludic.render3d/shaders/terrain.frag @@ -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), diff --git a/packages/ludic.render3d/shaders/tersun.frag b/packages/ludic.render3d/shaders/tersun.frag index 7a72c6bf..f11c851b 100644 --- a/packages/ludic.render3d/shaders/tersun.frag +++ b/packages/ludic.render3d/shaders/tersun.frag @@ -12,13 +12,16 @@ in vec3 v_wpos; in vec2 v_huv; out float o_sh; 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 mat4 u_view; uniform float u_far_split; uniform float u_far_band; void main() { vec3 p = v_wpos; if (p.y < u_clip_y) discard; - vec3 N = normalize(texture(u_height, v_huv).gba); + vec3 N = terNormal(v_huv); float dist = length(p - u_cam_pos); float viewDepth = -(u_view * vec4(p, 1.0)).z; // The same tier choice the ground makes, cross-faded over the same band: the near tier diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index 433ee665..863ac4c8 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -199,16 +199,19 @@ function terrain_generate(render3d_st: mut Render3dState) -> void { } mesh_draw(render3d_st, render3d_st.sky_fullscreen) # second pass: R = height, GBA = the smooth surface normal, baked once (ternormal.frag) + # the height stays in its own R32F (physics and every placement read it at full precision); the + # baked normal goes beside it as x and z in RG16F, y rebuilt where it is read - 128 MB where one + # RGBA32F carrying both was 256 (plan 23 of maroon-lake) let raw = render3d_st.ter_height_tex - render3d_st.ter_height_tex = tex_target(render3d_st, TERRAIN_RES, TERRAIN_RES, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_LINEAR) - gpu_fb_color(render3d_st, 0, render3d_st.ter_height_tex) + if render3d_st.ter_normal_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_normal_tex) } + render3d_st.ter_normal_tex = tex_target(render3d_st, TERRAIN_RES, TERRAIN_RES, GL_RG16F, GL_RG, GL_FLOAT, GL_LINEAR) + gpu_fb_color(render3d_st, 0, render3d_st.ter_normal_tex) let pn = r3d_program(render3d_st, "fullscreen.vert", "ternormal.frag", "") gpu_use_program(render3d_st, pn) r3d_bind_2d(render3d_st, pn, "u_src", 0, raw) u_f(render3d_st, gpu_uniform(render3d_st, pn, "u_half"), float(render3d_st.TERRAIN_HALF)) mesh_draw(render3d_st, render3d_st.sky_fullscreen) gpu_program_free(render3d_st, pn) - gpu_tex_free(render3d_st, raw) # read the heights back for placement, into the array a previous map had: always the same size, # and made anew on every build it was 64 MB lost per world swap if render3d_st.ter_heights == null { render3d_st.ter_heights = floats(TERRAIN_RES * TERRAIN_RES) } @@ -224,6 +227,7 @@ function terrain_generate(render3d_st: mut Render3dState) -> void { # The height field for shaders that place things on the ground (model.vert's u_ground) function terrain_bind_height(render3d_st: mut Render3dState, p: int) -> void { r3d_bind_2d(render3d_st, p, "u_ts_height", 5, render3d_st.ter_height_tex) + r3d_bind_2d(render3d_st, p, "u_ter_normal", 6, render3d_st.ter_normal_tex) u_f(render3d_st, gpu_uniform(render3d_st, p, "u_ts_half"), float(render3d_st.TERRAIN_HALF)) u_f2(render3d_st, gpu_uniform(render3d_st, p, "u_ts_origin"), render3d_st.ter_ox, render3d_st.ter_oz) } @@ -243,6 +247,7 @@ function terrain_bake_shadow(render3d_st: mut Render3dState) -> void { gpu_blend(render3d_st, false) gpu_use_program(render3d_st, p) r3d_bind_2d(render3d_st, p, "u_height", 0, render3d_st.ter_height_tex) + r3d_bind_2d(render3d_st, p, "u_ter_normal", 7, render3d_st.ter_normal_tex) u_f(render3d_st, gpu_uniform(render3d_st, p, "u_half"), float(render3d_st.TERRAIN_HALF)) u_v3(render3d_st, gpu_uniform(render3d_st, p, "u_sun"), render3d_st.sun_dir) mesh_draw(render3d_st, render3d_st.sky_fullscreen) @@ -408,6 +413,7 @@ function terrain_draw_shadow(light_vp: floats) -> void { function terrain_bind_prog(render3d_st: mut Render3dState, p: int) -> void { gpu_use_program(render3d_st, p) r3d_bind_2d(render3d_st, p, "u_height", 0, render3d_st.ter_height_tex) + r3d_bind_2d(render3d_st, p, "u_ter_normal", 7, render3d_st.ter_normal_tex) r3d_bind_2d(render3d_st, p, "u_grass_d", 1, render3d_st.ter_tex[0]); r3d_bind_2d(render3d_st, p, "u_grass_n", 2, render3d_st.ter_tex[1]); r3d_bind_2d(render3d_st, p, "u_grass_a", 3, render3d_st.ter_tex[2]) # The cliff maps went with the dead cliff sample. Binding textures for uniforms the # shader no longer declares leaves those units pointing at nothing, which the driver @@ -516,6 +522,7 @@ function terrain_sun_pass(render3d_st: mut Render3dState, w: int, h: int, depth: let p = render3d_st.ter_sun_prog gpu_use_program(render3d_st, p) r3d_bind_2d(render3d_st, p, "u_height", 0, render3d_st.ter_height_tex) + r3d_bind_2d(render3d_st, p, "u_ter_normal", 7, render3d_st.ter_normal_tex) u_f(render3d_st, gpu_uniform(render3d_st, p, "u_half"), float(render3d_st.TERRAIN_HALF)) u_mat4(render3d_st, gpu_uniform(render3d_st, p, "u_view"), render3d_st.cam_view) u_mat4(render3d_st, gpu_uniform(render3d_st, p, "u_proj"), render3d_st.cam_proj) @@ -748,6 +755,7 @@ function cdlod_select(render3d_st: mut Render3dState, level: int, ix: int, iz: i # frame-sized targets and the shadow texture, whose size does not depend on the map. function terrain_unload(render3d_st: mut Render3dState) -> void { if render3d_st.ter_height_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_height_tex); render3d_st.ter_height_tex = 0 } + if render3d_st.ter_normal_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_normal_tex); render3d_st.ter_normal_tex = 0 } if render3d_st.ter_dem_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_dem_tex); render3d_st.ter_dem_tex = 0 } if render3d_st.ter_ortho_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_ortho_tex); render3d_st.ter_ortho_tex = 0 } if render3d_st.ter_heights != null { free(render3d_st.ter_heights); render3d_st.ter_heights = null }