CultMath: snoise(float3) takes the upstream simplex-boundary fix

Kernel radius^2 0.6 -> 0.5, scale 42 -> 105, permute (34x+1)x -> (34x+10)x, per
stegu/webgl-noise 22434e04d7 (fix 21d9fe23d7, scale ff3b5d34ea, permute a3e6d57095).
snoise_grad, fbm_grad, ridged_grad follow. C#, both HLSL copies, design.md and
THIRD-PARTY-NOTICES.md change together. The 2D snoise shares the permute helper, so
its values change too. Golden values are regenerated in the next commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 11:13:34 +02:00
commit 8703b44636
6 changed files with 76 additions and 64 deletions

View file

@ -6,11 +6,15 @@
simplex-noise functions by Ian McEwan and Ashima Arts, as distributed by Stefan
Gustavson's `webgl-noise` project and Unity Mathematics.
`math.snoise(float3)` and `cultmath_snoise(float3)` follow `src/noise3D.glsl` and
`math.snoise(float2)` follows `src/noise2D.glsl` of stegu/webgl-noise at commit
`22434e04d7753f7e949e8d724ab3da2864c17a0f` (the simplex-boundary fix is
`21d9fe23d7`, scale `ff3b5d34ea`, permutation `a3e6d57095`).
`math.snoise_grad`/`cultmath_snoise_grad` (and the `fbm_grad`/`ridged_grad`
octave sums built on them) follow the analytic-gradient differentiation from
the same project's `src/noise3Dgrad.glsl`, retargeted onto this file's own
`snoise(float3)` falloff radius, permutation, and scale constants (see the
`math.snoise_grad` doc comment).
the same project's `src/noise3Dgrad.glsl`, with the same kernel radius,
permutation and scale as `snoise(float3)`.
- Copyright 2011 Ashima Arts.
- Upstream: <https://github.com/ashima/webgl-noise>

View file

@ -233,15 +233,25 @@ together, laid out as `float4(gradient.xyz, value.w)`, with no value-only twin
paths are proven bit-identical (`PrunedSearchIsBitIdenticalToTheUnprunedFiveCubedSearch`,
which samples `[-50, 50]^3`, is comfortably inside that domain).
- `snoise(float3)` follows webgl-noise `src/noise3D.glsl` as of
stegu/webgl-noise `22434e04d7`: kernel radius squared `0.5`, output scale
`105`, permutation `mod289((34x+10)x)`. Upstream's `21d9fe23d7` (2020-10-14,
"an age-old bug that caused slight discontinuities along simplex
boundaries") moved the radius from `0.6` to `0.5`; at `0.6` a lattice vertex
outside the four summed corners can lie inside the kernel, leaving a value
jump at the cell boundary (3.1e-3 over the committed probe's 400 lines). `ff3b5d34ea` set the scale
to `105` and `a3e6d57095` (2021-06-30) the permutation; the 2D `snoise` shares
that permutation helper, so its values changed too, while its own kernel
(`0.5`) and scale (`130`) already matched upstream.
`NoiseGradTests.SnoiseAndItsGradientAreContinuousAcrossSimplexCellBoundaries`
pins the continuity.
- `snoise_grad(float3 p)` carries `snoise(float3)` to value-and-gradient form,
following the differentiation in webgl-noise `src/noise3Dgrad.glsl` (Ashima
Arts / Ian McEwan, MIT) but retargeted onto `snoise(float3)`'s own falloff
radius (`0.6`), permutation (`mod289((34x+1)x)`), and scale (`42`) rather
than upstream's `0.5`/`mod289((34x+10)x)`/`105`: the differentiation itself
does not depend on those constants (per corner, `m0 = max(radius -
dot(x,x), 0)`, so `d(m0^4 * dot(p,x))/dx = -8*m0^3*dot(p,x)*x + m0^4*p`,
summed over the four corners and scaled by the same constant as the value),
so `.w` stays exactly `snoise(float3)`'s own field. `fbm_grad` and
Arts / Ian McEwan, MIT), which uses the same radius, permutation and scale as
`snoise(float3)`: per corner, `m0 = max(0.5 - dot(x,x), 0)`, so
`d(m0^4 * dot(p,x))/dx = -8*m0^3*dot(p,x)*x + m0^4*p`, summed over the four
corners and scaled by the same constant as the value, so `.w` is exactly
`snoise(float3)`. `fbm_grad` and
`ridged_grad` are octave sums of `snoise_grad`: each octave samples at
`p * frequency`, so by the chain rule its gradient scales by that same
frequency, and both amplitude (`gain`) and frequency (`lacunarity`) compound

View file

@ -76,8 +76,8 @@ float cultmath_smootherstep(float value)
float2 cultmath_snoise_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
float3 cultmath_snoise_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
float4 cultmath_snoise_mod289(float4 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
float4 cultmath_snoise_permute(float4 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((value * 34.0) + 10.0) * value); }
float4 cultmath_snoise_permute(float4 value) { return cultmath_snoise_mod289(((value * 34.0) + 10.0) * value); }
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), same float32 evaluation
// order as the C# math.snoise(float3) mirror.
@ -125,9 +125,9 @@ float cultmath_snoise(float3 value)
p2 *= norm.z;
p3 *= norm.w;
float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
float4 m = max(0.5 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
m = m * m;
return 42.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
return 105.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
}
float cultmath_snoise(float2 value)
@ -162,12 +162,11 @@ float cultmath_snoise(float2 value)
return 130.0 * dot(m, g);
}
// Ashima Arts / Ian McEwan 3D simplex noise with analytic gradient (invariant 8), following the
// differentiation in webgl-noise src/noise3Dgrad.glsl (Ashima Arts / McEwan, MIT; confirmed at
// stegu/webgl-noise today), retargeted onto cultmath_snoise(float3)'s own constants (0.6 falloff
// radius, permute(x) = mod289((34x+1)x), scale 42; see the C# math.snoise_grad comment for the full
// derivation and why the differentiation itself is unaffected by that retargeting). Every local up
// through p0..p3 and x0..x3 is cultmath_snoise(float3)'s own derivation verbatim.
// Analytic gradient of cultmath_snoise(float3) (invariant 8), following webgl-noise
// src/noise3Dgrad.glsl (Ashima Arts / McEwan, MIT); same kernel, permutation and scale as
// cultmath_snoise(float3) (stegu/webgl-noise 22434e04d7). See the C# math.snoise_grad comment for the
// derivation. Every local up through p0..p3 and x0..x3 is cultmath_snoise(float3)'s own derivation
// verbatim.
float4 cultmath_snoise_grad(float3 value)
{
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
@ -212,18 +211,18 @@ float4 cultmath_snoise_grad(float3 value)
p2 *= norm.z;
p3 *= norm.w;
float4 m0 = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
float4 m0 = max(0.5 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
float4 m2 = m0 * m0;
float4 m3 = m2 * m0;
float4 m4 = m2 * m2;
float4 px = float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3));
float value2 = 42.0 * dot(m4, px);
float value2 = 105.0 * dot(m4, px);
float3 grad = -8.0 * m3.x * x0 * px.x + m4.x * p0
+ -8.0 * m3.y * x1 * px.y + m4.y * p1
+ -8.0 * m3.z * x2 * px.z + m4.z * p2
+ -8.0 * m3.w * x3 * px.w + m4.w * p3;
grad *= 42.0;
grad *= 105.0;
return float4(grad, value2);
}

View file

@ -595,7 +595,10 @@ public static partial class math
}
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), mirrored by cultmath_snoise(float3)
// in shaders/CultMath.hlsl with the same float32 evaluation order.
// in shaders/CultMath.hlsl with the same float32 evaluation order. Follows stegu/webgl-noise
// src/noise3D.glsl at 22434e04d7 (kernel radius^2 0.5, scale 105, permute (34x+10)x); see
// THIRD-PARTY-NOTICES.md. Continuous across simplex-cell boundaries
// (NoiseGradTests.SnoiseAndItsGradientAreContinuousAcrossSimplexCellBoundaries).
public static float snoise(float3 value)
{
const float cx = 1.0f / 6.0f;
@ -642,9 +645,9 @@ public static partial class math
p2 *= norm.z;
p3 *= norm.w;
var m = max(0.6f - new float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0f);
var m = max(0.5f - new float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0f);
m = m * m;
return 42.0f * dot(m * m, new float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
return 105.0f * dot(m * m, new float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
}
// Ashima Arts / Ian McEwan 2D simplex noise, kept component-explicit so
@ -690,25 +693,18 @@ public static partial class math
private static float2 snoise_mod289(float2 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
private static float3 snoise_mod289(float3 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
private static float4 snoise_mod289(float4 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
private static float3 snoise_permute(float3 value) => snoise_mod289(((value * 34.0f) + 1.0f) * value);
private static float4 snoise_permute(float4 value) => snoise_mod289(((value * 34.0f) + 1.0f) * value);
private static float3 snoise_permute(float3 value) => snoise_mod289(((value * 34.0f) + 10.0f) * value);
private static float4 snoise_permute(float4 value) => snoise_mod289(((value * 34.0f) + 10.0f) * value);
// Ashima Arts / Ian McEwan 3D simplex noise with analytic gradient (invariant 8), following the
// differentiation in webgl-noise src/noise3Dgrad.glsl (Ashima Arts / McEwan, MIT; confirmed at
// stegu/webgl-noise today), retargeted onto THIS file's own snoise(float3) constants: upstream's
// noise3Dgrad.glsl uses a 0.5 falloff radius, permute(x) = mod289((34x+10)x) and an overall scale
// of 105, while this repo's existing snoise(float3) (and cultmath_snoise) use 0.6, permute(x) =
// mod289((34x+1)x) and scale 42 (Ashima's alternate, second-order-artifact-reduced noise3D.glsl
// constants, already load-bearing here and pinned by the existing snoise tests): every local up
// through p0..p3 and x0..x3 below is snoise(float3)'s own derivation verbatim, unchanged, so .w
// stays that same field (NoiseGradTests.SnoiseGradValueIsBitEqualToSnoise pins the two bit-equal
// over a random sweep, stronger than the spec's 1e-6 floor). The differentiation itself carries
// over unchanged from upstream, because it never
// depends on the threshold or permutation constants: per corner, m0 = max(radius - dot(x,x), 0),
// so dm0/dx = -2x, and d(m0^4 * dot(p,x))/dx = 4*m0^3*(-2x)*dot(p,x) + m0^4*p
// = -8*m0^3*dot(p,x)*x + m0^4*p (p is that corner's own gradient constant, independent of x, so
// only the dot(p,x) factor contributes there); summed over the four corners and scaled by the
// same 42 as the value.
// Analytic gradient of snoise(float3) (invariant 8), following the differentiation in
// webgl-noise src/noise3Dgrad.glsl (Ashima Arts / McEwan, MIT), which uses the same kernel
// radius^2 0.5, permutation (34x+10)x and scale 105 as snoise(float3) (stegu/webgl-noise
// 22434e04d7). Every local up through p0..p3 and x0..x3 below is snoise(float3)'s own derivation
// verbatim, so .w is that same field (NoiseGradTests.SnoiseGradValueIsBitEqualToSnoise pins the
// two bit-equal over a random sweep). Per corner, m0 = max(0.5 - dot(x,x), 0), so dm0/dx = -2x,
// and d(m0^4 * dot(p,x))/dx = 4*m0^3*(-2x)*dot(p,x) + m0^4*p = -8*m0^3*dot(p,x)*x + m0^4*p (p is
// that corner's own gradient constant, independent of x); summed over the four corners and
// scaled by the same 105 as the value.
public static float4 snoise_grad(float3 value)
{
const float cx = 1.0f / 6.0f;
@ -755,18 +751,18 @@ public static partial class math
p2 *= norm.z;
p3 *= norm.w;
var m0 = max(0.6f - new float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0f);
var m0 = max(0.5f - new float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0f);
var m2 = m0 * m0;
var m3 = m2 * m0;
var m4 = m2 * m2;
var px = new float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3));
var value2 = 42.0f * dot(m4, px);
var value2 = 105.0f * dot(m4, px);
var grad = -8.0f * m3.x * x0 * px.x + m4.x * p0
+ -8.0f * m3.y * x1 * px.y + m4.y * p1
+ -8.0f * m3.z * x2 * px.z + m4.z * p2
+ -8.0f * m3.w * x3 * px.w + m4.w * p3;
grad *= 42.0f;
grad *= 105.0f;
return new float4(grad, value2);
}

View file

@ -76,8 +76,8 @@ float cultmath_smootherstep(float value)
float2 cultmath_snoise_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
float3 cultmath_snoise_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
float4 cultmath_snoise_mod289(float4 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
float4 cultmath_snoise_permute(float4 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((value * 34.0) + 10.0) * value); }
float4 cultmath_snoise_permute(float4 value) { return cultmath_snoise_mod289(((value * 34.0) + 10.0) * value); }
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), same float32 evaluation
// order as the C# math.snoise(float3) mirror.
@ -125,9 +125,9 @@ float cultmath_snoise(float3 value)
p2 *= norm.z;
p3 *= norm.w;
float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
float4 m = max(0.5 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
m = m * m;
return 42.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
return 105.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
}
float cultmath_snoise(float2 value)
@ -162,12 +162,11 @@ float cultmath_snoise(float2 value)
return 130.0 * dot(m, g);
}
// Ashima Arts / Ian McEwan 3D simplex noise with analytic gradient (invariant 8), following the
// differentiation in webgl-noise src/noise3Dgrad.glsl (Ashima Arts / McEwan, MIT; confirmed at
// stegu/webgl-noise today), retargeted onto cultmath_snoise(float3)'s own constants (0.6 falloff
// radius, permute(x) = mod289((34x+1)x), scale 42; see the C# math.snoise_grad comment for the full
// derivation and why the differentiation itself is unaffected by that retargeting). Every local up
// through p0..p3 and x0..x3 is cultmath_snoise(float3)'s own derivation verbatim.
// Analytic gradient of cultmath_snoise(float3) (invariant 8), following webgl-noise
// src/noise3Dgrad.glsl (Ashima Arts / McEwan, MIT); same kernel, permutation and scale as
// cultmath_snoise(float3) (stegu/webgl-noise 22434e04d7). See the C# math.snoise_grad comment for the
// derivation. Every local up through p0..p3 and x0..x3 is cultmath_snoise(float3)'s own derivation
// verbatim.
float4 cultmath_snoise_grad(float3 value)
{
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
@ -212,18 +211,18 @@ float4 cultmath_snoise_grad(float3 value)
p2 *= norm.z;
p3 *= norm.w;
float4 m0 = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
float4 m0 = max(0.5 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
float4 m2 = m0 * m0;
float4 m3 = m2 * m0;
float4 m4 = m2 * m2;
float4 px = float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3));
float value2 = 42.0 * dot(m4, px);
float value2 = 105.0 * dot(m4, px);
float3 grad = -8.0 * m3.x * x0 * px.x + m4.x * p0
+ -8.0 * m3.y * x1 * px.y + m4.y * p1
+ -8.0 * m3.z * x2 * px.z + m4.z * p2
+ -8.0 * m3.w * x3 * px.w + m4.w * p3;
grad *= 42.0;
grad *= 105.0;
return float4(grad, value2);
}

View file

@ -6,11 +6,15 @@
simplex-noise functions by Ian McEwan and Ashima Arts, as distributed by Stefan
Gustavson's `webgl-noise` project and Unity Mathematics.
`math.snoise(float3)` and `cultmath_snoise(float3)` follow `src/noise3D.glsl` and
`math.snoise(float2)` follows `src/noise2D.glsl` of stegu/webgl-noise at commit
`22434e04d7753f7e949e8d724ab3da2864c17a0f` (the simplex-boundary fix is
`21d9fe23d7`, scale `ff3b5d34ea`, permutation `a3e6d57095`).
`math.snoise_grad`/`cultmath_snoise_grad` (and the `fbm_grad`/`ridged_grad`
octave sums built on them) follow the analytic-gradient differentiation from
the same project's `src/noise3Dgrad.glsl`, retargeted onto this file's own
`snoise(float3)` falloff radius, permutation, and scale constants (see the
`math.snoise_grad` doc comment).
the same project's `src/noise3Dgrad.glsl`, with the same kernel radius,
permutation and scale as `snoise(float3)`.
- Copyright 2011 Ashima Arts.
- Upstream: <https://github.com/ashima/webgl-noise>