OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / shared / proc_simplex.inc

163 lines · inc ref:
// ============================================================================
// ASHIMA SIMPLEX -- ONE TEXT, COMPILED AS BOTH GLSL AND C++.
//
// ⚠ NOT BLENDER'S. Blender's Noise Texture is Perlin (blender_perlin.inc);
// this is the Ashima Arts / webgl-noise `snoise` that proc-nodes' own Simplex
// Noise node uses, and that blender-bridge mirrors as the
// ProcNodes_SimplexNoise group.
//
// ---------------------------------------------------------------------------
// ⛔ THIS FILE REPLACES A BROKEN SCALAR PORT, AND THE HISTORY IS THE POINT.
//
// proc-nodes had TWO implementations: a hand-written GLSL copy of the vector
// form below, and a file-local C++ "scalarisation" of it in noise_nodes.cpp.
// They were never compared -- and they were not the same function:
//
//     scalar C++ (CPU)   simplex(1.20, 0.36, 2.04) =  5557.13
//     vector GLSL (GPU)  simplex(1.20, 0.36, 2.04) =    -0.5551
//
// The CPU was out of range by four orders of magnitude, so the node's Value
// output -- documented 0..1 -- was in the thousands on the CPU and correct on
// the GPU. Every mesh baked on the CPU had different noise from the same graph
// rendered on the GPU, and nothing in the codebase could see it.
//
// TWO THINGS BROKE IN THE SCALARISATION, both visible above:
//
//   1. THE GRADIENT CONSTANTS. `ns = n_ * D.wyz - D.xzx` with D = (0, .5, 1, 2)
//      is (2/7, 0.5/7 - 1, 1/7) = (0.2857, -0.9286, 0.1429). The scalar copy
//      used (2/7, -1.0, 0.5/7) -- two of the three wrong.
//
//   2. THE LANE SHUFFLES CANNOT BE DONE PER CORNER. `b0 = vec4(x.xy, y.xy)`
//      and `a0 = b0.xzyw + s0.xzyw * sh.xxyy` mix the x and y components of
//      DIFFERENT corners into one vec4. A per-corner scalar rewrite has no way
//      to express that, which is why the scalarisation was doomed rather than
//      merely mistyped.
//
// So this file is the VECTOR form -- the one that was right -- with every
// swizzle written out as an explicit constructor, because GLSL has swizzles
// and C++ does not. `vec3(x0.y, x0.z, x0.x)` is what `x0.yzx` means and both
// languages accept it.
// ============================================================================

#ifndef PN_PROC_SIMPLEX
#define PN_PROC_SIMPLEX

#ifndef BN_FN
#define BN_FN
#endif

BN_FN vec3 ps_mod289_v3(vec3 x) {
	return x - floor(x * (1.0f / 289.0f)) * 289.0f;
}

BN_FN vec4 ps_mod289_v4(vec4 x) {
	return x - floor(x * (1.0f / 289.0f)) * 289.0f;
}

BN_FN vec4 ps_permute(vec4 x) {
	return ps_mod289_v4(((x * 34.0f) + 10.0f) * x);
}

BN_FN vec4 ps_taylor_inv_sqrt(vec4 r) {
	return 1.79284291400159f - 0.85373472095314f * r;
}

BN_FN float ps_simplex_noise_3d_v(vec3 v) {
	// C = (1/6, 1/3), D = (0, 0.5, 1, 2) in the original; written out below
	// wherever a swizzle of them was used.
	float Cx = 1.0f / 6.0f;
	float Cy = 1.0f / 3.0f;

	// First corner
	vec3 i = floor(v + vec3(dot(v, vec3(Cy, Cy, Cy))));
	vec3 x0 = v - i + vec3(dot(i, vec3(Cx, Cx, Cx)));

	// Other corners
	vec3 g = step(vec3(x0.y, x0.z, x0.x), vec3(x0.x, x0.y, x0.z));
	vec3 l = 1.0f - g;
	vec3 i1 = min(vec3(g.x, g.y, g.z), vec3(l.z, l.x, l.y));
	vec3 i2 = max(vec3(g.x, g.y, g.z), vec3(l.z, l.x, l.y));

	vec3 x1 = x0 - i1 + vec3(Cx, Cx, Cx);
	vec3 x2 = x0 - i2 + vec3(Cy, Cy, Cy);
	vec3 x3 = x0 - vec3(0.5f, 0.5f, 0.5f);   // D.yyy

	// Permutations
	i = ps_mod289_v3(i);
	vec4 p = ps_permute(ps_permute(ps_permute(
			vec4(i.z, i.z, i.z, i.z) + vec4(0.0f, i1.z, i2.z, 1.0f))
			+ vec4(i.y, i.y, i.y, i.y) + vec4(0.0f, i1.y, i2.y, 1.0f))
			+ vec4(i.x, i.x, i.x, i.x) + vec4(0.0f, i1.x, i2.x, 1.0f));

	// Gradients: 7x7 points over a square, mapped onto an octahedron.
	// ⛔ ns = n_ * D.wyz - D.xzx = n_ * (2, 0.5, 1) - (0, 1, 0). The scalar
	// port that this file replaces got two of these three wrong.
	float n_ = 0.142857142857f; // 1/7
	vec3 ns = vec3(n_ * 2.0f - 0.0f, n_ * 0.5f - 1.0f, n_ * 1.0f - 0.0f);

	vec4 j = p - 49.0f * floor(p * ns.z * ns.z);

	vec4 x_ = floor(j * ns.z);
	vec4 y_ = floor(j - 7.0f * x_);

	vec4 x = x_ * ns.x + vec4(ns.y, ns.y, ns.y, ns.y);
	vec4 y = y_ * ns.x + vec4(ns.y, ns.y, ns.y, ns.y);
	vec4 h = 1.0f - abs(x) - abs(y);

	// ⛔ THE LANES CROSS HERE. b0 holds x.xy and y.xy -- two components of one
	// corner pair and two of another -- and a0 then shuffles them again. This
	// is what a per-corner scalarisation cannot represent.
	vec4 b0 = vec4(x.x, x.y, y.x, y.y);
	vec4 b1 = vec4(x.z, x.w, y.z, y.w);

	vec4 s0 = floor(b0) * 2.0f + 1.0f;
	vec4 s1 = floor(b1) * 2.0f + 1.0f;
	vec4 sh = -step(h, vec4(0.0f));

	vec4 a0 = vec4(b0.x, b0.z, b0.y, b0.w) +
			vec4(s0.x, s0.z, s0.y, s0.w) * vec4(sh.x, sh.x, sh.y, sh.y);
	vec4 a1 = vec4(b1.x, b1.z, b1.y, b1.w) +
			vec4(s1.x, s1.z, s1.y, s1.w) * vec4(sh.z, sh.z, sh.w, sh.w);

	vec3 p0 = vec3(a0.x, a0.y, h.x);
	vec3 p1 = vec3(a0.z, a0.w, h.y);
	vec3 p2 = vec3(a1.x, a1.y, h.z);
	vec3 p3 = vec3(a1.z, a1.w, h.w);

	// Normalise gradients
	vec4 norm = ps_taylor_inv_sqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
	p0 *= norm.x;
	p1 *= norm.y;
	p2 *= norm.z;
	p3 *= norm.w;

	// Mix contributions from the four corners
	vec4 m = max(0.5f - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), vec4(0.0f));
	m = m * m;
	return 105.0f * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
}

// The scalar entry point the nodes call, so the call sites did not change.
BN_FN float ps_simplex_noise_3d(float x, float y, float z) {
	return ps_simplex_noise_3d_v(vec3(x, y, z));
}

BN_FN float ps_fbm_noise_3d(float x, float y, float z, int octaves, float roughness,
		float lacunarity) {
	float value = 0.0f;
	float amplitude = 1.0f;
	float frequency = 1.0f;
	float max_amp = 0.0f;

	for (int i = 0; i < octaves; i++) {
		value += amplitude * ps_simplex_noise_3d_v(vec3(x, y, z) * frequency);
		max_amp += amplitude;
		amplitude *= roughness;
		frequency *= lacunarity;
	}

	return value / max_amp;
}

#endif // PN_PROC_SIMPLEX