Files
Noita-GPU-Vulkan/shaders/procgrad.glsl
T
2026-10-19 20:59:15 +02:00

120 lines
6.4 KiB
GLSL

// Shared by terrain.comp and render.comp: the 70-scaled simplex noise, the biome-map topology lookup and the gradient of the procedural biomes (FUN_0087e8d0).
// Needs: perm (binding 8), ffloor, ps, atab (areas.glsl) declared before the include.
vec2 grad12(uint g) {
const vec2 G[12] = vec2[12](vec2(1, 1), vec2(-1, 1), vec2(1, -1), vec2(-1, -1), vec2(1, 0), vec2(-1, 0), vec2(1, 0), vec2(-1, 0), vec2(0, 1), vec2(0, -1), vec2(0, 1), vec2(0, -1));
return G[g];
}
float simplex70(float x, float y) {
const float F2 = 0.36602540378, G2 = 0.21132486540;
float s = (x + y) * F2;
int i = ffloor(x + s), j = ffloor(y + s);
float t = float(i + j) * G2;
float x0 = x - (float(i) - t), y0 = y - (float(j) - t);
int i1 = x0 > y0 ? 1 : 0, j1 = 1 - i1;
float x1 = x0 - float(i1) + G2, y1 = y0 - float(j1) + G2;
float x2 = x0 - 1.0 + 2.0 * G2, y2 = y0 - 1.0 + 2.0 * G2;
uint ii = uint(i) & 255u, jj = uint(j) & 255u;
float n = 0.0;
float t0 = 0.5 - x0 * x0 - y0 * y0;
if (t0 >= 0.0) { t0 *= t0; n += t0 * t0 * dot(grad12(ps(ii + ps(jj)) % 12u), vec2(x0, y0)); }
float t1 = 0.5 - x1 * x1 - y1 * y1;
if (t1 >= 0.0) { t1 *= t1; n += t1 * t1 * dot(grad12(ps(ii + uint(i1) + ps(jj + uint(j1))) % 12u), vec2(x1, y1)); }
float t2 = 0.5 - x2 * x2 - y2 * y2;
if (t2 >= 0.0) { t2 *= t2; n += t2 * t2 * dot(grad12(ps(ii + 1u + ps(jj + 1u)) % 12u), vec2(x2, y2)); }
return 70.0 * n;
}
float fbm70(float x, float y) {
const vec4 M = vec4(0.84147, 0.5403, 0.5403, -0.84147);
const float K[3] = float[3](2.02, 2.33, 2.01);
float sum = simplex70(x, y) * 0.5;
float w = 0.25;
for (int o = 0; o < 3; ++o) {
float nx = (M.x * x + M.z * y) * K[o], ny = (M.y * x + M.w * y) * K[o];
x = nx; y = ny;
sum += simplex70(x, y) * w;
w *= 0.5;
}
return sum / 0.9375;
}
// The topology record (offset in atab) of a biome-map tile, 0xFFFFFFFF when there is none (FUN_0087d870: x wraps, y is clamped).
uint tile_rec(int tx, int ty) {
int W = int(atab[2]), H = int(atab[3]);
tx = ((tx % W) + W) % W;
ty = clamp(ty, 0, H - 1);
return atab[atab[4] + uint(ty * W + tx)];
}
// word 20 of the record: biome edge flags (bit 0 noise_biome_edges, bit 1 big_noise_biome_edges, bit 2 fat_biome_edges)
bool bedge(uint rec, uint bit) { return (atab[rec + 20u] & bit) != 0u; }
// FUN_0087d9a0: the biome-map tile whose biome a world cell belongs to. Within 42 cells of the edge of a tile whose neighbour is another biome, the lookup point is
// displaced by noise (wavy biome borders): small (2.5 cells) unless both biomes have big_noise_biome_edges (default on): 11 * simplex + 30 * sin / cos.
ivec2 border_tile(ivec2 w) {
const int ix = w.x + 17920, iy = w.y + 7168;
const int fx = ix & 511, fy = iy & 511;
const int W = int(atab[2]), H = int(atab[3]);
const int tx = ix >> 9, ty = clamp(iy >> 9, 0, H - 1);
const ivec2 here = ivec2(tx, ty);
const uint b = tile_rec(tx, ty);
if (b == 0xFFFFFFFFu || !bedge(b, 1u) || bedge(b, 4u)) return here;
ivec2 n = ivec2(-1000000);
const bool l = fx <= 41, r = fx >= 471, t = fy <= 41, bt = fy >= 471;
if (l && tile_rec(tx - 1, ty) != b) n = ivec2(tx - 1, ty);
else if (t && tile_rec(tx, ty - 1) != b) n = ivec2(tx, ty - 1);
else if (r && tile_rec(tx + 1, ty) != b) n = ivec2(tx + 1, ty);
else if (bt && tile_rec(tx, ty + 1) != b) n = ivec2(tx, ty + 1);
else if (l && t && tile_rec(tx - 1, ty - 1) != b) n = ivec2(tx - 1, ty - 1);
else if (l && bt && tile_rec(tx - 1, ty + 1) != b) n = ivec2(tx - 1, ty + 1);
else if (r && t && tile_rec(tx + 1, ty - 1) != b) n = ivec2(tx + 1, ty - 1);
else if (r && bt && tile_rec(tx + 1, ty + 1) != b) n = ivec2(tx + 1, ty + 1);
if (n.x == -1000000) return here;
const uint nb = tile_rec(n.x, n.y);
if (nb == 0xFFFFFFFFu || !bedge(nb, 1u)) return here;
const float xf = float(ix), yf = float(iy);
float xx, yy;
if (!bedge(b, 2u) || !bedge(nb, 2u)) {
const float d = simplex70(xf * 0.05, yf * 0.05) * 2.5;
xx = xf + d; yy = yf + d;
} else {
const float n11 = simplex70(xf * 0.05, yf * 0.05) * 11.0;
xx = xf + n11 + 30.0 * sin(yf * 0.005);
yy = yf + n11 + 30.0 * cos(xf * 0.005);
}
const ivec2 t2 = ivec2(int(xx) >> 9, int(yy) >> 9);
const uint r2 = tile_rec(t2.x, t2.y);
// the displaced tile must have its area loaded: otherwise the cell would be placeholder rock (a column of rock stood in the sky at the edge of the loaded region)
if (r2 != 0xFFFFFFFFu && bedge(r2, 1u) && t2.x >= 0 && t2.y >= 0 && t2.x < W && t2.y < H && atab[atab[1] + uint(t2.y) * atab[2] + uint(t2.x)] != 0u) return t2;
return here;
}
uint topo_rec(ivec2 w) { // the topology record of the biome the world cell belongs to
const ivec2 t = border_tile(w);
return tile_rec(t.x, t.y);
}
int area_of_cell(ivec2 w) { // the loaded area of that biome, -1 when none
const ivec2 t = border_tile(w);
if (t.x < 0 || t.y < 0 || t.x >= int(atab[2]) || t.y >= int(atab[3])) return -1;
return int(atab[atab[1] + uint(t.y) * atab[2] + uint(t.x)]) - 1;
}
float tf(uint rec, int k) { return uintBitsToFloat(atab[rec + uint(k)]); }
vec2 grad_limits(uint rec, float X) { // FUN_0087eaf0: the start and end of the gradient at X
float sY = tf(rec, 11), eY = tf(rec, 12);
int mode = int((atab[rec] >> 16) & 15u);
if (mode == 0) return vec2(sY, eY);
if (mode == 3) { float d = (X - tf(rec, 13)) * tf(rec, 14); return vec2(sY + d, eY + d); }
float xp = uintBitsToFloat(atab[6]) + X, sc = tf(rec, 15); // the noise offset of the world (header word 6)
float s = (mode == 2 ? fbm70(xp * sc, sY * sc) : float(simplex70(xp * sc, sY * sc))) * tf(rec, 16) + sY;
float e = float(simplex70(xp * sc, eY * sc)) * tf(rec, 17) + eY;
return vec2(s, e);
}
float proc_gradient(ivec2 w, uint rec) { // FUN_0087e8d0: 0 above the start, 1 below the end, linear between; blended with the left neighbour within 41 cells of the tile edge
float X = float(w.x), Y = float(w.y);
vec2 L = grad_limits(rec, X);
int fx = (w.x + 17920) & 511;
if (Y <= 380.0 && fx <= 41) {
ivec2 wl = ivec2(w.x - fx - 1, w.y);
uint recL = topo_rec(wl);
if (recL != 0xFFFFFFFFu && recL != rec) L = mix(L, grad_limits(recL, X), 1.0 - float(fx) / 41.0);
}
if (L.x >= Y) return 0.0;
if (Y >= L.y || L.y == L.x) return 1.0;
return (Y - L.x) / (L.y - L.x);
}