OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / addons / nubis / shaders / noise_perlin_worley.glsl

141 lines · glsl ref:
#[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));
}