OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / distribute / hypertps-win64 / shaders / edge_intersect.glsl

307 lines · glsl ref:
#version 450

// ============================================================================
// Stage 4+5 FUSED: Edge Intersection + Surface Normals
//
// Template shader: the SDF body is injected at the GENERATOR_SDF_BODY marker
// by DCWorker at shader composition time.
//
// For each active edge (compacted via Phase E), find the zero-crossing
// position via binary search, then compute the gradient (normal) at that
// point via central differences — all in a single pass.
//
// Previously these were two separate dispatches (edge_intersect + surface_normals).
// Fusing them eliminates 1 dispatch + 1 barrier and reuses SDF cache locality
// from the binary search for the gradient evaluations.
//
// Input:
//   density[33^3]         — density samples (for initial bracket)
//   edge_flags[32^3x3]    — 1 if sign change
//   edge_scan[32^3x3]     — exclusive prefix sum of edge_flags
//   Params UBO            — density evaluation parameters
//   voronoi_data[]        — voronoi cell data
//   perm_tables[]         — noise permutation tables
//   csg_primitives[]      — CSG primitive list
//   active_edge_indices[] — compacted active edge list (Phase E)
//   counts[]              — counts[0] = active edge count
//
// Output:
//   edge_data[active_count] — vec3 position + vec3 normal + uint edge_idx + pad
//
// Dispatch: ceil(EDGE_COUNT / 256), 1, 1
// ============================================================================

#define THREADS 256
#define BINARY_SEARCH_ITERS 8

layout(local_size_x = THREADS, local_size_y = 1, local_size_z = 1) in;

layout(set = 0, binding = 0, std430) readonly  buffer DensityBuf  { float density[];    };
layout(set = 0, binding = 1, std430) readonly  buffer EdgeFlags   { uint  edge_flags[]; };
layout(set = 0, binding = 2, std430) readonly  buffer EdgeScan    { uint  edge_scan[];  };
layout(set = 0, binding = 3, std430) writeonly buffer EdgeData    { float edge_data[];  };

// Density evaluation parameters (same layout as density_fill.glsl)
layout(set = 0, binding = 4, std140) uniform Params {
    int   grid_stride;
    float voxel_scale;
    float origin_x;
    float origin_y;
    float origin_z;
    float primary_frequency;
    float primary_strength;
    float tunnel_frequency;
    float tunnel_strength;
    float tunnel_radius;
    float ridge_frequency;
    float ridge_strength;
    float warp_frequency;
    float warp_strength;
    float detail_frequency;
    float detail_strength;
    float ground_bias;
    float solid_floor_depth;
    float sky_height;
    float flat_center_radius;
    float floor_strength;
    float floor_height_min;
    float floor_height_max;
    float room_height;
    float terrain_size;
    int primary_octaves;
    int tunnel_octaves;
    int ridge_octaves;
    int warp_octaves;
    int detail_octaves;
    int compute_normals;
    int seed;
    int voronoi_cell_count;
    int use_noise;
    int spherical;
    float planet_radius;
    float planet_center_x;
    float planet_center_y;
    float planet_center_z;
    float island_radius;
    int flags;            // bit1 = sample settled-edit brick (matches GpuParams)
};

layout(set = 0, binding = 5, std430) readonly buffer VoronoiBuf {
    int   voronoi_count;
    float voronoi_data[];
};

layout(set = 0, binding = 6, std430) readonly buffer PermBuf {
    int perm_tables[];
};

// ---- CSG primitives ----
// Edit buffer — MUST match the density-stage layout in dc_compute.cpp (GpuEdit /
// EditBuf). Previously this stage declared a divergent CSGPrimitive struct over
// the same binding-7 bytes, so the mesh stage read edits with scrambled field
// offsets (box center from half-extents, op/group from position floats) — the
// zero-crossing binary search and normals were placed on a garbage surface while
// the density grid was carved correctly, producing per-chunk steps, dark seams
// and mis-snapped cut floors. Same struct + same apply_gpu_edits math now.
struct GpuEdit {
    uint  edit_type;    // 0=sphere_sub,1=sphere_add,2=box_sub,3=box_add,4=mesh_sub,5=mesh_add,6=sphere_hard_sub,7=sphere_hard_add
    float pos_x, pos_y, pos_z;
    float half_x, half_y, half_z;
    float radius;
    float strength;
    uint  _pad0, _pad1, _pad2;
};

layout(set = 0, binding = 7, std430) readonly buffer EditBuf {
    uint edit_count;
    uint _edit_pad0, _edit_pad1, _edit_pad2;
    GpuEdit gpu_edits[];
};

// Convex-brush half-space planes (binding 11) — MUST match dc_compute.cpp
// binding 12. A CONVEX edit (etype 8/9) reads [_pad1, _pad1+_pad2) of
// vec4(nx,ny,nz,d); inside the brush = (n·p <= d).
layout(set = 0, binding = 11, std430) readonly buffer ConvexPlaneBuf {
    vec4 convex_planes[];
};

// Phase E: Compacted active edge index list
layout(set = 0, binding = 8, std430) readonly buffer ActiveEdgesBuf { uint active_edge_indices[]; };
layout(set = 0, binding = 9, std430) readonly buffer CountBuf { uint counts[]; };

// Settled-edit SDF delta brick (chunk-local, density-stride grid). Trilinearly
// sampled inside evaluate_density (noise_sdf.glsl) so the fractional binary-
// search + normal evals see the SAME baked surface as the density-grid
// endpoints. A zero buffer is an exact no-op (d += 0). Binding 10 was free.
layout(set = 0, binding = 10, std430) readonly buffer BrickBuf { float brick_delta[]; };

layout(push_constant) uniform PushConstants {
    vec3  cell_origin;
    float voxel_size;
    ivec3 grid_dims;       // (32, 32, 32)
    uint  total_elements;
    uint  mesh_quality_flags;
    uint  _pc_pad0;
    uint  _pc_pad1;
    uint  _pc_pad2;
};

// =====================================================================
// SDF body — injected by DCWorker from generator GLSL
// Must define: float evaluate_density(float x, float y, float z)
// =====================================================================
// GENERATOR_SDF_BODY

// =====================================================================
// Main: binary search + gradient in single pass
// =====================================================================

void main() {
    uint tid = gl_GlobalInvocationID.x;
    if (tid >= counts[0]) return;  // active edge count from prepare_dispatch
    uint gid = active_edge_indices[tid];  // original edge grid index

    int voxels = grid_dims.x;  // 32
    int density_dim = voxels + 1;  // 33

    // Decode: gid = (z*V*V + y*V + x) * 3 + dir
    uint voxel_idx = gid / 3u;
    uint dir = gid % 3u;
    int z = int(voxel_idx) / (voxels * voxels);
    int rem = int(voxel_idx) % (voxels * voxels);
    int y = rem / voxels;
    int x = rem % voxels;

    // Endpoint world positions
    vec3 p0 = cell_origin + vec3(float(x), float(y), float(z)) * voxel_size;
    vec3 p1 = p0;
    if (dir == 0u) p1.x += voxel_size;
    else if (dir == 1u) p1.y += voxel_size;
    else p1.z += voxel_size;

    // Endpoint densities from the grid
    int d_idx0 = z * density_dim * density_dim + y * density_dim + x;
    float d0 = density[d_idx0];

    // Binary search: find the zero crossing between p0 (d0) and p1 (d1)
    float lo = 0.0;
    float hi = 1.0;
    float d_lo = d0;
    int d_idx1;
    if (dir == 0u) d_idx1 = d_idx0 + 1;
    else if (dir == 1u) d_idx1 = d_idx0 + density_dim;
    else d_idx1 = d_idx0 + density_dim * density_dim;
    float d_hi = density[d_idx1];

    for (int i = 0; i < BINARY_SEARCH_ITERS; i++) {
        float mid = (lo + hi) * 0.5;
        vec3 p_mid = mix(p0, p1, mid);
        float d_mid = evaluate_density(p_mid.x, p_mid.y, p_mid.z);

        if ((d_lo >= 0.0) == (d_mid >= 0.0)) {
            lo = mid;
            d_lo = d_mid;
        } else {
            hi = mid;
            d_hi = d_mid;
        }
    }

    float t = (lo + hi) * 0.5;
    t = clamp(t, 0.001, 0.999);
    vec3 pos = mix(p0, p1, t);

    // ---- Surface normal via central differences (fused from Stage 5) ----
    float eps = voxel_size * 0.01;
    vec3 n;
    n.x = evaluate_density(pos.x + eps, pos.y, pos.z) - evaluate_density(pos.x - eps, pos.y, pos.z);
    n.y = evaluate_density(pos.x, pos.y + eps, pos.z) - evaluate_density(pos.x, pos.y - eps, pos.z);
    n.z = evaluate_density(pos.x, pos.y, pos.z + eps) - evaluate_density(pos.x, pos.y, pos.z - eps);

    float len = length(n);
    if (len > 1e-8) {
        n /= len;
    } else {
        n = vec3(0.0, 1.0, 0.0);  // fallback
    }

    // --- EXACT convex plane normal (feeds the qef_solve plane-snap) ----------
    // If `pos` lies on a convex brush face, replace the grid/noise-contaminated
    // central-difference normal with the brush's EXACT analytic plane normal
    // (argmax_i nᵢ·p − dᵢ). This gives the mesh stage clean, identical per-face
    // normals so the qef_solve clustering (0.999 tight) reconstructs the exact
    // planes for the plane-snap. ONLY convex crossings (etype 8/9) are touched —
    // organic/box/sphere normals stay central-diff (noisy → never cluster tight).
    uint hit_brush = 0u;         // which convex brush this crossing lies on (0 = none)
    uint hit_mat = 0u;           // that brush's material_id (rides GpuEdit._pad0)
    vec3 cdn = n;                // central-diff normal, kept for sign reference
    vec4 hit_pl = vec4(0.0);     // exact active brush face plane (nx,ny,nz,d)
    float min_absf = voxel_size * 1.8;   // crossings within ~2 voxels of a brush face
                                         // (0.5→1.0→1.8) — the override sets the EXACT
                                         // ray-plane crossing for these cells, so even
                                         // the edge cells the scalar brick rounds ~1-2
                                         // voxels off get exact Hermite → the snap lands
                                         // on the exact plane/crease → sawtooth (and the
                                         // geometric material-bleed at boundary tris)
                                         // clears. Safe: qef_solve's safety net gates the
                                         // vertex MOVE (tiny, since crossings are now
                                         // exact), not |f|; kept just under its 2.0 limit.
    for (uint ei = 0u; ei < edit_count; ei++) {
        GpuEdit ce = gpu_edits[ei];
        if (ce.edit_type != 8u && ce.edit_type != 9u) continue;
        vec3 dcv = pos - vec3(ce.pos_x, ce.pos_y, ce.pos_z);
        if (dot(dcv, dcv) > ce.radius * ce.radius) continue;
        uint poff = ce._pad1;
        uint pcnt = ce._pad2;
        float best = -1e30;
        vec4 best_pl = vec4(0.0);
        for (uint pi = 0u; pi < pcnt; pi++) {
            vec4 pl = convex_planes[poff + pi];
            float dp = pl.x * pos.x + pl.y * pos.y + pl.z * pos.z - pl.w;
            if (dp > best) { best = dp; best_pl = pl; }   // argmax = active convex face
        }
        // Pick the brush whose surface is CLOSEST to this crossing (smallest |f|) —
        // the actual ACTIVE surface in a dense/overlapping brush region.
        if (abs(best) < min_absf) {
            min_absf = abs(best);
            hit_pl = best_pl;
            hit_brush = ei + 1u;
            hit_mat = ce._pad0;              // material_id for the shader (route ②)
        }
    }
    // STEP 1 — EXACT ANALYTIC HERMITE (the Cepero path). If this crossing lies on
    // a convex brush face, discard the binary-searched position (which rides the
    // blended/noisy union density field) and the central-diff normal, and replace
    // them with the EXACT ray-vs-plane intersection along this edge + the exact
    // plane normal. Feeding exact Hermite to the QEF lets Dual Contouring
    // reproduce flat faces + razor edges/corners WITHOUT any downstream snap
    // (verified: test_hermite_qef.cpp). Guard: skip if the edge is ~parallel to
    // the face (no clean crossing) and keep the sampled fallback.
    if (hit_brush != 0u) {
        vec3 edir = p1 - p0;
        float denom = dot(hit_pl.xyz, edir);
        if (abs(denom) > 1e-8) {
            float te = clamp((hit_pl.w - dot(hit_pl.xyz, p0)) / denom, 0.0, 1.0);
            pos = p0 + te * edir;                          // exact crossing position
        }
        n = (dot(hit_pl.xyz, cdn) < 0.0) ? -hit_pl.xyz : hit_pl.xyz;  // exact normal
    }
    // ------------------------------------------------------------------------

    // Write to compacted output: position + normal + edge index
    uint dest = edge_scan[gid];
    uint base = dest * 8u;  // 8 floats per edge

    edge_data[base + 0u] = pos.x;
    edge_data[base + 1u] = pos.y;
    edge_data[base + 2u] = pos.z;
    edge_data[base + 3u] = n.x;
    edge_data[base + 4u] = n.y;
    edge_data[base + 5u] = n.z;
    edge_data[base + 6u] = uintBitsToFloat(gid);  // edge index for QEF
    // Pack brush id (high bits: hard-cell flag + per-brush confinement) with the
    // material_id (low 12 bits) into one slot — no extra buffer. qef_solve unpacks:
    // hit_brush = v>>12, material = v&0xFFF. Organic (hit_brush 0, mat 0) packs to 0.
    edge_data[base + 7u] = uintBitsToFloat((hit_brush << 12u) | (hit_mat & 0xFFFu));
}