MONKEY (reviewfix): stitch displaced water LOD edges
Co-Authored-By: GPT-5.6 Sol (Codex)
This commit is contained in:
co-authored by
GPT-5.6 Sol (Codex)
parent
32b3880cc1
commit
c59d42e708
@@ -132,9 +132,25 @@ fn water_time() -> f32 {
|
||||
|
||||
// Vertex Gerstner displacement for the ocean's long swell (zero everywhere else).
|
||||
// `liquid/waves.rs` mirrors it on the CPU for the swimmer bob; the two are one contract.
|
||||
fn water_swell(xz: vec2<f32>, authored_depth: f32) -> vec3<f32> {
|
||||
fn water_swell(xz: vec2<f32>, authored_depth: f32, stitch: f32) -> vec3<f32> {
|
||||
if water.lane.z < 0.5 && water.lane.y > 0.5 && water.lane.x < 0.5 && water.mode.x > 0.5 {
|
||||
return water_gerstner(xz, water_time(), swell_shore_fade(authored_depth)).xyz;
|
||||
let now = water_time();
|
||||
let shore = swell_shore_fade(authored_depth);
|
||||
// MONKEY (reviewfix): a tagged fine boundary vertex follows the straight edge between the
|
||||
// two displaced coarse endpoints. That closes the T-junction against a coarse neighbour.
|
||||
if stitch >= 1.0 {
|
||||
let lane = u32(floor(stitch));
|
||||
let along = fract(stitch);
|
||||
var direction = vec2<f32>(1.0, 0.0);
|
||||
if lane == 2u { direction = vec2<f32>(-1.0, 0.0); }
|
||||
if lane == 3u { direction = vec2<f32>(0.0, 1.0); }
|
||||
if lane == 4u { direction = vec2<f32>(0.0, -1.0); }
|
||||
let lo = xz - direction * along * 4.1666665;
|
||||
let hi = lo + direction * 4.1666665;
|
||||
return mix(water_gerstner(lo, now, shore).xyz,
|
||||
water_gerstner(hi, now, shore).xyz, along);
|
||||
}
|
||||
return water_gerstner(xz, now, shore).xyz;
|
||||
}
|
||||
return vec3<f32>(0.0);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ fn vertex(in: Vertex) -> LiquidVsOut {
|
||||
out.world_position =
|
||||
mesh_functions::mesh_position_local_to_world(world_from_local, vec4<f32>(in.position, 1.0));
|
||||
// MONKEY (enhanced water): ocean Gerstner displacement; zero on Classic/other surfaces.
|
||||
out.world_position += vec4<f32>(water_swell(out.world_position.xz, in.uv_b.x), 0.0);
|
||||
out.world_position += vec4<f32>(water_swell(out.world_position.xz, in.uv_b.x, in.uv_b.y), 0.0);
|
||||
out.clip_position = position_world_to_clip(out.world_position.xyz);
|
||||
out.world_normal = mesh_functions::mesh_normal_local_to_world(in.normal, in.instance_index);
|
||||
out.uv = in.uv;
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
//!
|
||||
//! Wgpu has no tessellation stage. The coarse grid therefore remains the streamed source of
|
||||
//! truth, while a 4x bilinear copy is built only when the camera enters the surface's near ring.
|
||||
//! Every original edge vertex survives bit-for-bit, so a fine chunk and its coarse neighbour share
|
||||
//! the same boundary. Hysteresis avoids rebuilding while the camera hovers at the threshold.
|
||||
//! Every original edge vertex survives bit-for-bit. Non-corner fine vertices on an outer edge carry
|
||||
//! a compact stitch tag in UV1.y, so the vertex shader lerps the displaced coarse endpoints instead
|
||||
//! of evaluating a new Gerstner point that can leave the neighbour's straight edge. Hysteresis
|
||||
//! avoids rebuilding while the camera hovers at the threshold.
|
||||
//! A full MCLQ copy is 1,089 vertices + 6,144 indices (about 68 KiB without allocator overhead);
|
||||
//! the 64 yd ring bounds ordinary terrain residency to roughly 3.5 MiB, and a per-surface ceiling
|
||||
//! refuses pathological MLIQ grids rather than allowing one pool to consume an unbounded copy.
|
||||
@@ -135,6 +137,46 @@ fn bilerp<const N: usize>(
|
||||
out
|
||||
}
|
||||
|
||||
/// MONKEY (reviewfix): encode an outer-edge fine vertex as `axis/sign + coarse-edge fraction`.
|
||||
/// UV1.y is otherwise zero. Coarse corners and interior fine vertices need no stitch.
|
||||
fn stitch_tag(
|
||||
positions: &[[f32; 3]],
|
||||
cols: usize,
|
||||
rows: usize,
|
||||
fine_cols: usize,
|
||||
fine_rows: usize,
|
||||
fx: usize,
|
||||
fy: usize,
|
||||
sx: usize,
|
||||
sy: usize,
|
||||
tx: f32,
|
||||
ty: f32,
|
||||
) -> f32 {
|
||||
let (a, b, t) = if (fy == 0 || fy + 1 == fine_rows) && fx % SUBDIVISIONS != 0 {
|
||||
let y = if fy == 0 { 0 } else { rows - 1 };
|
||||
(y * cols + sx, y * cols + sx + 1, tx)
|
||||
} else if (fx == 0 || fx + 1 == fine_cols) && fy % SUBDIVISIONS != 0 {
|
||||
let x = if fx == 0 { 0 } else { cols - 1 };
|
||||
(sy * cols + x, (sy + 1) * cols + x, ty)
|
||||
} else {
|
||||
return 0.0;
|
||||
};
|
||||
let dx = positions[b][0] - positions[a][0];
|
||||
let dz = positions[b][2] - positions[a][2];
|
||||
let lane = if dx.abs() >= dz.abs() {
|
||||
if dx >= 0.0 {
|
||||
1.0
|
||||
} else {
|
||||
2.0
|
||||
}
|
||||
} else if dz >= 0.0 {
|
||||
3.0
|
||||
} else {
|
||||
4.0
|
||||
};
|
||||
lane + t
|
||||
}
|
||||
|
||||
/// Four-way subdivision of every source cell. The output is still one indexed regular grid: there
|
||||
/// are no duplicated cell-edge vertices, and dry source cells emit no triangles.
|
||||
fn subdivide(coarse: &Mesh, grid: [u32; 2], wet: &[bool]) -> Option<Mesh> {
|
||||
@@ -185,7 +227,11 @@ fn subdivide(coarse: &Mesh, grid: [u32; 2], wet: &[bool]) -> Option<Mesh> {
|
||||
let tx = (fx - sx * SUBDIVISIONS) as f32 / SUBDIVISIONS as f32;
|
||||
fine_positions.push(bilerp(positions, cols, sx, sy, tx, ty));
|
||||
fine_uv0.push(bilerp(uv0, cols, sx, sy, tx, ty));
|
||||
fine_uv1.push(bilerp(uv1, cols, sx, sy, tx, ty));
|
||||
let mut uv1 = bilerp(uv1, cols, sx, sy, tx, ty);
|
||||
uv1[1] = stitch_tag(
|
||||
positions, cols, rows, fine_cols, fine_rows, fx, fy, sx, sy, tx, ty,
|
||||
);
|
||||
fine_uv1.push(uv1);
|
||||
if let (Some(source), Some(output)) = (colours, fine_colours.as_mut()) {
|
||||
output.push(bilerp(source, cols, sx, sy, tx, ty));
|
||||
}
|
||||
@@ -265,6 +311,14 @@ mod tests {
|
||||
assert_eq!(positions[20], [0.0, 0.0, 1.0]);
|
||||
assert_eq!(positions[24], [1.0, 1.0, 1.0]);
|
||||
assert_eq!(positions[12], [0.5, 0.25, 0.5]);
|
||||
let Some(VertexAttributeValues::Float32x2(uv1)) = fine.attribute(Mesh::ATTRIBUTE_UV_1)
|
||||
else {
|
||||
panic!("uv1");
|
||||
};
|
||||
assert_eq!(uv1[0][1], 0.0, "coarse corner");
|
||||
assert_eq!(uv1[2][1], 1.5, "top-edge midpoint, +x at t=0.5");
|
||||
assert_eq!(uv1[10][1], 3.5, "left-edge midpoint, +z at t=0.5");
|
||||
assert_eq!(uv1[12][1], 0.0, "interior fine vertex");
|
||||
assert_eq!(fine.indices().expect("indices").len(), 16 * 6);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user