OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / shaders / generators / noise_sdf.glsl

376 lines · glsl ref:
// =====================================================================
// Noise SDF body — pluggable generator for the DC GPU pipeline.
//
// Provides: float evaluate_density(float x, float y, float z)
//
// Requires (declared by template):
//   - Params UBO fields: primary_frequency, primary_strength, etc.
//   - perm_tables[] (PermBuf), voronoi_count/voronoi_data[] (VoronoiBuf)
//   - CSGPrimitive struct, csg_count, csg_primitives[] (CSGBuf)
//
// Extracted from density_fill.glsl — the 7-layer noise + CSG evaluation
// that was previously duplicated across density_fill, edge_intersect,
// and surface_normals shaders.
// =====================================================================

// Permutation table offsets for each noise generator
#define PERM_PRIMARY  0
#define PERM_TUNNEL_A 512
#define PERM_TUNNEL_B 1024
#define PERM_RIDGE    1536
#define PERM_WARP     2048
#define PERM_DETAIL   2560

int perm(int table_offset, int idx) {
    return perm_tables[table_offset + (idx & 255)];
}

int fastfloor(float x) {
    int xi = int(x);
    return (x < float(xi)) ? xi - 1 : xi;
}

float grad3(int hash, float x, float y, float z) {
    int h = hash & 15;
    float u = (h < 8) ? x : y;
    float v = (h < 4) ? y : ((h == 12 || h == 14) ? x : z);
    return (((h & 1) != 0) ? -u : u) + (((h & 2) != 0) ? -v : v);
}

float contrib(int table_offset, int ii, int jj, int kk, float x, float y, float z) {
    float t = 0.6 - x * x - y * y - z * z;
    if (t < 0.0) return 0.0;
    t *= t;
    int gi = perm(table_offset, ii + perm(table_offset, jj + perm(table_offset, kk)));
    return t * t * grad3(gi, x, y, z);
}

float noise3(int table_offset, float x, float y, float z) {
    const float F3 = 1.0 / 3.0;
    const float G3 = 1.0 / 6.0;

    float s = (x + y + z) * F3;
    int i = fastfloor(x + s);
    int j = fastfloor(y + s);
    int k = fastfloor(z + s);

    float t = float(i + j + k) * G3;
    float X0 = float(i) - t;
    float Y0 = float(j) - t;
    float Z0 = float(k) - t;
    float x0 = x - X0;
    float y0 = y - Y0;
    float z0 = z - Z0;

    int i1, j1, k1;
    int i2, j2, k2;

    if (x0 >= y0) {
        if (y0 >= z0) {
            i1=1; j1=0; k1=0; i2=1; j2=1; k2=0;
        } else if (x0 >= z0) {
            i1=1; j1=0; k1=0; i2=1; j2=0; k2=1;
        } else {
            i1=0; j1=0; k1=1; i2=1; j2=0; k2=1;
        }
    } else {
        if (y0 < z0) {
            i1=0; j1=0; k1=1; i2=0; j2=1; k2=1;
        } else if (x0 < z0) {
            i1=0; j1=1; k1=0; i2=0; j2=1; k2=1;
        } else {
            i1=0; j1=1; k1=0; i2=1; j2=1; k2=0;
        }
    }

    float x1 = x0 - float(i1) + G3;
    float y1 = y0 - float(j1) + G3;
    float z1 = z0 - float(k1) + G3;
    float x2 = x0 - float(i2) + 2.0 * G3;
    float y2 = y0 - float(j2) + 2.0 * G3;
    float z2 = z0 - float(k2) + 2.0 * G3;
    float x3 = x0 - 1.0 + 3.0 * G3;
    float y3 = y0 - 1.0 + 3.0 * G3;
    float z3 = z0 - 1.0 + 3.0 * G3;

    int ii = i & 255;
    int jj = j & 255;
    int kk = k & 255;

    float n0 = contrib(table_offset, ii,      jj,      kk,      x0, y0, z0);
    float n1 = contrib(table_offset, ii + i1,  jj + j1,  kk + k1,  x1, y1, z1);
    float n2 = contrib(table_offset, ii + i2,  jj + j2,  kk + k2,  x2, y2, z2);
    float n3 = contrib(table_offset, ii + 1,   jj + 1,   kk + 1,   x3, y3, z3);

    return 32.0 * (n0 + n1 + n2 + n3);
}

float fbm3(int table_offset, float x, float y, float z, int octaves) {
    float sum = 0.0;
    float amp = 1.0;
    float freq = 1.0;
    float max_amp = 0.0;
    for (int o = 0; o < octaves; o++) {
        sum += noise3(table_offset, x * freq, y * freq, z * freq) * amp;
        max_amp += amp;
        amp *= 0.5;    // persistence
        freq *= 2.0;   // lacunarity
    }
    return sum / max_amp;
}

// Helper: sample noise with frequency scaling
float sample_noise(int table_offset, float freq, float px, float py, float pz, int oct) {
    return fbm3(table_offset, px * freq, py * freq, pz * freq, oct);
}

// =====================================================================
// CSG primitive evaluation (port of dc_density.cpp)
// =====================================================================


// EDIT APPLICATION LIVES IN THE TEMPLATE NOW (edge_intersect.glsl's
// dc_sample_field), not here. apply_gpu_edits and apply_brick_delta moved there
// with it. This body returns the RAW field, which is the same contract
// DENSITY_GLSL_BODY has always had -- one contract for both templates, so a
// custom generator can satisfy it. Re-adding an inline apply here would DOUBLE
// it, and apply_gpu_edits is additive, not idempotent.

// =====================================================================
// Density evaluation (exact port of DCDensity::evaluate)
// =====================================================================

float clampf(float v, float lo, float hi) {
    return clamp(v, lo, hi);
}

float smoothstep_manual(float t) {
    return t * t * (3.0 - 2.0 * t);
}

// Hash-based procedural Voronoi (infinite tiling) — IDENTICAL to the CPU
// DCDensity::evaluate_internal and the density-fill stage in dc_compute.cpp.
// The previous buffer-based voronoi_data[] path used a DIFFERENT cell set
// (SeededRNG LCG from configure()) than the carved density grid, so the mesh
// stage placed floor/ceiling features at different heights than the grid —
// adjacent-chunk mismatches. This hash path reconstructs the exact same cells.
uint hash2d(int ix, int iz, int seed_offset) {
    uint h = uint(ix * 374761393 + iz * 668265263 + seed_offset);
    h = (h ^ (h >> 13u)) * 1274126177u;
    h = h ^ (h >> 16u);
    return h;
}

float hash_float(int ix, int iz, int seed_offset) {
    return float(hash2d(ix, iz, seed_offset) & 0xFFFFFFu) / 16777216.0;
}

float evaluate_density(float x, float y, float z) {
    // === Base density: flat ground or spherical planet ===
    float d;
    if (spherical != 0) {
        // Sphere SDF: positive inside (solid), negative outside (air)
        float dx = x - planet_center_x;
        float dy = y - planet_center_y;
        float dz = z - planet_center_z;
        float dist = sqrt(dx * dx + dy * dy + dz * dz);
        d = planet_radius - dist;

        // Early out: deep interior or far above surface
        float max_displacement = primary_strength + ridge_strength +
                                  detail_strength + warp_strength * 0.5;
        if (abs(d) > max_displacement * 2.5) {
            return d;
        }
    } else {
        d = -y * ground_bias;
    }

    // === Noise layers (skipped when use_noise == 0) ===
    if (use_noise == 0) {
        if (spherical == 0) {
            float floor_blend = max((-y - solid_floor_depth) * 0.1, 0.0);
            d += floor_blend;
            float sky_blend = max((y - sky_height) * 0.1, 0.0);
            d -= sky_blend;
        }
        return d;
    }

    // === Layer 1: Domain warping ===
    float wx = x + sample_noise(PERM_WARP, warp_frequency, x, y, z, warp_octaves) * warp_strength;
    float wy = y + sample_noise(PERM_WARP, warp_frequency, x + 31.7, y + 47.3, z + 89.1, warp_octaves) * warp_strength;
    float wz = z + sample_noise(PERM_WARP, warp_frequency, x + 73.1, y + 11.9, z + 59.7, warp_octaves) * warp_strength;

    // === Layer 2: Primary 3D noise ===
    d += sample_noise(PERM_PRIMARY, primary_frequency, wx, wy, wz, primary_octaves) * primary_strength;

    // === Layer 3: Ridged noise ===
    float ridge_raw = sample_noise(PERM_RIDGE, ridge_frequency, wx, wy, wz, ridge_octaves);
    float ridge_eps = 0.08;
    float ridged = 1.0 - sqrt(ridge_raw * ridge_raw + ridge_eps * ridge_eps);
    ridged = max(ridged, 0.0);
    ridged = ridged * ridged;
    d += ridged * ridge_strength;

    // === Layer 4: Detail noise ===
    d += sample_noise(PERM_DETAIL, detail_frequency, wx, wy, wz, detail_octaves) * detail_strength;

    // === Layer 5: Tunnels ===
    if (spherical != 0) {
        // Spherical tunnels: depth-based fade (carve crust, not surface)
        float max_displacement = primary_strength + ridge_strength +
                                  detail_strength + warp_strength * 0.5;
        float crust_depth = max_displacement * 2.0;
        if (d > -tunnel_strength && d < crust_depth) {
            float ta = sample_noise(PERM_TUNNEL_A, tunnel_frequency, wx, wy * 0.7, wz, tunnel_octaves);
            float tb = sample_noise(PERM_TUNNEL_B, tunnel_frequency, wx + 200.0, wy * 0.7, wz + 200.0, tunnel_octaves);
            float tube_dist_sq = ta * ta + tb * tb;
            float radius_sq = tunnel_radius * tunnel_radius;
            float tube_carve = max(1.0 - tube_dist_sq / radius_sq, 0.0);
            float depth_below_surface = d;
            float surface_fade = clampf(depth_below_surface / (tunnel_strength * 0.5), 0.0, 1.0);
            d -= tube_carve * tunnel_strength * surface_fade;
        }
    } else {
        // Flat terrain tunnels
        float ta = sample_noise(PERM_TUNNEL_A, tunnel_frequency, wx, wy * 0.7, wz, tunnel_octaves);
        float tb = sample_noise(PERM_TUNNEL_B, tunnel_frequency, wx + 200.0, wy * 0.7, wz + 200.0, tunnel_octaves);
        float tube_dist_sq = ta * ta + tb * tb;
        float radius_sq = tunnel_radius * tunnel_radius;
        float tube_carve = max(1.0 - tube_dist_sq / radius_sq, 0.0);
        d -= tube_carve * tunnel_strength;
    }

    // === Flat terrain only: voronoi, height clamping, spawn area ===
    if (spherical == 0) {
        // Voronoi floors/ceilings — hash-based, matching CPU evaluate_internal
        // and the density-fill stage (NOT the legacy voronoi_data[] buffer).
        if (floor_strength > 0.0) {
            float cell_spacing = terrain_size / float(voronoi_cell_count);
            int gx = int(floor(x / cell_spacing));
            int gz = int(floor(z / cell_spacing));
            int seed_base = seed + 100;

            float nearest_dist_sq = 3.402823e+38;
            float nearest_floor_h = 0.0;
            float second_dist_sq = 3.402823e+38;

            for (int dzi = -1; dzi <= 1; dzi++) {
                for (int dxi = -1; dxi <= 1; dxi++) {
                    int cxi = gx + dxi;
                    int czi = gz + dzi;
                    float jx = hash_float(cxi, czi, seed_base);
                    float jz = hash_float(cxi, czi, seed_base + 7);
                    float cell_x = (float(cxi) + 0.2 + jx * 0.6) * cell_spacing;
                    float cell_z = (float(czi) + 0.2 + jz * 0.6) * cell_spacing;
                    float fh = hash_float(cxi, czi, seed_base + 13);
                    float flr_h = floor_height_min + fh * (floor_height_max - floor_height_min);

                    float ddx = x - cell_x;
                    float ddz = z - cell_z;
                    float vd = ddx * ddx + ddz * ddz;
                    if (vd < nearest_dist_sq) {
                        second_dist_sq = nearest_dist_sq;
                        nearest_dist_sq = vd;
                        nearest_floor_h = flr_h;
                    } else if (vd < second_dist_sq) {
                        second_dist_sq = vd;
                    }
                }
            }

            float edge_ratio = 1.0 - nearest_dist_sq / (second_dist_sq + 0.001);
            float cell_blend = clampf(edge_ratio * 2.0, 0.0, 1.0);
            cell_blend = smoothstep_manual(cell_blend);

            float floor_dist = nearest_floor_h - y;
            float floor_effect = 0.0;
            if (floor_dist > 0.0) {
                floor_effect = clampf(floor_dist * 0.5, 0.0, 2.0);
            } else if (floor_dist > -1.5) {
                floor_effect = floor_dist * 1.5;
            }

            float ceiling_h = nearest_floor_h + room_height;
            float ceil_dist = y - ceiling_h;
            float ceil_effect = 0.0;
            if (ceil_dist > 0.0 && ceil_dist < 3.0) {
                ceil_effect = -clampf(ceil_dist * 0.5, 0.0, 2.0);
            }

            d += (floor_effect + ceil_effect) * cell_blend * floor_strength;
        }

        // Height clamping
        float floor_blend = max((-y - solid_floor_depth) * 0.1, 0.0);
        d += floor_blend;
        float sky_blend = max((y - sky_height) * 0.1, 0.0);
        d -= sky_blend;

        // Crater system: spawn crater + random craters across the island.
        // Ported verbatim from CPU DCDensity::evaluate_internal. Crater #0
        // (radius = flat_center_radius) SUBSUMES the old standalone flat-center
        // spawn block — do NOT keep both (double-flatten).
        // CraterInfo: cx, cz, radius
        float crater_cx[6];
        float crater_cz[6];
        float crater_r[6];
        int num_craters = 0;

        // Main spawn crater at origin
        crater_cx[num_craters] = 0.0;
        crater_cz[num_craters] = 0.0;
        crater_r[num_craters]  = flat_center_radius;
        num_craters++;

        // Random craters seeded from terrain seed
        for (int ci = 0; ci < 5; ci++) {
            float angle = hash_float(ci, seed, 200) * 6.2831853;
            float cdistr = 40.0 + hash_float(ci, seed, 201) * (island_radius * 0.5);
            float cr = 10.0 + hash_float(ci, seed, 202) * 20.0;
            crater_cx[num_craters] = cos(angle) * cdistr;
            crater_cz[num_craters] = sin(angle) * cdistr;
            crater_r[num_craters]  = cr;
            num_craters++;
        }

        for (int ci = 0; ci < num_craters; ci++) {
            float cdx = x - crater_cx[ci];
            float cdz = z - crater_cz[ci];
            float cdist = sqrt(cdx * cdx + cdz * cdz);
            float cr = crater_r[ci];
            float ctransition = cr * 0.5;

            if (cdist < cr + ctransition) {
                // Flat center
                float blend = 1.0 - clampf(
                    (cdist - cr * 0.3) / (cr * 0.7 + ctransition),
                    0.0, 1.0);
                blend = smoothstep_manual(blend);
                float flat_d = -y * 0.8;
                d = d + (flat_d - d) * blend;

                // Crater rim
                float rim_start = cr * 0.7;
                float rim_end = cr + ctransition * 0.5;
                if (cdist > rim_start && cdist < rim_end) {
                    float rim_t = (cdist - rim_start) / (rim_end - rim_start);
                    rim_t = sin(rim_t * 3.14159);
                    float rim_height = cr * 0.2;
                    float rim_noise = fbm3(PERM_DETAIL,
                        x * detail_frequency * 2.0,
                        y * detail_frequency,
                        z * detail_frequency * 2.0, 2) * rim_height * 0.4;
                    float rim_pull = (rim_height + rim_noise) * rim_t;
                    d += max(0.0, (rim_pull - y) * 0.3) * rim_t;
                }
            }
        }
    } // end flat-terrain-only block

    // The brick and the edits are applied by the TEMPLATE (dc_sample_field in
    // edge_intersect.glsl) — see the note above. Nothing to do here.
    return d;
}