OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / shaders / surface_normals.glsl

174 lines · glsl ref:
#version 450

// ============================================================================
// Stage 5: 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 intersection, compute the gradient of the density
// field at the intersection point via central differences using the full
// density evaluation function.
//
// Input:
//   density[]      — density grid (unused by noise SDF, used by grid SDF)
//   edge_data[]    — position set by Stage 4
//   Params UBO     — density evaluation parameters
//   voronoi_data[] — voronoi cell data
//   perm_tables[]  — noise permutation tables
//   csg_primitives — CSG primitive list
//   counts[]       — counts[0] = active edge count
//
// Output:
//   edge_data[active].normal — updated with normalized gradient
//
// Dispatch: ceil(active_count / 256), 1, 1
// ============================================================================

#define THREADS 256

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 = 3, std430)          buffer EdgeData   { float edge_data[]; };

// Density evaluation parameters (same layout as edge_intersect.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;
};

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
struct CSGPrimitive {
    uint  prim_type;
    uint  op;
    uint  group;
    uint  flags;
    float pos_x, pos_y, pos_z;
    float _pad0;
    float size_x, size_y, size_z;
    float _pad1;
};

layout(set = 0, binding = 7, std430) readonly buffer CSGBuf {
    uint csg_count;
    uint _csg_pad[3];
    CSGPrimitive csg_primitives[];
};

layout(set = 0, binding = 8, std430) readonly buffer CountBuf { uint counts[]; };

layout(push_constant) uniform PushConstants {
    vec3  cell_origin;
    float voxel_size;
    ivec3 grid_dims;
    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: compute normals via central differences on the full SDF
// =====================================================================

void main() {
    uint gid = gl_GlobalInvocationID.x;
    uint active_count = counts[0];
    if (gid >= active_count) return;

    uint base = gid * 8u;

    // Read world-space position (set by edge_intersect)
    float wx = edge_data[base + 0u];
    float wy = edge_data[base + 1u];
    float wz = edge_data[base + 2u];

    // Central differences on the full SDF
    // HALF A VOXEL, not a hundredth.
    //
    // evaluate_density reaches the surface through a large offset and a small
    // scale -- (y - floor_y) * (height_scale / y_range), about 2.63 for a
    // terrain 165 m up in a 21 km band. A 1 cm probe asks for a difference of
    // ~1e-7 out of 2.63, which is below one float32 ULP: measured, the
    // horizontal components came back EXACTLY 0.0 for every slope tested from
    // 0.003 to 0.03. The normal then collapses to straight up and the mesh
    // shows isolated wrong-facing triangles wherever rounding tips it -- worst
    // on SMOOTH ground, where the true gradient is smallest.
    //
    // Half a voxel restores it (2.9e-2 against an exact 3.0e-2). DC already
    // resolves features at one voxel, so probing at half of one costs nothing
    // it could have represented; a hundredth of a voxel was asking float32 for
    // precision it does not have.
    float eps = voxel_size * 0.5;
    vec3 n;
    n.x = evaluate_density(wx + eps, wy, wz) - evaluate_density(wx - eps, wy, wz);
    n.y = evaluate_density(wx, wy + eps, wz) - evaluate_density(wx, wy - eps, wz);
    n.z = evaluate_density(wx, wy, wz + eps) - evaluate_density(wx, wy, wz - eps);

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

    edge_data[base + 3u] = n.x;
    edge_data[base + 4u] = n.y;
    edge_data[base + 5u] = n.z;
}