From 8703b44636d1de6ca86a0df2c7f3cb6e5a1db335 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 29 Sep 2026 11:13:34 +0200 Subject: [PATCH] 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 --- packages/cultmath/THIRD-PARTY-NOTICES.md | 10 +++-- packages/cultmath/docs/design.md | 24 +++++++--- packages/cultmath/shaders/CultMath.hlsl | 25 +++++------ packages/cultmath/src/CultMath/math.cs | 44 +++++++++---------- .../Shaders/CultMath.hlsl | 25 +++++------ .../THIRD-PARTY-NOTICES.md | 10 +++-- 6 files changed, 75 insertions(+), 63 deletions(-) diff --git a/packages/cultmath/THIRD-PARTY-NOTICES.md b/packages/cultmath/THIRD-PARTY-NOTICES.md index 2b4f2acd..1de744a7 100644 --- a/packages/cultmath/THIRD-PARTY-NOTICES.md +++ b/packages/cultmath/THIRD-PARTY-NOTICES.md @@ -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: diff --git a/packages/cultmath/docs/design.md b/packages/cultmath/docs/design.md index 408a49a3..e14927fe 100644 --- a/packages/cultmath/docs/design.md +++ b/packages/cultmath/docs/design.md @@ -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 diff --git a/packages/cultmath/shaders/CultMath.hlsl b/packages/cultmath/shaders/CultMath.hlsl index b7cb69ca..50c5a746 100644 --- a/packages/cultmath/shaders/CultMath.hlsl +++ b/packages/cultmath/shaders/CultMath.hlsl @@ -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); } diff --git a/packages/cultmath/src/CultMath/math.cs b/packages/cultmath/src/CultMath/math.cs index 380267c8..23916b6b 100644 --- a/packages/cultmath/src/CultMath/math.cs +++ b/packages/cultmath/src/CultMath/math.cs @@ -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); } diff --git a/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl b/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl index b7cb69ca..50c5a746 100644 --- a/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl +++ b/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl @@ -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); } diff --git a/packages/cultmath/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md b/packages/cultmath/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md index 2b4f2acd..1de744a7 100644 --- a/packages/cultmath/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md +++ b/packages/cultmath/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md @@ -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: