OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / 4501578

noise: the hand-written GLSL library is gone -- one implementation, everywhere
The last two nodes come off it. DisplaceNode and the Simplex Noise node each emitted from GpuCompiler's hand-written library while their CPU ran a FILE-LOCAL copy in noise_nodes.cpp -- 376 lines of it, including a FOURTH Jenkins Lookup3 hash. Neither pair had ever been compared. file-local perlin vs blender_perlin.inc 660000/660000 bit-exact file-local simplex vs proc_simplex.inc 240000/240000 bit-exact emitted density shader 367 lines REMOVED, 0 added, proc_density byte-identical (md5) op parity 770/770 · golden 14/14 · noise parity 5300/5300 terrain path correlation 1.000000, RMS 1.967e-07 -- unmoved Because the deleted C++ agreed with the .inc replacing it to the bit, this is a relocation and not a change, and the golden shader proves it from the other side: what left the emitted text is exactly the twenty dead library functions, and the density function itself hashes the same. `get_noise_library_glsl` is now `get_erosion_library_glsl`, which is what it actually returns. The batch path no longer SNIFFS emitted text for names like "perlin_" to decide what to inject; nodes declare what they need through compile_glsl_prelude(), the way the density path always has. Of the twenty functions deleted, only TWO had a caller. The other eighteen -- a Voronoi, four musgrave forms, five Perlin fractal variants -- were emitted by nothing and had been dead for as long as anyone can tell. --- ⛔ AND THE SIMPLEX NOISE NODE IS BROKEN. Found by this work, not caused by it. simplex(0.50, 0.15, 0.85) = -7349.26 (a simplex must be in [-1, 1]) simplex(1.20, 0.36, 2.04) = 5557.13 fbm, 4 octaves = 15221.4 SimplexNoiseNode "Value" = 7611 (documented 0..1) Range is ~±1e5 at EVERY magnitude tested, |p| ~ 1 included, so it is not a large-coordinate precision effect -- my first reading of it was wrong and the measurement corrected me. Something in the port of Ashima's gradient or its 105.0 scaling is wrong. ⚠ THE REASON NOBODY SAW IT is the thing this whole exercise is about. There were two implementations, and they agreed with each other: the file-local C++ and the hand-written GLSL computed the same wrong answer, so every comparison anyone could run came back green. It took comparing against what a simplex IS to find, which no parity gate between two copies can do. Not fixed here. Fixing the arithmetic changes what the node outputs, and the Blender-side ProcNodes_SimplexNoise group has to be compared first to know what the target is. The gate covers what can be honestly held -- the raw lattice at ordinary coordinates, where the two machines agree to 5.8e-06 -- and the octave loop is deliberately NOT gated, with the reason written where the next person will look, because widening a tolerance until a broken function passes is reporting agreement rather than measuring it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4501578692fd3c84c9da704e57492520f5c2eb5f
by plankatron <fleeky@gmail.com> · 2026-09-06T01:01:20.000Z

Changed files (11) +373 -1128

SConstruct +7 -0
……@@ -78,6 +78,13 @@ glsl_inc_gen = [
7878 "src/shared/blender_ramp_blend.inc",
7979 "python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_RAMP_BLEND_GLSL",
8080 ),
81 # Ashima simplex, sixth .inc. NOT Blender's -- it is proc-nodes' own
82 # Simplex Noise node, which blender-bridge mirrors as a node group.
83 env.Command(
84 "src/gpu/proc_simplex_glsl.gen.h",
85 "src/shared/proc_simplex.inc",
86 "python3 tools/gen_glsl_inc.py $SOURCE $TARGET PROC_SIMPLEX_GLSL",
87 ),
8188]
8289
8390sources = (
demo/golden/map_dc_erosion_density.glsl +0 -367
……@@ -1,371 +1,4 @@
11
2vec3 mod289_v3(vec3 x) {
3 return x - floor(x * (1.0 / 289.0)) * 289.0;
4}
5
6vec4 mod289_v4(vec4 x) {
7 return x - floor(x * (1.0 / 289.0)) * 289.0;
8}
9
10vec4 permute(vec4 x) {
11 return mod289_v4(((x * 34.0) + 10.0) * x);
12}
13
14vec4 taylor_inv_sqrt(vec4 r) {
15 return 1.79284291400159 - 0.85373472095314 * r;
16}
17
18float simplex_noise_3d(vec3 v) {
19 const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
20 const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
21
22 // First corner
23 vec3 i = floor(v + dot(v, C.yyy));
24 vec3 x0 = v - i + dot(i, C.xxx);
25
26 // Other corners
27 vec3 g = step(x0.yzx, x0.xyz);
28 vec3 l = 1.0 - g;
29 vec3 i1 = min(g.xyz, l.zxy);
30 vec3 i2 = max(g.xyz, l.zxy);
31
32 vec3 x1 = x0 - i1 + C.xxx;
33 vec3 x2 = x0 - i2 + C.yyy;
34 vec3 x3 = x0 - D.yyy;
35
36 // Permutations
37 i = mod289_v3(i);
38 vec4 p = permute(permute(permute(
39 i.z + vec4(0.0, i1.z, i2.z, 1.0))
40 + i.y + vec4(0.0, i1.y, i2.y, 1.0))
41 + i.x + vec4(0.0, i1.x, i2.x, 1.0));
42
43 // Gradients: 7x7 points over a square, mapped onto an octahedron
44 float n_ = 0.142857142857; // 1.0 / 7.0
45 vec3 ns = n_ * D.wyz - D.xzx;
46
47 vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
48
49 vec4 x_ = floor(j * ns.z);
50 vec4 y_ = floor(j - 7.0 * x_);
51
52 vec4 x = x_ * ns.x + ns.yyyy;
53 vec4 y = y_ * ns.x + ns.yyyy;
54 vec4 h = 1.0 - abs(x) - abs(y);
55
56 vec4 b0 = vec4(x.xy, y.xy);
57 vec4 b1 = vec4(x.zw, y.zw);
58
59 vec4 s0 = floor(b0) * 2.0 + 1.0;
60 vec4 s1 = floor(b1) * 2.0 + 1.0;
61 vec4 sh = -step(h, vec4(0.0));
62
63 vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
64 vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
65
66 vec3 p0 = vec3(a0.xy, h.x);
67 vec3 p1 = vec3(a0.zw, h.y);
68 vec3 p2 = vec3(a1.xy, h.z);
69 vec3 p3 = vec3(a1.zw, h.w);
70
71 // Normalise gradients
72 vec4 norm = taylor_inv_sqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
73 p0 *= norm.x;
74 p1 *= norm.y;
75 p2 *= norm.z;
76 p3 *= norm.w;
77
78 // Mix contributions from the four corners
79 vec4 m = max(0.5 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
80 m = m * m;
81 return 105.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
82}
83
84float noise_fbm(vec3 pos, int octaves, float roughness, float lacunarity) {
85 float value = 0.0;
86 float amplitude = 1.0;
87 float frequency = 1.0;
88 float max_amp = 0.0;
89
90 for (int i = 0; i < octaves; i++) {
91 value += amplitude * simplex_noise_3d(pos * frequency);
92 max_amp += amplitude;
93 amplitude *= roughness;
94 frequency *= lacunarity;
95 }
96
97 return value / max_amp;
98}
99
100// Voronoi noise — distance to nearest cell center
101float noise_voronoi(vec3 pos) {
102 vec3 i_pos = floor(pos);
103 vec3 f_pos = fract(pos);
104 float min_dist = 1e10;
105
106 for (int x = -1; x <= 1; x++) {
107 for (int y = -1; y <= 1; y++) {
108 for (int z = -1; z <= 1; z++) {
109 vec3 offset = vec3(float(x), float(y), float(z));
110 vec3 cell = i_pos + offset;
111 // Hash-based random point in cell
112 vec3 r = fract(sin(vec3(
113 dot(cell, vec3(127.1, 311.7, 74.7)),
114 dot(cell, vec3(269.5, 183.3, 246.1)),
115 dot(cell, vec3(113.5, 271.9, 124.6))
116 )) * 43758.5453);
117 vec3 cell_pos = offset + r - f_pos;
118 float d = length(cell_pos);
119 min_dist = min(min_dist, d);
120 }
121 }
122 }
123
124 return min_dist;
125}
126
127// Musgrave Multifractal
128float noise_musgrave_multi(vec3 pos, int octaves, float roughness, float lacunarity) {
129 float value = 1.0;
130 float frequency = 1.0;
131
132 for (int i = 0; i < octaves; i++) {
133 float n = simplex_noise_3d(pos * frequency);
134 value *= (n * roughness + 1.0);
135 frequency *= lacunarity;
136 }
137
138 return value;
139}
140
141// Musgrave Heterogeneous
142float noise_musgrave_hetero(vec3 pos, int octaves, float roughness, float lacunarity) {
143 float value = simplex_noise_3d(pos) + 1.0;
144 float frequency = lacunarity;
145 float amplitude = roughness;
146
147 for (int i = 1; i < octaves; i++) {
148 float n = (simplex_noise_3d(pos * frequency) + 1.0) * amplitude * value;
149 value += n;
150 frequency *= lacunarity;
151 amplitude *= roughness;
152 }
153
154 return value;
155}
156
157// Musgrave Hybrid Multifractal
158float noise_musgrave_hybrid(vec3 pos, int octaves, float roughness, float lacunarity) {
159 float value = simplex_noise_3d(pos) + 1.0;
160 float weight = value;
161 float frequency = lacunarity;
162 float amplitude = roughness;
163
164 for (int i = 1; i < octaves; i++) {
165 weight = min(weight, 1.0);
166 float n = (simplex_noise_3d(pos * frequency) + 1.0) * amplitude;
167 value += weight * n;
168 weight *= n;
169 frequency *= lacunarity;
170 amplitude *= roughness;
171 }
172
173 return value;
174}
175
176// Musgrave Ridged Multifractal
177float noise_musgrave_ridged(vec3 pos, int octaves, float roughness, float lacunarity) {
178 float value = 0.0;
179 float frequency = 1.0;
180 float amplitude = 1.0;
181 float weight = 1.0;
182
183 for (int i = 0; i < octaves; i++) {
184 float n = abs(simplex_noise_3d(pos * frequency));
185 n = 1.0 - n;
186 n *= n;
187 n *= weight;
188 weight = clamp(n * 2.0, 0.0, 1.0);
189 value += n * amplitude;
190 frequency *= lacunarity;
191 amplitude *= roughness;
192 }
193
194 return value;
195}
196
197// =========================================================================
198// Perlin noise — Blender's exact implementation (Jenkins Lookup3 hash)
199// =========================================================================
200
201#define PERLIN_ROT(x, k) (((x) << (k)) | ((x) >> (32u - (k))))
202
203uint jenkins_hash3(uint kx, uint ky, uint kz) {
204 uint a, b, c;
205 a = b = c = 0xdeadbeefu + (3u << 2u) + 13u;
206 c += kz; b += ky; a += kx;
207 // hash_bit_final
208 c ^= b; c -= PERLIN_ROT(b, 14u);
209 a ^= c; a -= PERLIN_ROT(c, 11u);
210 b ^= a; b -= PERLIN_ROT(a, 25u);
211 c ^= b; c -= PERLIN_ROT(b, 16u);
212 a ^= c; a -= PERLIN_ROT(c, 4u);
213 b ^= a; b -= PERLIN_ROT(a, 14u);
214 c ^= b; c -= PERLIN_ROT(b, 24u);
215 return c;
216}
217
218float perlin_fade(float t) {
219 return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
220}
221
222float perlin_negate_if(float val, uint cond) {
223 return (cond != 0u) ? -val : val;
224}
225
226float perlin_noise_grad(uint hash, float x, float y, float z) {
227 uint h = hash & 15u;
228 float u = h < 8u ? x : y;
229 float vt = ((h == 12u) || (h == 14u)) ? x : z;
230 float v = h < 4u ? y : vt;
231 return perlin_negate_if(u, h & 1u) + perlin_negate_if(v, h & 2u);
232}
233
234float perlin_tri_mix(
235 float v0, float v1, float v2, float v3,
236 float v4, float v5, float v6, float v7,
237 float x, float y, float z) {
238 float x1 = 1.0 - x;
239 float y1 = 1.0 - y;
240 float z1 = 1.0 - z;
241 return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) +
242 z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x));
243}
244
245float perlin_noise_3d(vec3 p) {
246 int X = int(floor(p.x)); float fx = p.x - floor(p.x);
247 int Y = int(floor(p.y)); float fy = p.y - floor(p.y);
248 int Z = int(floor(p.z)); float fz = p.z - floor(p.z);
249
250 float u = perlin_fade(fx);
251 float v = perlin_fade(fy);
252 float w = perlin_fade(fz);
253
254 return perlin_tri_mix(
255 perlin_noise_grad(jenkins_hash3(uint(X), uint(Y), uint(Z)), fx, fy, fz),
256 perlin_noise_grad(jenkins_hash3(uint(X+1), uint(Y), uint(Z)), fx-1, fy, fz),
257 perlin_noise_grad(jenkins_hash3(uint(X), uint(Y+1), uint(Z)), fx, fy-1, fz),
258 perlin_noise_grad(jenkins_hash3(uint(X+1), uint(Y+1), uint(Z)), fx-1, fy-1, fz),
259 perlin_noise_grad(jenkins_hash3(uint(X), uint(Y), uint(Z+1)), fx, fy, fz-1),
260 perlin_noise_grad(jenkins_hash3(uint(X+1), uint(Y), uint(Z+1)), fx-1, fy, fz-1),
261 perlin_noise_grad(jenkins_hash3(uint(X), uint(Y+1), uint(Z+1)), fx, fy-1, fz-1),
262 perlin_noise_grad(jenkins_hash3(uint(X+1), uint(Y+1), uint(Z+1)), fx-1, fy-1, fz-1),
263 u, v, w);
264}
265
266float perlin_signed_3d(vec3 p) {
267 // Precision correction for very large coordinates (matches Blender noise.cc).
268 // For |x| < 1000000, this is a no-op (c=0, fmod returns x unchanged).
269 // CRITICAL: Use sign-preserving fmod, NOT GLSL mod().
270 // GLSL mod(x, y) = x - y*floor(x/y) which wraps negatives to positive.
271 // C++ fmodf(x, y) preserves the sign of x. For x in [-100000, 100000],
272 // fmodf is identity. We replicate this with trunc-based modulo.
273 vec3 c = vec3(
274 abs(p.x) >= 1000000.0 ? 0.5 : 0.0,
275 abs(p.y) >= 1000000.0 ? 0.5 : 0.0,
276 abs(p.z) >= 1000000.0 ? 0.5 : 0.0);
277 // trunc-based fmod: x - trunc(x/y) * y (preserves sign like C++ fmodf)
278 p = p - trunc(p / 100000.0) * 100000.0 + c;
279 return perlin_noise_3d(p) * 0.9820;
280}
281
282// Perlin fBM (matches Blender exactly, including fractional detail interpolation)
283float perlin_fbm(vec3 pos, int octaves, float roughness, float lacunarity, bool do_normalize) {
284 float fscale = 1.0;
285 float amp = 1.0;
286 float maxamp = 0.0;
287 float sum = 0.0;
288 float detail = float(octaves);
289
290 for (int i = 0; i <= octaves; i++) {
291 sum += perlin_signed_3d(pos * fscale) * amp;
292 maxamp += amp;
293 amp *= roughness;
294 fscale *= lacunarity;
295 }
296 // Fractional detail interpolation (matches CPU + Blender)
297 float rmd = detail - floor(detail);
298 if (rmd != 0.0) {
299 float t = perlin_signed_3d(pos * fscale);
300 float sum2 = sum + t * amp;
301 float maxamp2 = maxamp + amp;
302 return do_normalize ?
303 (1.0 - rmd) * (0.5 * sum / maxamp + 0.5) + rmd * (0.5 * sum2 / maxamp2 + 0.5) :
304 (1.0 - rmd) * sum + rmd * sum2;
305 }
306 return do_normalize ? 0.5 * sum / maxamp + 0.5 : sum;
307}
308
309// Perlin Multifractal
310float perlin_multi_fractal(vec3 pos, int octaves, float roughness, float lacunarity) {
311 float value = 1.0;
312 float pwr = 1.0;
313 for (int i = 0; i <= octaves; i++) {
314 value *= (pwr * perlin_signed_3d(pos) + 1.0);
315 pwr *= roughness;
316 pos *= lacunarity;
317 }
318 return value;
319}
320
321// Perlin Hybrid Multifractal
322float perlin_hybrid(vec3 pos, int octaves, float roughness, float lacunarity, float offset, float gain) {
323 float pwr = 1.0;
324 float value = 0.0;
325 float weight = 1.0;
326 for (int i = 0; (weight > 0.001) && (i <= octaves); i++) {
327 weight = min(weight, 1.0);
328 float signal = (perlin_signed_3d(pos) + offset) * pwr;
329 pwr *= roughness;
330 value += weight * signal;
331 weight *= gain * signal;
332 pos *= lacunarity;
333 }
334 return value;
335}
336
337// Perlin Ridged Multifractal
338float perlin_ridged(vec3 pos, int octaves, float roughness, float lacunarity, float offset, float gain) {
339 float pwr = roughness;
340 float signal = offset - abs(perlin_signed_3d(pos));
341 signal *= signal;
342 float value = signal;
343 for (int i = 1; i <= octaves; i++) {
344 pos *= lacunarity;
345 float weight = clamp(signal * gain, 0.0, 1.0);
346 signal = offset - abs(perlin_signed_3d(pos));
347 signal *= signal;
348 signal *= weight;
349 value += signal * pwr;
350 pwr *= roughness;
351 }
352 return value;
353}
354
355// Perlin Hetero Terrain
356float perlin_hetero(vec3 pos, int octaves, float roughness, float lacunarity, float offset) {
357 float pwr = roughness;
358 float value = offset + perlin_signed_3d(pos);
359 pos *= lacunarity;
360 for (int i = 1; i <= octaves; i++) {
361 float increment = (perlin_signed_3d(pos) + offset) * pwr * value;
362 value += increment;
363 pwr *= roughness;
364 pos *= lacunarity;
365 }
366 return value;
367}
368
3692// =========================================================================
3703// erosion_filter.inc — THE erosion filter. One copy. Compiled BOTH as GLSL
3714// and as C++ from this same text.
src/gpu/gpu_compiler.cpp +39 -376
……@@ -9,375 +9,27 @@ namespace godot {
99
1010// --- Noise library ---
1111
12String GpuCompiler::get_noise_library_glsl() {
13 return String(R"(
14vec3 mod289_v3(vec3 x) {
15 return x - floor(x * (1.0 / 289.0)) * 289.0;
16}
17
18vec4 mod289_v4(vec4 x) {
19 return x - floor(x * (1.0 / 289.0)) * 289.0;
20}
21
22vec4 permute(vec4 x) {
23 return mod289_v4(((x * 34.0) + 10.0) * x);
24}
25
26vec4 taylor_inv_sqrt(vec4 r) {
27 return 1.79284291400159 - 0.85373472095314 * r;
28}
29
30float simplex_noise_3d(vec3 v) {
31 const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
32 const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
33
34 // First corner
35 vec3 i = floor(v + dot(v, C.yyy));
36 vec3 x0 = v - i + dot(i, C.xxx);
37
38 // Other corners
39 vec3 g = step(x0.yzx, x0.xyz);
40 vec3 l = 1.0 - g;
41 vec3 i1 = min(g.xyz, l.zxy);
42 vec3 i2 = max(g.xyz, l.zxy);
43
44 vec3 x1 = x0 - i1 + C.xxx;
45 vec3 x2 = x0 - i2 + C.yyy;
46 vec3 x3 = x0 - D.yyy;
47
48 // Permutations
49 i = mod289_v3(i);
50 vec4 p = permute(permute(permute(
51 i.z + vec4(0.0, i1.z, i2.z, 1.0))
52 + i.y + vec4(0.0, i1.y, i2.y, 1.0))
53 + i.x + vec4(0.0, i1.x, i2.x, 1.0));
54
55 // Gradients: 7x7 points over a square, mapped onto an octahedron
56 float n_ = 0.142857142857; // 1.0 / 7.0
57 vec3 ns = n_ * D.wyz - D.xzx;
58
59 vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
60
61 vec4 x_ = floor(j * ns.z);
62 vec4 y_ = floor(j - 7.0 * x_);
63
64 vec4 x = x_ * ns.x + ns.yyyy;
65 vec4 y = y_ * ns.x + ns.yyyy;
66 vec4 h = 1.0 - abs(x) - abs(y);
67
68 vec4 b0 = vec4(x.xy, y.xy);
69 vec4 b1 = vec4(x.zw, y.zw);
70
71 vec4 s0 = floor(b0) * 2.0 + 1.0;
72 vec4 s1 = floor(b1) * 2.0 + 1.0;
73 vec4 sh = -step(h, vec4(0.0));
74
75 vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
76 vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
77
78 vec3 p0 = vec3(a0.xy, h.x);
79 vec3 p1 = vec3(a0.zw, h.y);
80 vec3 p2 = vec3(a1.xy, h.z);
81 vec3 p3 = vec3(a1.zw, h.w);
82
83 // Normalise gradients
84 vec4 norm = taylor_inv_sqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
85 p0 *= norm.x;
86 p1 *= norm.y;
87 p2 *= norm.z;
88 p3 *= norm.w;
89
90 // Mix contributions from the four corners
91 vec4 m = max(0.5 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
92 m = m * m;
93 return 105.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
94}
95
96float noise_fbm(vec3 pos, int octaves, float roughness, float lacunarity) {
97 float value = 0.0;
98 float amplitude = 1.0;
99 float frequency = 1.0;
100 float max_amp = 0.0;
101
102 for (int i = 0; i < octaves; i++) {
103 value += amplitude * simplex_noise_3d(pos * frequency);
104 max_amp += amplitude;
105 amplitude *= roughness;
106 frequency *= lacunarity;
107 }
108
109 return value / max_amp;
110}
111
112// Voronoi noise — distance to nearest cell center
113float noise_voronoi(vec3 pos) {
114 vec3 i_pos = floor(pos);
115 vec3 f_pos = fract(pos);
116 float min_dist = 1e10;
117
118 for (int x = -1; x <= 1; x++) {
119 for (int y = -1; y <= 1; y++) {
120 for (int z = -1; z <= 1; z++) {
121 vec3 offset = vec3(float(x), float(y), float(z));
122 vec3 cell = i_pos + offset;
123 // Hash-based random point in cell
124 vec3 r = fract(sin(vec3(
125 dot(cell, vec3(127.1, 311.7, 74.7)),
126 dot(cell, vec3(269.5, 183.3, 246.1)),
127 dot(cell, vec3(113.5, 271.9, 124.6))
128 )) * 43758.5453);
129 vec3 cell_pos = offset + r - f_pos;
130 float d = length(cell_pos);
131 min_dist = min(min_dist, d);
132 }
133 }
134 }
135
136 return min_dist;
137}
138
139// Musgrave Multifractal
140float noise_musgrave_multi(vec3 pos, int octaves, float roughness, float lacunarity) {
141 float value = 1.0;
142 float frequency = 1.0;
143
144 for (int i = 0; i < octaves; i++) {
145 float n = simplex_noise_3d(pos * frequency);
146 value *= (n * roughness + 1.0);
147 frequency *= lacunarity;
148 }
149
150 return value;
151}
152
153// Musgrave Heterogeneous
154float noise_musgrave_hetero(vec3 pos, int octaves, float roughness, float lacunarity) {
155 float value = simplex_noise_3d(pos) + 1.0;
156 float frequency = lacunarity;
157 float amplitude = roughness;
158
159 for (int i = 1; i < octaves; i++) {
160 float n = (simplex_noise_3d(pos * frequency) + 1.0) * amplitude * value;
161 value += n;
162 frequency *= lacunarity;
163 amplitude *= roughness;
164 }
165
166 return value;
167}
168
169// Musgrave Hybrid Multifractal
170float noise_musgrave_hybrid(vec3 pos, int octaves, float roughness, float lacunarity) {
171 float value = simplex_noise_3d(pos) + 1.0;
172 float weight = value;
173 float frequency = lacunarity;
174 float amplitude = roughness;
175
176 for (int i = 1; i < octaves; i++) {
177 weight = min(weight, 1.0);
178 float n = (simplex_noise_3d(pos * frequency) + 1.0) * amplitude;
179 value += weight * n;
180 weight *= n;
181 frequency *= lacunarity;
182 amplitude *= roughness;
183 }
184
185 return value;
186}
187
188// Musgrave Ridged Multifractal
189float noise_musgrave_ridged(vec3 pos, int octaves, float roughness, float lacunarity) {
190 float value = 0.0;
191 float frequency = 1.0;
192 float amplitude = 1.0;
193 float weight = 1.0;
194
195 for (int i = 0; i < octaves; i++) {
196 float n = abs(simplex_noise_3d(pos * frequency));
197 n = 1.0 - n;
198 n *= n;
199 n *= weight;
200 weight = clamp(n * 2.0, 0.0, 1.0);
201 value += n * amplitude;
202 frequency *= lacunarity;
203 amplitude *= roughness;
204 }
205
206 return value;
207}
208
209// =========================================================================
210// Perlin noise — Blender's exact implementation (Jenkins Lookup3 hash)
211// =========================================================================
212
213#define PERLIN_ROT(x, k) (((x) << (k)) | ((x) >> (32u - (k))))
214
215uint jenkins_hash3(uint kx, uint ky, uint kz) {
216 uint a, b, c;
217 a = b = c = 0xdeadbeefu + (3u << 2u) + 13u;
218 c += kz; b += ky; a += kx;
219 // hash_bit_final
220 c ^= b; c -= PERLIN_ROT(b, 14u);
221 a ^= c; a -= PERLIN_ROT(c, 11u);
222 b ^= a; b -= PERLIN_ROT(a, 25u);
223 c ^= b; c -= PERLIN_ROT(b, 16u);
224 a ^= c; a -= PERLIN_ROT(c, 4u);
225 b ^= a; b -= PERLIN_ROT(a, 14u);
226 c ^= b; c -= PERLIN_ROT(b, 24u);
227 return c;
228}
229
230float perlin_fade(float t) {
231 return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
232}
233
234float perlin_negate_if(float val, uint cond) {
235 return (cond != 0u) ? -val : val;
236}
237
238float perlin_noise_grad(uint hash, float x, float y, float z) {
239 uint h = hash & 15u;
240 float u = h < 8u ? x : y;
241 float vt = ((h == 12u) || (h == 14u)) ? x : z;
242 float v = h < 4u ? y : vt;
243 return perlin_negate_if(u, h & 1u) + perlin_negate_if(v, h & 2u);
244}
245
246float perlin_tri_mix(
247 float v0, float v1, float v2, float v3,
248 float v4, float v5, float v6, float v7,
249 float x, float y, float z) {
250 float x1 = 1.0 - x;
251 float y1 = 1.0 - y;
252 float z1 = 1.0 - z;
253 return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) +
254 z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x));
255}
256
257float perlin_noise_3d(vec3 p) {
258 int X = int(floor(p.x)); float fx = p.x - floor(p.x);
259 int Y = int(floor(p.y)); float fy = p.y - floor(p.y);
260 int Z = int(floor(p.z)); float fz = p.z - floor(p.z);
261
262 float u = perlin_fade(fx);
263 float v = perlin_fade(fy);
264 float w = perlin_fade(fz);
265
266 return perlin_tri_mix(
267 perlin_noise_grad(jenkins_hash3(uint(X), uint(Y), uint(Z)), fx, fy, fz),
268 perlin_noise_grad(jenkins_hash3(uint(X+1), uint(Y), uint(Z)), fx-1, fy, fz),
269 perlin_noise_grad(jenkins_hash3(uint(X), uint(Y+1), uint(Z)), fx, fy-1, fz),
270 perlin_noise_grad(jenkins_hash3(uint(X+1), uint(Y+1), uint(Z)), fx-1, fy-1, fz),
271 perlin_noise_grad(jenkins_hash3(uint(X), uint(Y), uint(Z+1)), fx, fy, fz-1),
272 perlin_noise_grad(jenkins_hash3(uint(X+1), uint(Y), uint(Z+1)), fx-1, fy, fz-1),
273 perlin_noise_grad(jenkins_hash3(uint(X), uint(Y+1), uint(Z+1)), fx, fy-1, fz-1),
274 perlin_noise_grad(jenkins_hash3(uint(X+1), uint(Y+1), uint(Z+1)), fx-1, fy-1, fz-1),
275 u, v, w);
276}
277
278float perlin_signed_3d(vec3 p) {
279 // Precision correction for very large coordinates (matches Blender noise.cc).
280 // For |x| < 1000000, this is a no-op (c=0, fmod returns x unchanged).
281 // CRITICAL: Use sign-preserving fmod, NOT GLSL mod().
282 // GLSL mod(x, y) = x - y*floor(x/y) which wraps negatives to positive.
283 // C++ fmodf(x, y) preserves the sign of x. For x in [-100000, 100000],
284 // fmodf is identity. We replicate this with trunc-based modulo.
285 vec3 c = vec3(
286 abs(p.x) >= 1000000.0 ? 0.5 : 0.0,
287 abs(p.y) >= 1000000.0 ? 0.5 : 0.0,
288 abs(p.z) >= 1000000.0 ? 0.5 : 0.0);
289 // trunc-based fmod: x - trunc(x/y) * y (preserves sign like C++ fmodf)
290 p = p - trunc(p / 100000.0) * 100000.0 + c;
291 return perlin_noise_3d(p) * 0.9820;
292}
293
294// Perlin fBM (matches Blender exactly, including fractional detail interpolation)
295float perlin_fbm(vec3 pos, int octaves, float roughness, float lacunarity, bool do_normalize) {
296 float fscale = 1.0;
297 float amp = 1.0;
298 float maxamp = 0.0;
299 float sum = 0.0;
300 float detail = float(octaves);
301
302 for (int i = 0; i <= octaves; i++) {
303 sum += perlin_signed_3d(pos * fscale) * amp;
304 maxamp += amp;
305 amp *= roughness;
306 fscale *= lacunarity;
307 }
308 // Fractional detail interpolation (matches CPU + Blender)
309 float rmd = detail - floor(detail);
310 if (rmd != 0.0) {
311 float t = perlin_signed_3d(pos * fscale);
312 float sum2 = sum + t * amp;
313 float maxamp2 = maxamp + amp;
314 return do_normalize ?
315 (1.0 - rmd) * (0.5 * sum / maxamp + 0.5) + rmd * (0.5 * sum2 / maxamp2 + 0.5) :
316 (1.0 - rmd) * sum + rmd * sum2;
317 }
318 return do_normalize ? 0.5 * sum / maxamp + 0.5 : sum;
319}
320
321// Perlin Multifractal
322float perlin_multi_fractal(vec3 pos, int octaves, float roughness, float lacunarity) {
323 float value = 1.0;
324 float pwr = 1.0;
325 for (int i = 0; i <= octaves; i++) {
326 value *= (pwr * perlin_signed_3d(pos) + 1.0);
327 pwr *= roughness;
328 pos *= lacunarity;
329 }
330 return value;
331}
332
333// Perlin Hybrid Multifractal
334float perlin_hybrid(vec3 pos, int octaves, float roughness, float lacunarity, float offset, float gain) {
335 float pwr = 1.0;
336 float value = 0.0;
337 float weight = 1.0;
338 for (int i = 0; (weight > 0.001) && (i <= octaves); i++) {
339 weight = min(weight, 1.0);
340 float signal = (perlin_signed_3d(pos) + offset) * pwr;
341 pwr *= roughness;
342 value += weight * signal;
343 weight *= gain * signal;
344 pos *= lacunarity;
345 }
346 return value;
347}
348
349// Perlin Ridged Multifractal
350float perlin_ridged(vec3 pos, int octaves, float roughness, float lacunarity, float offset, float gain) {
351 float pwr = roughness;
352 float signal = offset - abs(perlin_signed_3d(pos));
353 signal *= signal;
354 float value = signal;
355 for (int i = 1; i <= octaves; i++) {
356 pos *= lacunarity;
357 float weight = clamp(signal * gain, 0.0, 1.0);
358 signal = offset - abs(perlin_signed_3d(pos));
359 signal *= signal;
360 signal *= weight;
361 value += signal * pwr;
362 pwr *= roughness;
363 }
364 return value;
365}
366
367// Perlin Hetero Terrain
368float perlin_hetero(vec3 pos, int octaves, float roughness, float lacunarity, float offset) {
369 float pwr = roughness;
370 float value = offset + perlin_signed_3d(pos);
371 pos *= lacunarity;
372 for (int i = 1; i <= octaves; i++) {
373 float increment = (perlin_signed_3d(pos) + offset) * pwr * value;
374 value += increment;
375 pwr *= roughness;
376 pos *= lacunarity;
377 }
378 return value;
379}
380)")
12String GpuCompiler::get_erosion_library_glsl() {
13 // THE HAND-WRITTEN NOISE LIBRARY THAT USED TO BE HERE IS GONE.
14 //
15 // ~366 lines of GLSL: an Ashima simplex, a Jenkins hash, a Perlin with its
16 // five fractal variants, a Voronoi and four musgrave forms. Every one of
17 // them was a SECOND implementation of something proc-nodes also had in
18 // C++, and nothing compared any pair. Two of them were live -- noise_fbm
19 // for the Simplex Noise node and perlin_fbm for Displace -- and the rest
20 // had no caller at all.
21 //
22 // Both live ones now emit calls into src/shared/*.inc, the same text their
23 // execute() runs, and they bring it with them through
24 // compile_glsl_prelude(). The deletion is a relocation and not a change:
25 // the C++ that was deleted alongside this agreed with the .inc replacing
26 // it on 660000/660000 comparisons for Perlin and 240000/240000 for
27 // simplex, bit-for-bit.
28 //
29 // What remains below is the erosion filter and its wrapper globals, which
30 // are already one text (src/shared/erosion_filter.inc) and are injected
31 // wholesale for the erosion family.
32 return String()
38133 // THE erosion filter, generated from src/shared/erosion_filter.inc by
38234 // tools/gen_erosion_glsl.py. It used to be inlined in the literal above,
38335 // and dc-terrain carried a hand-written C++ transcription of it that had
……@@ -509,6 +161,7 @@ String GpuCompiler::compile_batch(
509161 ctx.graph = p_graph;
510162
511163 String main_body;
164 String preludes;
512165 bool needs_noise = false;
513166
514167 for (int i = 0; i < p_nodes.size(); i++) {
……@@ -522,11 +175,18 @@ String GpuCompiler::compile_batch(
522175 continue;
523176 }
524177
525 if (glsl.find("noise_fbm") != -1 || glsl.find("simplex_noise_3d") != -1 ||
526 glsl.find("noise_voronoi") != -1 || glsl.find("noise_musgrave") != -1 ||
527 glsl.find("perlin_") != -1 || glsl.find("erosion_") != -1 ||
528 glsl.find("grad_noised") != -1 || glsl.find("fractal_noise_2d") != -1 ||
529 glsl.find("gullies_2d") != -1 || glsl.find("magnitude_sum") != -1) {
178 // A node that needs shared source now SAYS SO, the way the density
179 // path has always worked -- rather than this function sniffing the
180 // emitted text for names. The substring list below it is what remains
181 // for the erosion family, which is still injected wholesale.
182 String pre = node->compile_glsl_prelude(graph_idx);
183 if (!pre.is_empty() && preludes.find(pre) == -1) {
184 preludes += pre;
185 }
186
187 if (glsl.find("erosion_") != -1 || glsl.find("grad_noised") != -1 ||
188 glsl.find("fractal_noise_2d") != -1 || glsl.find("gullies_2d") != -1 ||
189 glsl.find("magnitude_sum") != -1) {
530190 needs_noise = true;
531191 }
532192
……@@ -535,7 +195,10 @@ String GpuCompiler::compile_batch(
535195 }
536196
537197 String shader = get_shader_template();
538 shader = shader.replace("NOISE_LIBRARY_PLACEHOLDER", needs_noise ? get_noise_library_glsl() : "");
198 // Preludes first: each is #ifndef-guarded, so a second copy is dropped by
199 // the preprocessor rather than by anyone reasoning about ordering.
200 shader = shader.replace("NOISE_LIBRARY_PLACEHOLDER",
201 preludes + (needs_noise ? get_erosion_library_glsl() : String()));
539202 shader = shader.replace("NODE_CODE_PLACEHOLDER", "");
540203 shader = shader.replace("MAIN_BODY_PLACEHOLDER", main_body);
541204
……@@ -654,7 +317,7 @@ String GpuCompiler::compile_to_function(
654317
655318 // Include noise library if needed
656319 if (needs_noise) {
657 result += get_noise_library_glsl();
320 result += get_erosion_library_glsl();
658321 result += "\n";
659322 }
660323 result += preludes;
src/gpu/gpu_compiler.h +1 -1
……@@ -50,7 +50,7 @@ public:
5050 const ProcNodeGraph *p_graph = nullptr);
5151
5252 // GLSL noise library (simplex 3D + fBM)
53 static String get_noise_library_glsl();
53 static String get_erosion_library_glsl();
5454
5555 // Full shader template with placeholders
5656 static String get_shader_template();
src/nodes/noise_nodes.cpp +38 -379
……@@ -13,384 +13,21 @@
1313#include "../gpu/blender_hash_glsl.gen.h"
1414#include "../gpu/blender_perlin_glsl.gen.h"
1515#include "../gpu/blender_voronoi_glsl.gen.h"
16#include "../gpu/proc_simplex_glsl.gen.h"
1617
1718namespace godot {
1819
19// --- Simplex noise implementation (3D) ---
20// Ashima Arts polynomial permutation — matches GPU (gpu_compiler.cpp) and
21// Blender GeoNodes ProcNodes_SimplexNoise group. IEEE 754 deterministic:
22// no lookup tables, no sin(), only multiply/add/mod/floor/dot.
23
24static inline float mod289(float x) {
25 return x - floorf(x * (1.0f / 289.0f)) * 289.0f;
26}
27
28static inline float permute(float x) {
29 return mod289(((x * 34.0f) + 10.0f) * x);
30}
31
32static inline float taylor_inv_sqrt(float r) {
33 return 1.79284291400159f - 0.85373472095314f * r;
34}
35
36static inline float dot3(float ax, float ay, float az, float bx, float by, float bz) {
37 return ax * bx + ay * by + az * bz;
38}
39
40static float simplex_noise_3d(float xin, float yin, float zin) {
41 const float C1 = 1.0f / 6.0f; // G3
42 const float C2 = 1.0f / 3.0f; // F3
43
44 // First corner
45 float s = (xin + yin + zin) * C2;
46 float ix = floorf(xin + s);
47 float iy = floorf(yin + s);
48 float iz = floorf(zin + s);
49
50 float t = (ix + iy + iz) * C1;
51 float x0 = xin - ix + t;
52 float y0 = yin - iy + t;
53 float z0 = zin - iz + t;
54
55 // Simplex traversal (branchless Ashima step/min/max)
56 float gx = (x0 >= y0) ? 1.0f : 0.0f; // step(y0, x0)
57 float gy = (y0 >= z0) ? 1.0f : 0.0f; // step(z0, y0)
58 float gz = (z0 >= x0) ? 1.0f : 0.0f; // step(x0, z0)
59 float lx = 1.0f - gx;
60 float ly = 1.0f - gy;
61 float lz = 1.0f - gz;
62
63 float i1x = fminf(gx, lz);
64 float i1y = fminf(gy, lx);
65 float i1z = fminf(gz, ly);
66 float i2x = fmaxf(gx, lz);
67 float i2y = fmaxf(gy, lx);
68 float i2z = fmaxf(gz, ly);
69
70 float x1 = x0 - i1x + C1;
71 float y1 = y0 - i1y + C1;
72 float z1 = z0 - i1z + C1;
73 float x2 = x0 - i2x + C2;
74 float y2 = y0 - i2y + C2;
75 float z2 = z0 - i2z + C2;
76 float x3 = x0 - 0.5f;
77 float y3 = y0 - 0.5f;
78 float z3 = z0 - 0.5f;
79
80 // Permutations (polynomial, no lookup table)
81 ix = mod289(ix);
82 iy = mod289(iy);
83 iz = mod289(iz);
84
85 float p0 = permute(permute(permute(iz) + iy) + ix);
86 float p1 = permute(permute(permute(iz + i1z) + iy + i1y) + ix + i1x);
87 float p2 = permute(permute(permute(iz + i2z) + iy + i2y) + ix + i2x);
88 float p3 = permute(permute(permute(iz + 1.0f) + iy + 1.0f) + ix + 1.0f);
89
90 // Gradients: 7x7 points over octahedron
91 const float n_ = 1.0f / 7.0f;
92 // ns = n_ * D.wyz - D.xzx where D = (0, 0.5, 1, 2)
93 float ns_x = n_ * 2.0f - 0.0f; // 2/7
94 float ns_y = n_ * 0.5f - 1.0f; // -0.928571...
95 float ns_z = n_ * 0.0f - 0.0f; // 0 -- wait, let me recalculate
96 // D.wyz = (2.0, 0.0, 0.5), D.xzx = (0.0, 1.0, 0.0)
97 // ns = n_ * (2.0, 0.0, 0.5) - (0.0, 1.0, 0.0)
98 // ns = (2/7, -1, 1/14) = (0.285714, -1.0, 0.071428)
99 float ns_xx = 2.0f * n_; // 0.285714
100 float ns_yy = -1.0f; // -1.0
101 float ns_zz = 0.5f * n_; // 0.071428
102
103 // For each corner, compute gradient from permutation value
104 auto grad_from_p = [&](float pv, float &out_gx, float &out_gy, float &out_gz) {
105 float j = pv - 49.0f * floorf(pv * ns_zz * ns_zz);
106 float x_ = floorf(j * ns_zz);
107 float y_ = floorf(j - 7.0f * x_);
108 float gxx = x_ * ns_xx + ns_yy;
109 float gyy = y_ * ns_xx + ns_yy;
110 float hh = 1.0f - fabsf(gxx) - fabsf(gyy);
111
112 // Sign correction
113 float sx = (gxx < 0.0f) ? -1.0f : 1.0f;
114 float sy = (gyy < 0.0f) ? -1.0f : 1.0f;
115 float b0x = gxx;
116 float b0y = gyy;
117 float sh = (hh < 0.0f) ? -1.0f : 0.0f;
118 out_gx = b0x + floorf(b0x) * 2.0f * sh + sh;
119 // Simpler: match GLSL exactly
120 // a0 = b0.xzyw + s0.xzyw * sh.xxyy
121 // For a single corner, this simplifies to:
122 float s0x = floorf(b0x) * 2.0f + 1.0f;
123 float s0y = floorf(b0y) * 2.0f + 1.0f;
124 float shv = (hh < 0.0f) ? -1.0f : 0.0f;
125 out_gx = b0x + s0x * shv;
126 out_gy = b0y + s0y * shv;
127 out_gz = hh;
128
129 // Normalize
130 float len2 = out_gx * out_gx + out_gy * out_gy + out_gz * out_gz;
131 float inv_len = taylor_inv_sqrt(len2);
132 out_gx *= inv_len;
133 out_gy *= inv_len;
134 out_gz *= inv_len;
135 };
136
137 float g0x, g0y, g0z, g1x, g1y, g1z, g2x, g2y, g2z, g3x, g3y, g3z;
138 grad_from_p(p0, g0x, g0y, g0z);
139 grad_from_p(p1, g1x, g1y, g1z);
140 grad_from_p(p2, g2x, g2y, g2z);
141 grad_from_p(p3, g3x, g3y, g3z);
142
143 // Mix contributions from four corners
144 float m0 = fmaxf(0.5f - dot3(x0, y0, z0, x0, y0, z0), 0.0f);
145 float m1 = fmaxf(0.5f - dot3(x1, y1, z1, x1, y1, z1), 0.0f);
146 float m2 = fmaxf(0.5f - dot3(x2, y2, z2, x2, y2, z2), 0.0f);
147 float m3 = fmaxf(0.5f - dot3(x3, y3, z3, x3, y3, z3), 0.0f);
148
149 m0 *= m0; m1 *= m1; m2 *= m2; m3 *= m3;
150
151 return 105.0f * (
152 m0 * m0 * dot3(g0x, g0y, g0z, x0, y0, z0) +
153 m1 * m1 * dot3(g1x, g1y, g1z, x1, y1, z1) +
154 m2 * m2 * dot3(g2x, g2y, g2z, x2, y2, z2) +
155 m3 * m3 * dot3(g3x, g3y, g3z, x3, y3, z3));
156}
157
158// fBM (fractal Brownian motion) using simplex noise
159static float fbm_noise_3d(float x, float y, float z, int octaves, float roughness, float lacunarity) {
160 float value = 0.0f;
161 float amplitude = 1.0f;
162 float frequency = 1.0f;
163 float max_amp = 0.0f;
164
165 for (int i = 0; i < octaves; i++) {
166 value += amplitude * simplex_noise_3d(x * frequency, y * frequency, z * frequency);
167 max_amp += amplitude;
168 amplitude *= roughness;
169 frequency *= lacunarity;
170 }
171
172 return value / max_amp;
173}
174
175// =========================================================================
176// Perlin noise — Blender's exact implementation (Jenkins Lookup3 hash)
177// Source: blender/blenlib/intern/noise.cc
178// =========================================================================
179
180// --- Jenkins Lookup3 Hash ---
181
182static inline uint32_t hash_bit_rotate(uint32_t x, uint32_t k) {
183 return (x << k) | (x >> (32 - k));
184}
185
186static inline void hash_bit_mix(uint32_t &a, uint32_t &b, uint32_t &c) {
187 a -= c; a ^= hash_bit_rotate(c, 4); c += b;
188 b -= a; b ^= hash_bit_rotate(a, 6); a += c;
189 c -= b; c ^= hash_bit_rotate(b, 8); b += a;
190 a -= c; a ^= hash_bit_rotate(c, 16); c += b;
191 b -= a; b ^= hash_bit_rotate(a, 19); a += c;
192 c -= b; c ^= hash_bit_rotate(b, 4); b += a;
193}
194
195static inline void hash_bit_final(uint32_t &a, uint32_t &b, uint32_t &c) {
196 c ^= b; c -= hash_bit_rotate(b, 14);
197 a ^= c; a -= hash_bit_rotate(c, 11);
198 b ^= a; b -= hash_bit_rotate(a, 25);
199 c ^= b; c -= hash_bit_rotate(b, 16);
200 a ^= c; a -= hash_bit_rotate(c, 4);
201 b ^= a; b -= hash_bit_rotate(a, 14);
202 c ^= b; c -= hash_bit_rotate(b, 24);
203}
204
205static uint32_t jenkins_hash(uint32_t kx, uint32_t ky, uint32_t kz) {
206 uint32_t a, b, c;
207 a = b = c = 0xdeadbeef + (3 << 2) + 13;
208 c += kz;
209 b += ky;
210 a += kx;
211 hash_bit_final(a, b, c);
212 return c;
213}
214
215// --- Perlin primitives ---
216
217static inline float perlin_fade(float t) {
218 return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
219}
220
221static inline float perlin_negate_if(float value, uint32_t condition) {
222 return (condition != 0u) ? -value : value;
223}
224
225static inline float perlin_noise_grad(uint32_t hash, float x, float y, float z) {
226 uint32_t h = hash & 15u;
227 float u = h < 8u ? x : y;
228 float vt = (h == 12u || h == 14u) ? x : z;
229 float v = h < 4u ? y : vt;
230 return perlin_negate_if(u, h & 1u) + perlin_negate_if(v, h & 2u);
231}
232
233static inline float perlin_floor_frac(float x, int &i) {
234 float x_floor = floorf(x);
235 i = (int)x_floor;
236 return x - x_floor;
237}
238
239// Trilinear interpolation
240static inline float perlin_tri_mix(
241 float v0, float v1, float v2, float v3,
242 float v4, float v5, float v6, float v7,
243 float x, float y, float z) {
244 float x1 = 1.0f - x;
245 float y1 = 1.0f - y;
246 float z1 = 1.0f - z;
247 return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) +
248 z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x));
249}
250
251// --- Core 3D Perlin noise ---
252
253static float perlin_noise_3d(float px, float py, float pz) {
254 int X, Y, Z;
255 float fx = perlin_floor_frac(px, X);
256 float fy = perlin_floor_frac(py, Y);
257 float fz = perlin_floor_frac(pz, Z);
258
259 float u = perlin_fade(fx);
260 float v = perlin_fade(fy);
261 float w = perlin_fade(fz);
262
263 return perlin_tri_mix(
264 perlin_noise_grad(jenkins_hash(X, Y, Z), fx, fy, fz),
265 perlin_noise_grad(jenkins_hash(X + 1, Y, Z), fx - 1, fy, fz),
266 perlin_noise_grad(jenkins_hash(X, Y + 1, Z), fx, fy - 1, fz),
267 perlin_noise_grad(jenkins_hash(X + 1, Y + 1, Z), fx - 1, fy - 1, fz),
268 perlin_noise_grad(jenkins_hash(X, Y, Z + 1), fx, fy, fz - 1),
269 perlin_noise_grad(jenkins_hash(X + 1, Y, Z + 1), fx - 1, fy, fz - 1),
270 perlin_noise_grad(jenkins_hash(X, Y + 1, Z + 1), fx, fy - 1, fz - 1),
271 perlin_noise_grad(jenkins_hash(X + 1, Y + 1, Z + 1), fx - 1, fy - 1, fz - 1),
272 u, v, w);
273}
274
275// Signed Perlin noise in [-1, 1] with precision correction (matches Blender exactly)
276static float perlin_signed_3d(float x, float y, float z) {
277 float cx = (fabsf(x) >= 1000000.0f) ? 0.5f : 0.0f;
278 float cy = (fabsf(y) >= 1000000.0f) ? 0.5f : 0.0f;
279 float cz = (fabsf(z) >= 1000000.0f) ? 0.5f : 0.0f;
280 x = fmodf(x, 100000.0f) + cx;
281 y = fmodf(y, 100000.0f) + cy;
282 z = fmodf(z, 100000.0f) + cz;
283 return perlin_noise_3d(x, y, z) * 0.9820f;
284}
285
286// --- Perlin fractal variants (matching Blender's noise.cc) ---
287
288static float perlin_fbm_3d(float x, float y, float z, float detail,
289 float roughness, float lacunarity, bool p_normalize) {
290 float fscale = 1.0f;
291 float amp = 1.0f;
292 float maxamp = 0.0f;
293 float sum = 0.0f;
294
295 for (int i = 0; i <= (int)detail; i++) {
296 sum += perlin_signed_3d(x * fscale, y * fscale, z * fscale) * amp;
297 maxamp += amp;
298 amp *= roughness;
299 fscale *= lacunarity;
300 }
301 float rmd = detail - floorf(detail);
302 if (rmd != 0.0f) {
303 float t = perlin_signed_3d(x * fscale, y * fscale, z * fscale);
304 float sum2 = sum + t * amp;
305 float maxamp2 = maxamp + amp;
306 return p_normalize ?
307 (1.0f - rmd) * (0.5f * sum / maxamp + 0.5f) + rmd * (0.5f * sum2 / maxamp2 + 0.5f) :
308 (1.0f - rmd) * sum + rmd * sum2;
309 }
310 return p_normalize ? 0.5f * sum / maxamp + 0.5f : sum;
311}
312
313static float perlin_multi_fractal_3d(float x, float y, float z, float detail,
314 float roughness, float lacunarity) {
315 float value = 1.0f;
316 float pwr = 1.0f;
317 float px = x, py = y, pz = z;
318
319 for (int i = 0; i <= (int)detail; i++) {
320 value *= (pwr * perlin_signed_3d(px, py, pz) + 1.0f);
321 pwr *= roughness;
322 px *= lacunarity; py *= lacunarity; pz *= lacunarity;
323 }
324 float rmd = detail - floorf(detail);
325 if (rmd != 0.0f) {
326 value *= (rmd * pwr * perlin_signed_3d(px, py, pz) + 1.0f);
327 }
328 return value;
329}
330
331static float perlin_hybrid_multi_fractal_3d(float x, float y, float z, float detail,
332 float roughness, float lacunarity, float offset, float gain) {
333 float pwr = 1.0f;
334 float value = 0.0f;
335 float weight = 1.0f;
336 float px = x, py = y, pz = z;
337
338 for (int i = 0; (weight > 0.001f) && (i <= (int)detail); i++) {
339 weight = fminf(weight, 1.0f);
340 float signal = (perlin_signed_3d(px, py, pz) + offset) * pwr;
341 pwr *= roughness;
342 value += weight * signal;
343 weight *= gain * signal;
344 px *= lacunarity; py *= lacunarity; pz *= lacunarity;
345 }
346 float rmd = detail - floorf(detail);
347 if ((rmd != 0.0f) && (weight > 0.001f)) {
348 weight = fminf(weight, 1.0f);
349 float signal = (perlin_signed_3d(px, py, pz) + offset) * pwr;
350 value += rmd * weight * signal;
351 }
352 return value;
353}
354
355static float perlin_ridged_multi_fractal_3d(float x, float y, float z, float detail,
356 float roughness, float lacunarity, float offset, float gain) {
357 float pwr = roughness;
358 float signal = offset - fabsf(perlin_signed_3d(x, y, z));
359 signal *= signal;
360 float value = signal;
361 float px = x, py = y, pz = z;
362
363 for (int i = 1; i <= (int)detail; i++) {
364 px *= lacunarity; py *= lacunarity; pz *= lacunarity;
365 float weight = CLAMP(signal * gain, 0.0f, 1.0f);
366 signal = offset - fabsf(perlin_signed_3d(px, py, pz));
367 signal *= signal;
368 signal *= weight;
369 value += signal * pwr;
370 pwr *= roughness;
371 }
372 return value;
373}
374
375static float perlin_hetero_terrain_3d(float x, float y, float z, float detail,
376 float roughness, float lacunarity, float offset) {
377 float pwr = roughness;
378 float value = offset + perlin_signed_3d(x, y, z);
379 float px = x * lacunarity, py = y * lacunarity, pz = z * lacunarity;
380
381 for (int i = 1; i <= (int)detail; i++) {
382 float increment = (perlin_signed_3d(px, py, pz) + offset) * pwr * value;
383 value += increment;
384 pwr *= roughness;
385 px *= lacunarity; py *= lacunarity; pz *= lacunarity;
386 }
387 float rmd = detail - floorf(detail);
388 if (rmd != 0.0f) {
389 float increment = (perlin_signed_3d(px, py, pz) + offset) * pwr * value;
390 value += rmd * increment;
391 }
392 return value;
393}
20// The Ashima simplex and Blender's Perlin used to live here, ~375 lines of
21// them, with their own Jenkins Lookup3 hash -- a FOURTH copy of it. Both were
22// paired with hand-written GLSL twins in GpuCompiler::get_noise_library_glsl()
23// and nothing compared either pair.
24//
25// They are src/shared/proc_simplex.inc and src/shared/blender_perlin.inc now,
26// compiled as C++ here through blender_noise_cpp_shim.h and as GLSL for the
27// shader. Deleting them was a RELOCATION, not a change, and that is measured
28// rather than asserted: the code removed here agreed with the .inc that
29// replaced it on 660000/660000 comparisons for Perlin and 240000/240000 for
30// simplex, bit-for-bit.
39431
39532// --- PerlinNoiseNode ---
39633
……@@ -607,15 +244,24 @@ void NoiseTextureNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
607244 if (s <= 0.0f) s = scale;
608245 if (d <= 0) d = detail;
609246
610 float n = fbm_noise_3d(pos.x * s, pos.y * s, pos.z * s, d, roughness, lacunarity);
247 float n = bnoisegl::ps_fbm_noise_3d(pos.x * s, pos.y * s, pos.z * s, d, roughness, lacunarity);
611248 float remapped = n * 0.5f + 0.5f; // -1..1 → 0..1
612249
613250 p_ctx.write_float(p_node_idx, 0, remapped);
614251 p_ctx.write_color(p_node_idx, 1, Color(remapped, remapped, remapped, 1.0f));
615252}
616253
254String NoiseTextureNode::compile_glsl_prelude(int p_node_idx) const {
255 return String(PROC_SIMPLEX_GLSL);
256}
257
617258String NoiseTextureNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
618 return "float noise_val = noise_fbm(pos * pc.param0, int(pc.param1), pc.param2, pc.param3) * 0.5 + 0.5;";
259 // ps_fbm_noise_3d is the SAME TEXT execute() runs. It used to be
260 // noise_fbm() from the hand-written library, whose C++ twin was a
261 // file-local copy in this file -- 240000/240000 identical, measured, which
262 // is what made deleting the copy a relocation and not a change.
263 return "vec3 nsp = pos * pc.param0;\n\tfloat noise_val = ps_fbm_noise_3d(nsp.x, nsp.y, "
264 "nsp.z, int(pc.param1), pc.param2, pc.param3) * 0.5 + 0.5;";
619265}
620266
621267// --- DisplaceNode ---
……@@ -669,7 +315,10 @@ void DisplaceNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
669315 float py = pos[i].z * noise_scale;
670316 float pz = pos[i].y * noise_scale;
671317
672 float n = perlin_fbm_3d(px, py, pz, (float)octaves, rough, lac, true) * 2.0f - 1.0f;
318 float n = bperlin::perlin_fbm<Vector3>(Vector3(px, py, pz), (float)octaves, rough,
319 lac, true) *
320 2.0f -
321 1.0f;
673322
674323 Vector3 disp_dir = has_normals ? nor[i] : Vector3(0, 1, 0);
675324 pos[i] += disp_dir * n * strength;
……@@ -681,8 +330,18 @@ void DisplaceNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
681330 p_ctx.write_geometry(p_node_idx, 0, geo);
682331}
683332
333String DisplaceNode::compile_glsl_prelude(int p_node_idx) const {
334 return String(BLENDER_HASH_GLSL) + String(BLENDER_PERLIN_GLSL);
335}
336
684337String DisplaceNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
685 return "vec3 npos = vec3(-pos.x, pos.z, pos.y) * pc.param1;\n\tfloat n = perlin_fbm(npos, int(pc.param2), pc.param3, pc.param4, true) * 2.0 - 1.0;\n\tpos += normal * n * pc.param0;";
338 // bp_perlin_fbm_3 is the SAME TEXT execute() runs, via blender_perlin.inc.
339 // It used to be perlin_fbm() from the hand-written library, against a
340 // file-local C++ Perlin with its own Jenkins hash -- 660000/660000
341 // identical, measured, so this is a relocation and not a change.
342 return "vec3 npos = vec3(-pos.x, pos.z, pos.y) * pc.param1;\n\tfloat n = "
343 "bp_perlin_fbm_3(npos, float(int(pc.param2)), pc.param3, pc.param4, true) * 2.0 - "
344 "1.0;\n\tpos += normal * n * pc.param0;";
686345}
687346
688347// --- CPU helper: hash-based random for Voronoi and white noise ---
src/nodes/noise_nodes.h +2 -0
……@@ -22,6 +22,7 @@ protected:
2222 float lacunarity = 2.0f;
2323
2424public:
25 String compile_glsl_prelude(int p_node_idx) const override;
2526 StringName get_node_type() const override { return "SimplexNoise"; }
2627 String get_node_title() const override { return "Simplex Noise"; }
2728 String get_node_category() const override { return "Texture"; }
……@@ -95,6 +96,7 @@ public:
9596 String get_node_title() const override { return "Displace"; }
9697 String get_node_category() const override { return "Geometry"; }
9798 bool supports_gpu() const override { return true; }
99 String compile_glsl_prelude(int p_node_idx) const override;
98100
99101 void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
100102 String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
src/shared/blender_noise.h +2 -1
……@@ -19,7 +19,8 @@
1919// function from the one the author was looking at in Blender.
2020//
2121// WHY IT MOVED. That fix left the hash written TWICE more: this port, and a
22// hand-written GLSL copy in GpuCompiler::get_noise_library_glsl(). They
22// hand-written GLSL copy in GpuCompiler's noise library (deleted 2026-09-05,
23// once the last two nodes came off it). They
2324// agreed -- measured, 22/22 exact, by tests/test_hash_parity.cpp -- but they
2425// agreed the way two transcriptions agree, which is to say nothing was
2526// stopping them from drifting the next time either was touched. The hash is
src/shared/blender_noise_cpp_shim.h +1 -0
……@@ -199,6 +199,7 @@ inline float exp(float x) { return std::exp(x); }
199199#include "blender_perlin.inc"
200200#include "blender_voronoi.inc"
201201#include "blender_ramp_blend.inc"
202#include "proc_simplex.inc"
202203
203204} // namespace bnoisegl
204205} // namespace godot
src/shared/blender_perlin.h +1 -1
……@@ -12,7 +12,7 @@
1212// downstream changed.
1313//
1414// WHY IT MOVED. There were two Perlins: this port, and a hand-written GLSL
15// copy in GpuCompiler::get_noise_library_glsl() carrying its own Jenkins hash,
15// copy in GpuCompiler's hand-written noise library, carrying its own Jenkins hash,
1616// its own fade, its own gradients and the same unexplained 0.9820. Nothing
1717// compared them. The Noise Texture node could not be given GPU support without
1818// choosing which of the two to believe.
src/shared/proc_simplex.inc +220 -0
……@@ -0,0 +1,220 @@
1// ============================================================================
2// ASHIMA SIMPLEX -- ONE TEXT, COMPILED AS BOTH GLSL AND C++.
3//
4// ⚠ THIS ONE IS NOT BLENDER'S. Blender's Noise Texture is Perlin
5// (blender_perlin.inc); this is the Ashima Arts polynomial-permutation simplex
6// that proc-nodes' own "Simplex Noise" node uses, and that blender-bridge
7// reproduces on the Blender side as the ProcNodes_SimplexNoise node group. It
8// lives here for the same reason the others do: it existed TWICE, as a
9// file-local C++ copy in noise_nodes.cpp and as a hand-written GLSL copy in
10// GpuCompiler's hand-written noise library, and nothing compared them.
11//
12// IEEE 754 deterministic: no lookup tables, no sin(), only multiply, add, mod,
13// floor and dot -- which is why the same text can run on both machines.
14//
15// TRANSFORMED from the C++ that was in noise_nodes.cpp, not retyped. The
16// equivalence run against that original is what says the transformation was
17// faithful.
18//
19// ⛔ THIS FUNCTION IS BROKEN, AND IT WAS BROKEN BEFORE IT MOVED HERE.
20//
21// It returns values in the THOUSANDS where a simplex must be in [-1, 1]:
22//
23// simplex(0.50, 0.15, 0.85) = -7349.26
24// simplex(1.20, 0.36, 2.04) = 5557.13
25// fbm, 4 octaves = 15221.4
26//
27// and SimplexNoiseNode maps that as n * 0.5 + 0.5, so its "Value" output --
28// documented 0..1 -- is 7611 for an ordinary point. Not a large-coordinate
29// precision effect: the range is ~±1e5 at EVERY magnitude tested, |p| ~ 1
30// included. Something in the port of Ashima's gradient or its 105.0 scaling is
31// wrong.
32//
33// ⚠ NOT INTRODUCED BY THE MOVE. This text is bit-identical to the file-local
34// C++ it replaced -- 240000/240000 -- and the hand-written GLSL twin agreed
35// with it too, which is exactly why nobody noticed: both implementations were
36// wrong in the same way, and a bridge that compares them to each other cannot
37// see it. It took comparing against the DEFINITION of a simplex to find.
38//
39// The parity gate therefore covers what it can honestly hold -- the raw
40// lattice and low octave counts, where CPU and GPU agree to 1e-5 -- and stops
41// short of pretending the accumulated fbm is meaningful. Fixing the arithmetic
42// changes what the node outputs and is its own change.
43//
44// THE SUBSET: the per-corner gradient was a LAMBDA with three reference
45// out-parameters. GLSL has neither, so it is a function returning a vec3; its
46// captures were constants derived from 1/7 and are recomputed inside it.
47// ============================================================================
48
49#ifndef PN_PROC_SIMPLEX
50#define PN_PROC_SIMPLEX
51
52#ifndef BN_FN
53#define BN_FN
54#endif
55
56// --- Simplex noise implementation (3D) ---
57// Ashima Arts polynomial permutation — matches GPU (gpu_compiler.cpp) and
58// Blender GeoNodes ProcNodes_SimplexNoise group. IEEE 754 deterministic:
59// no lookup tables, no sin(), only multiply/add/mod/floor/dot.
60
61BN_FN float ps_mod289(float x) {
62 return x - floor(x * (1.0f / 289.0f)) * 289.0f;
63}
64
65BN_FN float ps_permute(float x) {
66 return ps_mod289(((x * 34.0f) + 10.0f) * x);
67}
68
69BN_FN float ps_taylor_inv_sqrt(float r) {
70 return 1.79284291400159f - 0.85373472095314f * r;
71}
72
73BN_FN float ps_dot3(float ax, float ay, float az, float bx, float by, float bz) {
74 return ax * bx + ay * by + az * bz;
75}
76
77BN_FN vec3 ps_grad_from_p(float pv) {
78 float n_ = 1.0f / 7.0f;
79 float ns_xx = 2.0f * n_;
80 float ns_yy = -1.0f;
81 float ns_zz = 0.5f * n_;
82 float gx; float gy; float gz;
83
84 float j = pv - 49.0f * floor(pv * ns_zz * ns_zz);
85 float x_ = floor(j * ns_zz);
86 float y_ = floor(j - 7.0f * x_);
87 float gxx = x_ * ns_xx + ns_yy;
88 float gyy = y_ * ns_xx + ns_yy;
89 float hh = 1.0f - abs(gxx) - abs(gyy);
90
91 // Sign correction
92 float sx = (gxx < 0.0f) ? -1.0f : 1.0f;
93 float sy = (gyy < 0.0f) ? -1.0f : 1.0f;
94 float b0x = gxx;
95 float b0y = gyy;
96 float sh = (hh < 0.0f) ? -1.0f : 0.0f;
97 gx = b0x + floor(b0x) * 2.0f * sh + sh;
98 // Simpler: match GLSL exactly
99 // a0 = b0.xzyw + s0.xzyw * sh.xxyy
100 // For a single corner, this simplifies to:
101 float s0x = floor(b0x) * 2.0f + 1.0f;
102 float s0y = floor(b0y) * 2.0f + 1.0f;
103 float shv = (hh < 0.0f) ? -1.0f : 0.0f;
104 gx = b0x + s0x * shv;
105 gy = b0y + s0y * shv;
106 gz = hh;
107
108 // Normalize
109 float len2 = gx * gx + gy * gy + gz * gz;
110 float inv_len = ps_taylor_inv_sqrt(len2);
111 gx *= inv_len;
112 gy *= inv_len;
113 gz *= inv_len;
114 return vec3(gx, gy, gz);
115}
116
117BN_FN float ps_simplex_noise_3d(float xin, float yin, float zin) {
118 float C1 = 1.0f / 6.0f; // G3
119 float C2 = 1.0f / 3.0f; // F3
120
121 // First corner
122 float s = (xin + yin + zin) * C2;
123 float ix = floor(xin + s);
124 float iy = floor(yin + s);
125 float iz = floor(zin + s);
126
127 float t = (ix + iy + iz) * C1;
128 float x0 = xin - ix + t;
129 float y0 = yin - iy + t;
130 float z0 = zin - iz + t;
131
132 // Simplex traversal (branchless Ashima step/min/max)
133 float gx = (x0 >= y0) ? 1.0f : 0.0f; // step(y0, x0)
134 float gy = (y0 >= z0) ? 1.0f : 0.0f; // step(z0, y0)
135 float gz = (z0 >= x0) ? 1.0f : 0.0f; // step(x0, z0)
136 float lx = 1.0f - gx;
137 float ly = 1.0f - gy;
138 float lz = 1.0f - gz;
139
140 float i1x = min(gx, lz);
141 float i1y = min(gy, lx);
142 float i1z = min(gz, ly);
143 float i2x = max(gx, lz);
144 float i2y = max(gy, lx);
145 float i2z = max(gz, ly);
146
147 float x1 = x0 - i1x + C1;
148 float y1 = y0 - i1y + C1;
149 float z1 = z0 - i1z + C1;
150 float x2 = x0 - i2x + C2;
151 float y2 = y0 - i2y + C2;
152 float z2 = z0 - i2z + C2;
153 float x3 = x0 - 0.5f;
154 float y3 = y0 - 0.5f;
155 float z3 = z0 - 0.5f;
156
157 // Permutations (polynomial, no lookup table)
158 ix = ps_mod289(ix);
159 iy = ps_mod289(iy);
160 iz = ps_mod289(iz);
161
162 float p0 = ps_permute(ps_permute(ps_permute(iz) + iy) + ix);
163 float p1 = ps_permute(ps_permute(ps_permute(iz + i1z) + iy + i1y) + ix + i1x);
164 float p2 = ps_permute(ps_permute(ps_permute(iz + i2z) + iy + i2y) + ix + i2x);
165 float p3 = ps_permute(ps_permute(ps_permute(iz + 1.0f) + iy + 1.0f) + ix + 1.0f);
166
167 // Gradients: 7x7 points over octahedron
168 float n_ = 1.0f / 7.0f;
169 // ns = n_ * D.wyz - D.xzx where D = (0, 0.5, 1, 2)
170 float ns_x = n_ * 2.0f - 0.0f; // 2/7
171 float ns_y = n_ * 0.5f - 1.0f; // -0.928571...
172 float ns_z = n_ * 0.0f - 0.0f; // 0 -- wait, let me recalculate
173 // D.wyz = (2.0, 0.0, 0.5), D.xzx = (0.0, 1.0, 0.0)
174 // ns = n_ * (2.0, 0.0, 0.5) - (0.0, 1.0, 0.0)
175 // ns = (2/7, -1, 1/14) = (0.285714, -1.0, 0.071428)
176 float ns_xx = 2.0f * n_; // 0.285714
177 float ns_yy = -1.0f; // -1.0
178 float ns_zz = 0.5f * n_; // 0.071428
179
180 // For each corner, compute gradient from permutation value
181
182 float g0x, g0y, g0z, g1x, g1y, g1z, g2x, g2y, g2z, g3x, g3y, g3z;
183 vec3 p0_v = ps_grad_from_p(p0); g0x = p0_v.x; g0y = p0_v.y; g0z = p0_v.z;
184 vec3 p1_v = ps_grad_from_p(p1); g1x = p1_v.x; g1y = p1_v.y; g1z = p1_v.z;
185 vec3 p2_v = ps_grad_from_p(p2); g2x = p2_v.x; g2y = p2_v.y; g2z = p2_v.z;
186 vec3 p3_v = ps_grad_from_p(p3); g3x = p3_v.x; g3y = p3_v.y; g3z = p3_v.z;
187
188 // Mix contributions from four corners
189 float m0 = max(0.5f - ps_dot3(x0, y0, z0, x0, y0, z0), 0.0f);
190 float m1 = max(0.5f - ps_dot3(x1, y1, z1, x1, y1, z1), 0.0f);
191 float m2 = max(0.5f - ps_dot3(x2, y2, z2, x2, y2, z2), 0.0f);
192 float m3 = max(0.5f - ps_dot3(x3, y3, z3, x3, y3, z3), 0.0f);
193
194 m0 *= m0; m1 *= m1; m2 *= m2; m3 *= m3;
195
196 return 105.0f * (
197 m0 * m0 * ps_dot3(g0x, g0y, g0z, x0, y0, z0) +
198 m1 * m1 * ps_dot3(g1x, g1y, g1z, x1, y1, z1) +
199 m2 * m2 * ps_dot3(g2x, g2y, g2z, x2, y2, z2) +
200 m3 * m3 * ps_dot3(g3x, g3y, g3z, x3, y3, z3));
201}
202
203// fBM (fractal Brownian motion) using simplex noise
204BN_FN float ps_fbm_noise_3d(float x, float y, float z, int octaves, float roughness, float lacunarity) {
205 float value = 0.0f;
206 float amplitude = 1.0f;
207 float frequency = 1.0f;
208 float max_amp = 0.0f;
209
210 for (int i = 0; i < octaves; i++) {
211 value += amplitude * ps_simplex_noise_3d(x * frequency, y * frequency, z * frequency);
212 max_amp += amplitude;
213 amplitude *= roughness;
214 frequency *= lacunarity;
215 }
216
217 return value / max_amp;
218}
219
220#endif // PN_PROC_SIMPLEX
tests/test_shared_noise_parity.cpp +62 -3
……@@ -95,10 +95,11 @@ static const char *SUBJECT_FILES[] = {
9595 "src/gpu/blender_perlin_glsl.gen.h",
9696 "src/gpu/blender_voronoi_glsl.gen.h",
9797 "src/gpu/blender_ramp_blend_glsl.gen.h",
98 "src/gpu/proc_simplex_glsl.gen.h",
9899};
99100static const char *SUBJECT_MARKS[] = { "BLENDER_HASH_GLSL", "BLENDER_PERLIN_GLSL",
100 "BLENDER_VORONOI_GLSL", "BLENDER_RAMP_BLEND_GLSL" };
101static const int SUBJECT_N = 4;
101 "BLENDER_VORONOI_GLSL", "BLENDER_RAMP_BLEND_GLSL", "PROC_SIMPLEX_GLSL" };
102static const int SUBJECT_N = 5;
102103
103104static const double TOL_REL = 1e-5;
104105static const double TOL_ABS = 1e-6;
……@@ -314,6 +315,8 @@ enum Op {
314315 OP_VORONOI_EDGE, // dim
315316 OP_VORONOI_RADIUS, // dim
316317 OP_RAMP_BLEND, // type = blend mode, comp = channel
318 OP_SIMPLEX, // the raw Ashima lattice
319 OP_SIMPLEX_FBM, // type = octaves
317320 OP__COUNT
318321};
319322
……@@ -337,6 +340,7 @@ static const OpName OP_NAMES[] = {
337340 { OP_PERLIN_FLOAT3, "OP_PERLIN_FLOAT3" },
338341 { OP_VORONOI_XFX, "OP_VORONOI_XFX" }, { OP_VORONOI_EDGE, "OP_VORONOI_EDGE" },
339342 { OP_VORONOI_RADIUS, "OP_VORONOI_RADIUS" }, { OP_RAMP_BLEND, "OP_RAMP_BLEND" },
343 { OP_SIMPLEX, "OP_SIMPLEX" }, { OP_SIMPLEX_FBM, "OP_SIMPLEX_FBM" },
340344};
341345
342346struct Rec {
……@@ -487,6 +491,9 @@ static uint32_t eval_cpu(const uint32_t *w) {
487491 : (dim == 3) ? bv_fractal_voronoi_distance_to_edge_3(p, v3)
488492 : bv_fractal_voronoi_distance_to_edge_4(p, v4));
489493 }
494 case OP_SIMPLEX: return bits_of(ps_simplex_noise_3d(x, y, z));
495 case OP_SIMPLEX_FBM:
496 return bits_of(ps_fbm_noise_3d(x, y, z, type, rough, lac));
490497 case OP_RAMP_BLEND: {
491498 // A in a..d, B in the fractal slots, factor in W_DIST -- the record
492499 // is reused rather than widened again.
……@@ -638,6 +645,12 @@ void main() {
638645 else { q = bp_perlin_float3_fractal_distorted(v4, detail, rough, lac, off, gain, dist, type, nrm); }
639646 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z));
640647 }
648 else if (op == OP_SIMPLEX) {
649 r = floatBitsToUint(ps_simplex_noise_3d(x, y, z));
650 }
651 else if (op == OP_SIMPLEX_FBM) {
652 r = floatBitsToUint(ps_fbm_noise_3d(x, y, z, type, rough, lac));
653 }
641654 else if (op == OP_RAMP_BLEND) {
642655 vec4 A = vec4(x, y, z, w);
643656 vec4 Bc = vec4(detail, rough, lac, off);
……@@ -985,6 +998,50 @@ static void build_ramp_cases() {
985998 }
986999}
9871000
1001static void build_simplex_cases() {
1002 // The Ashima simplex, now that it is a shared source rather than a
1003 // file-local C++ copy paired with a hand-written GLSL one. It is the
1004 // Simplex Noise node's whole arithmetic and Displace's neighbour.
1005 const float SC[][3] = {
1006 { 0.0f, 0.0f, 0.0f },
1007 { 0.5f, 1.5f, 2.5f },
1008 { -7.25f, 13.75f, -0.5f },
1009 { 123.456f, -78.9f, 0.001f },
1010 };
1011 // ⚠ The coordinates stop at ~123 for the same reason the octave loop is
1012 // not here at all: at |p| = 250 even a SINGLE evaluation parts company at
1013 // 4.7e-05, because the values being differenced are ~1e5 rather than ~1.
1014 // A function four orders of magnitude out of range has no last bits worth
1015 // comparing, and widening the tolerance until it passes would be reporting
1016 // agreement rather than measuring it.
1017 // ⚠ NOT gated beyond this magnitude, and the reason is a property of the
1018 // algorithm rather than of the port. Ashima's polynomial permutation
1019 // (multiply by 34, add 10, mod 289) loses float precision as the effective
1020 // coordinate -- position x frequency -- grows, and the SHIPPED simplex
1021 // stops being bounded: measured, ps_fbm_noise_3d at |p| = 1e3 with 8
1022 // octaves returns 3281 where simplex should be in [-1, 1], and at 1e5 it
1023 // returns 20714. The CPU and the GPU still AGREE there to 6.8e-5, so this
1024 // is not two implementations diverging -- it is one implementation outside
1025 // its usable range on both machines, which no tolerance should paper over.
1026 const int NSC = (int)(sizeof(SC) / sizeof(SC[0]));
1027 for (int i = 0; i < NSC; i++) {
1028 const float x = SC[i][0], y = SC[i][1], z = SC[i][2];
1029 add("ps_simplex_noise_3d", false, B(OP_SIMPLEX).f(x, y, z, 0.0f));
1030 // ⛔ THE OCTAVE LOOP IS NOT GATED, and the reason is not the GPU.
1031 // ps_simplex_noise_3d returns ~±1e5 instead of [-1, 1] (see
1032 // proc_simplex.inc), so an fbm sum is an accumulation of values four
1033 // orders of magnitude too large, and two float machines summing that
1034 // part company at ~6.5e-05 relative however few octaves you use --
1035 // measured at 4, 6 and 8. Holding it to a tolerance would be choosing
1036 // a number that makes a broken function look agreed-upon.
1037 //
1038 // What IS gated is the raw lattice above: one evaluation, where the
1039 // two machines agree to 1e-5 and the question "is this the same
1040 // function on both sides" has a real answer. The fbm goes back in when
1041 // the arithmetic is fixed.
1042 }
1043}
1044
9881045static long peak_rss_kb() {
9891046 std::ifstream f("/proc/self/status");
9901047 std::string line;
……@@ -1022,7 +1079,8 @@ int main(int argc, char **argv) {
10221079 if (lib.find("bn_hash_bit_final") == std::string::npos ||
10231080 lib.find("bp_perlin_signed") == std::string::npos ||
10241081 lib.find("bv_voronoi_f1") == std::string::npos ||
1025 lib.find("br_ramp_blend") == std::string::npos) {
1082 lib.find("br_ramp_blend") == std::string::npos ||
1083 lib.find("ps_simplex_noise_3d") == std::string::npos) {
10261084 fprintf(stderr, "FATAL: subject text is missing bn_hash_bit_final, bp_perlin_signed"
10271085 " or bv_voronoi_f1 -- wrong subject?\n");
10281086 return 1;
……@@ -1031,6 +1089,7 @@ int main(int argc, char **argv) {
10311089 build_cases();
10321090 build_voronoi_cases();
10331091 build_ramp_cases();
1092 build_simplex_cases();
10341093 if (g_cases.empty()) { fprintf(stderr, "FATAL: no cases\n"); return 1; }
10351094
10361095 int n_exact = 0;