snoise tie rule: x over y, y over z, x over z (a total order) in C# and both HLSL copies
The cyclic non-strict step(x0.yzx, x0.xyz) made the four corners a non-simplex where x0's components are equal: the origin and every (t, t, t) read a different function (snoise(0) was -0.4358730, jumps up to 0.77). One strict compare, z against x, gives simplexnoise1234's order. Off the tie set old and new return the same bits. NoiseGradTests.SnoiseIsContinuousOnTheSimplexDiagonal fails against the old rule (0.054 within 1 ulp of (-53.6, -53.6, -53.6)). The origin golden is 0 and a (1.25, 1.25, 1.25) diagonal row joins it. SNOISE_LIPSCHITZ and the GLSL follow in the next commits, so the ratio pin and CommittedGlslEqualsLowering are red here. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a796614276
commit
e89fd68778
5 changed files with 74 additions and 11 deletions
|
|
@ -80,13 +80,14 @@ float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((v
|
|||
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.
|
||||
// order as the C# math.snoise(float3) mirror. Ties resolve in simplexnoise1234's
|
||||
// total order; see math.snoise.
|
||||
float cultmath_snoise(float3 value)
|
||||
{
|
||||
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
|
||||
float3 i = floor(value + dot(value, c.yyy));
|
||||
float3 x0 = value - i + dot(i, c.xxx);
|
||||
float3 g = step(x0.yzx, x0.xyz);
|
||||
float3 g = float3(step(x0.y, x0.x), step(x0.z, x0.y), 1.0 - step(x0.z, x0.x));
|
||||
float3 l = 1.0 - g;
|
||||
float3 i1 = min(g.xyz, l.zxy);
|
||||
float3 i2 = max(g.xyz, l.zxy);
|
||||
|
|
@ -172,7 +173,7 @@ float4 cultmath_snoise_grad(float3 value)
|
|||
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
|
||||
float3 i = floor(value + dot(value, c.yyy));
|
||||
float3 x0 = value - i + dot(i, c.xxx);
|
||||
float3 g = step(x0.yzx, x0.xyz);
|
||||
float3 g = float3(step(x0.y, x0.x), step(x0.z, x0.y), 1.0 - step(x0.z, x0.x));
|
||||
float3 l = 1.0 - g;
|
||||
float3 i1 = min(g.xyz, l.zxy);
|
||||
float3 i2 = max(g.xyz, l.zxy);
|
||||
|
|
|
|||
|
|
@ -599,7 +599,10 @@ public static partial class math
|
|||
// 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).
|
||||
// (NoiseGradTests.SnoiseAndItsGradientAreContinuousAcrossSimplexCellBoundaries). Ties in x0 resolve
|
||||
// in Gustavson's simplexnoise1234 order (x over y, y over z, x over z), a total order, so the four
|
||||
// corners are always a simplex; upstream's step is cyclic and degenerates where x0's components are
|
||||
// equal (NoiseGradTests.SnoiseIsContinuousOnTheSimplexDiagonal).
|
||||
// SNOISE_LIPSCHITZ (math.Interval.cs) is measured from this kernel: a change here must re-run
|
||||
// NoiseBoundTests.MeasureLipschitz and re-pin it.
|
||||
public static float snoise(float3 value)
|
||||
|
|
@ -608,7 +611,7 @@ public static partial class math
|
|||
const float cy = 1.0f / 3.0f;
|
||||
var i = floor(value + dot(value, new float3(cy, cy, cy)));
|
||||
var x0 = value - i + dot(i, new float3(cx, cx, cx));
|
||||
var g = step(new float3(x0.y, x0.z, x0.x), x0);
|
||||
var g = new float3(step(x0.y, x0.x), step(x0.z, x0.y), 1.0f - step(x0.z, x0.x));
|
||||
var l = 1.0f - g;
|
||||
var lzxy = new float3(l.z, l.x, l.y);
|
||||
var i1 = min(g, lzxy);
|
||||
|
|
@ -714,7 +717,7 @@ public static partial class math
|
|||
const float cy = 1.0f / 3.0f;
|
||||
var i = floor(value + dot(value, new float3(cy, cy, cy)));
|
||||
var x0 = value - i + dot(i, new float3(cx, cx, cx));
|
||||
var g = step(new float3(x0.y, x0.z, x0.x), x0);
|
||||
var g = new float3(step(x0.y, x0.x), step(x0.z, x0.y), 1.0f - step(x0.z, x0.x));
|
||||
var l = 1.0f - g;
|
||||
var lzxy = new float3(l.z, l.x, l.y);
|
||||
var i1 = min(g, lzxy);
|
||||
|
|
|
|||
|
|
@ -444,7 +444,8 @@ public sealed class HlslSemanticsTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0.0f, 0.0f, 0.0f, -0.435872972f)]
|
||||
[InlineData(0.0f, 0.0f, 0.0f, 0.0f)]
|
||||
[InlineData(1.25f, 1.25f, 1.25f, -0.245501652f)]
|
||||
[InlineData(0.25f, -0.5f, 1.75f, 0.0492948629f)]
|
||||
[InlineData(12.25f, -4.5f, 3.125f, 0.443114728f)]
|
||||
[InlineData(-7.3f, 2.9f, 101.4f, -0.10221792f)]
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public sealed class NoiseGradTests
|
|||
{
|
||||
var i = math.floor(p + math.dot(p, new float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)));
|
||||
var x0 = p - i + math.dot(i, new float3(1.0f / 6.0f, 1.0f / 6.0f, 1.0f / 6.0f));
|
||||
var g = math.step(new float3(x0.y, x0.z, x0.x), x0);
|
||||
var g = new float3(math.step(x0.y, x0.x), math.step(x0.z, x0.y), 1.0f - math.step(x0.z, x0.x));
|
||||
var l = 1.0f - g;
|
||||
var lzxy = new float3(l.z, l.x, l.y);
|
||||
return (i, math.min(g, lzxy), math.max(g, lzxy));
|
||||
|
|
@ -190,6 +190,63 @@ public sealed class NoiseGradTests
|
|||
Assert.True(maxGradientJump < 1.0e-4f, $"gradient jump {maxGradientJump} across a simplex boundary");
|
||||
}
|
||||
|
||||
// The simplex diagonals are where x0's three components compare equal. The tie rule must be a total
|
||||
// order there (math.snoise): with a cyclic or all-strict compare the four corners stop being a
|
||||
// simplex and the value jumps by up to 0.77 on the line (t, t, t), lattice diagonals included.
|
||||
[Fact]
|
||||
public void SnoiseIsContinuousOnTheSimplexDiagonal()
|
||||
{
|
||||
const int vertices = 160;
|
||||
const int perVertex = 8;
|
||||
var random = new System.Random(0x71E5);
|
||||
var points = new System.Collections.Generic.List<float3>();
|
||||
for (var k = 0; k < 400; k++)
|
||||
{
|
||||
var t = (float)(random.NextDouble() * 128.0 - 64.0);
|
||||
points.Add(new float3(t, t, t));
|
||||
}
|
||||
|
||||
points.Add(new float3(0.0f, 0.0f, 0.0f));
|
||||
for (var k = 0; k < vertices; k++)
|
||||
{
|
||||
var lx = random.Next(-64, 65);
|
||||
var ly = random.Next(-64, 65);
|
||||
var lz = random.Next(-64, 65);
|
||||
var shift = (lx + ly + lz) / 6.0f;
|
||||
var v = new float3(lx - shift, ly - shift, lz - shift);
|
||||
for (var j = 0; j < perVertex; j++)
|
||||
{
|
||||
var s = j == 0 ? 0.0f : random.NextSingle();
|
||||
points.Add(new float3(v.x + s, v.y + s, v.z + s));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var p in points)
|
||||
{
|
||||
var value = math.snoise(p);
|
||||
for (var axis = 0; axis < 3; axis++)
|
||||
{
|
||||
for (var direction = -1; direction <= 1; direction += 2)
|
||||
{
|
||||
var q = p;
|
||||
for (var ulp = 1; ulp <= 2; ulp++)
|
||||
{
|
||||
var component = axis == 0 ? q.x : axis == 1 ? q.y : q.z;
|
||||
component = direction < 0 ? MathF.BitDecrement(component) : MathF.BitIncrement(component);
|
||||
q = axis == 0 ? new float3(component, q.y, q.z) : axis == 1 ? new float3(q.x, component, q.z) : new float3(q.x, q.y, component);
|
||||
var jump = MathF.Abs(math.snoise(q) - value);
|
||||
Assert.True(jump <= 1.0e-4f, $"snoise jumps {jump} within {ulp} ulp of {p}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var grad = math.snoise_grad(p);
|
||||
Assert.Equal(BitConverter.SingleToInt32Bits(value), BitConverter.SingleToInt32Bits(grad.w));
|
||||
var slope = math.length(new float3(grad.x, grad.y, grad.z));
|
||||
Assert.True(slope <= math.SNOISE_LIPSCHITZ, $"|snoise_grad| {slope} exceeds SNOISE_LIPSCHITZ at {p}");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- snoise_grad.w vs snoise ----
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -80,13 +80,14 @@ float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((v
|
|||
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.
|
||||
// order as the C# math.snoise(float3) mirror. Ties resolve in simplexnoise1234's
|
||||
// total order; see math.snoise.
|
||||
float cultmath_snoise(float3 value)
|
||||
{
|
||||
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
|
||||
float3 i = floor(value + dot(value, c.yyy));
|
||||
float3 x0 = value - i + dot(i, c.xxx);
|
||||
float3 g = step(x0.yzx, x0.xyz);
|
||||
float3 g = float3(step(x0.y, x0.x), step(x0.z, x0.y), 1.0 - step(x0.z, x0.x));
|
||||
float3 l = 1.0 - g;
|
||||
float3 i1 = min(g.xyz, l.zxy);
|
||||
float3 i2 = max(g.xyz, l.zxy);
|
||||
|
|
@ -172,7 +173,7 @@ float4 cultmath_snoise_grad(float3 value)
|
|||
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
|
||||
float3 i = floor(value + dot(value, c.yyy));
|
||||
float3 x0 = value - i + dot(i, c.xxx);
|
||||
float3 g = step(x0.yzx, x0.xyz);
|
||||
float3 g = float3(step(x0.y, x0.x), step(x0.z, x0.y), 1.0 - step(x0.z, x0.x));
|
||||
float3 l = 1.0 - g;
|
||||
float3 i1 = min(g.xyz, l.zxy);
|
||||
float3 i2 = max(g.xyz, l.zxy);
|
||||
|
|
|
|||
Loading…
Reference in a new issue