#[compute]
#version 450
// Generates a 128³ RGBA 3D noise texture:
// R = Perlin-Worley hybrid (base cloud shape)
// G = Worley noise octave 0 (large cells)
// B = Worley noise octave 1 (medium cells)
// A = Worley noise octave 2 (fine cells)
layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in;
layout(set = 0, binding = 0, rgba16f) uniform restrict writeonly image3D noise_out;
layout(push_constant, std430) uniform Params {
int size;
float frequency;
float seed;
float _pad;
} params;
// --- Hash functions (no texture dependencies) ---
vec3 hash3(vec3 p) {
p = vec3(
dot(p, vec3(127.1, 311.7, 74.7)),
dot(p, vec3(269.5, 183.3, 246.1)),
dot(p, vec3(113.5, 271.9, 124.6))
);
return fract(sin(p) * 43758.5453123);
}
float hash1(vec3 p) {
return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);
}
// --- Perlin noise (3D, smooth) ---
vec3 fade(vec3 t) {
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
}
float grad(float hash, vec3 p) {
int h = int(hash * 16.0) & 15;
float u = h < 8 ? p.x : p.y;
float v = h < 4 ? p.y : (h == 12 || h == 14 ? p.x : p.z);
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float perlin3(vec3 p) {
vec3 pi = floor(p);
vec3 pf = p - pi;
vec3 w = fade(pf);
float n000 = grad(hash1(pi + vec3(0,0,0)), pf - vec3(0,0,0));
float n100 = grad(hash1(pi + vec3(1,0,0)), pf - vec3(1,0,0));
float n010 = grad(hash1(pi + vec3(0,1,0)), pf - vec3(0,1,0));
float n110 = grad(hash1(pi + vec3(1,1,0)), pf - vec3(1,1,0));
float n001 = grad(hash1(pi + vec3(0,0,1)), pf - vec3(0,0,1));
float n101 = grad(hash1(pi + vec3(1,0,1)), pf - vec3(1,0,1));
float n011 = grad(hash1(pi + vec3(0,1,1)), pf - vec3(0,1,1));
float n111 = grad(hash1(pi + vec3(1,1,1)), pf - vec3(1,1,1));
float nx00 = mix(n000, n100, w.x);
float nx10 = mix(n010, n110, w.x);
float nx01 = mix(n001, n101, w.x);
float nx11 = mix(n011, n111, w.x);
float nxy0 = mix(nx00, nx10, w.y);
float nxy1 = mix(nx01, nx11, w.y);
return mix(nxy0, nxy1, w.z) * 0.5 + 0.5; // remap [-1,1] to [0,1]
}
// --- Worley noise (3D, distance to nearest cell center) ---
float worley3(vec3 p, float freq) {
vec3 sp = p * freq;
vec3 id = floor(sp);
vec3 fp = fract(sp);
float min_dist = 1.0;
for (int z = -1; z <= 1; z++) {
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
vec3 offset = vec3(float(x), float(y), float(z));
vec3 cell_id = id + offset;
vec3 cell_point = hash3(cell_id + vec3(params.seed)) + offset;
float d = length(cell_point - fp);
min_dist = min(min_dist, d);
}
}
}
return min_dist;
}
// --- Perlin fBM ---
float perlin_fbm(vec3 p, int octaves, float freq, float gain) {
float sum = 0.0;
float amp = 0.5;
float f = freq;
for (int i = 0; i < octaves; i++) {
sum += perlin3(p * f) * amp;
f *= 2.0;
amp *= gain;
}
return sum;
}
// --- Perlin-Worley hybrid (Schneider's approach) ---
float perlin_worley(vec3 p, float freq) {
float pn = perlin_fbm(p, 4, freq, 0.5);
float wn = 1.0 - worley3(p, freq);
// Remap: use Perlin as base, Worley shapes the boundaries
return clamp(pn + (1.0 - wn) * 0.5, 0.0, 1.0);
}
void main() {
ivec3 gid = ivec3(gl_GlobalInvocationID);
if (gid.x >= params.size || gid.y >= params.size || gid.z >= params.size) return;
// Normalized [0,1] position in the 3D texture (tiles seamlessly)
vec3 uvw = (vec3(gid) + 0.5) / float(params.size);
vec3 pos = uvw + vec3(params.seed);
float freq = params.frequency;
// R: Perlin-Worley hybrid (macro cloud shape)
float pw = perlin_worley(pos, freq);
// G, B, A: Worley noise at increasing frequencies (erosion octaves)
float w0 = 1.0 - worley3(pos, freq * 2.0);
float w1 = 1.0 - worley3(pos, freq * 4.0);
float w2 = 1.0 - worley3(pos, freq * 8.0);
imageStore(noise_out, gid, vec4(pw, w0, w1, w2));
}