diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 8339c5f..54e280f 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -1,5 +1,15 @@ # Third-Party Notices +## 2D Simplex Noise + +`math.simplex_noise` and `cultmath_simplex_noise` port the textureless 2D +simplex-noise function by Ian McEwan and Ashima Arts, as distributed by Stefan +Gustavson's `webgl-noise` project and Unity Mathematics. + +- Copyright 2011 Ashima Arts. +- Upstream: +- License: MIT. + ## Advanced Terrain Erosion Filter `src/CultMath/AdvancedErosionFilter.cs` and diff --git a/shaders/CultMath.hlsl b/shaders/CultMath.hlsl index cf76c8c..84d244b 100644 --- a/shaders/CultMath.hlsl +++ b/shaders/CultMath.hlsl @@ -73,6 +73,42 @@ float cultmath_smootherstep(float value) return value * value * value * (value * (value * 6.0 - 15.0) + 10.0); } +float2 cultmath_simplex_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; } +float3 cultmath_simplex_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; } +float3 cultmath_simplex_permute(float3 value) { return cultmath_simplex_mod289(((value * 34.0) + 1.0) * value); } + +float cultmath_simplex_noise(float2 value) +{ + const float4 c = float4( + 0.211324865405187, + 0.366025403784439, + -0.577350269189626, + 0.024390243902439); + float2 i = floor(value + dot(value, c.yy)); + float2 x0 = value - i + dot(i, c.xx); + float2 i1 = x0.x > x0.y ? float2(1.0, 0.0) : float2(0.0, 1.0); + float4 x12 = float4(x0.x + c.x - i1.x, x0.y + c.x - i1.y, x0.x + c.z, x0.y + c.z); + + i = cultmath_simplex_mod289(i); + float3 p = cultmath_simplex_permute( + cultmath_simplex_permute(i.y + float3(0.0, i1.y, 1.0)) + i.x + float3(0.0, i1.x, 1.0)); + float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0); + m *= m; + m *= m; + + float3 x = 2.0 * frac(p * c.w) - 1.0; + float3 h = abs(x) - 0.5; + float3 ox = floor(x + 0.5); + float3 a0 = x - ox; + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + float3 g = float3( + a0.x * x0.x + h.x * x0.y, + a0.y * x12.x + h.y * x12.y, + a0.z * x12.z + h.z * x12.w); + return 130.0 * dot(m, g); +} + float cultmath_lengthsq(float2 value) { return dot(value, value); } float cultmath_lengthsq(float3 value) { return dot(value, value); } float cultmath_lengthsq(float4 value) { return dot(value, value); } diff --git a/src/CultMath/math.cs b/src/CultMath/math.cs index 874554a..e15fbf0 100644 --- a/src/CultMath/math.cs +++ b/src/CultMath/math.cs @@ -327,6 +327,50 @@ public static class math public static float hash(float2 value) => hash(dot(value, new float2(127.1f, 311.7f))); public static float hash(float3 value) => hash(dot(value, new float3(127.1f, 311.7f, 74.7f))); + // Ashima Arts / Ian McEwan 2D simplex noise, kept component-explicit so + // the C# and HLSL mirrors preserve the same float32 evaluation order. + public static float simplex_noise(float2 value) + { + var c = new float4( + 0.211324865405187f, + 0.366025403784439f, + -0.577350269189626f, + 0.024390243902439f); + var i = floor(value + dot(value, new float2(c.y, c.y))); + var x0 = value - i + dot(i, new float2(c.x, c.x)); + var i1 = x0.x > x0.y ? new float2(1.0f, 0.0f) : new float2(0.0f, 1.0f); + var x12 = new float4( + x0.x + c.x - i1.x, + x0.y + c.x - i1.y, + x0.x + c.z, + x0.y + c.z); + + i = simplex_mod289(i); + var p = simplex_permute(simplex_permute(i.y + new float3(0.0f, i1.y, 1.0f)) + i.x + new float3(0.0f, i1.x, 1.0f)); + var m = max(0.5f - new float3( + dot(x0, x0), + x12.x * x12.x + x12.y * x12.y, + x12.z * x12.z + x12.w * x12.w), 0.0f); + m = m * m; + m = m * m; + + var x = 2.0f * frac(p * c.w) - 1.0f; + var h = abs(x) - 0.5f; + var ox = floor(x + 0.5f); + var a0 = x - ox; + m *= 1.79284291400159f - 0.85373472095314f * (a0 * a0 + h * h); + + var g = new float3( + a0.x * x0.x + h.x * x0.y, + a0.y * x12.x + h.y * x12.y, + a0.z * x12.z + h.z * x12.w); + return 130.0f * dot(m, g); + } + + private static float2 simplex_mod289(float2 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f; + private static float3 simplex_mod289(float3 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f; + private static float3 simplex_permute(float3 value) => simplex_mod289(((value * 34.0f) + 1.0f) * value); + public static float value_noise(float2 position) { var cell = floor(position); diff --git a/tests/CultMath.Tests/HlslMirrorTests.cs b/tests/CultMath.Tests/HlslMirrorTests.cs index 5e1451c..47f5897 100644 --- a/tests/CultMath.Tests/HlslMirrorTests.cs +++ b/tests/CultMath.Tests/HlslMirrorTests.cs @@ -51,6 +51,7 @@ public sealed class HlslMirrorTests "cultmath_cubic_bezier", "cultmath_hash", "cultmath_value_noise", + "cultmath_simplex_noise", "cultmath_value_noise_bicubic", "cultmath_value_noise_texture", "cultmath_value_noise_texture_bicubic", diff --git a/tests/CultMath.Tests/MathTests.cs b/tests/CultMath.Tests/MathTests.cs index bb816d4..6979bdd 100644 --- a/tests/CultMath.Tests/MathTests.cs +++ b/tests/CultMath.Tests/MathTests.cs @@ -235,6 +235,16 @@ public sealed class MathTests Assert.True(bicubic > -0.25f && bicubic < 1.25f); } + [Theory] + [InlineData(0.0f, 0.0f, 0.0f)] + [InlineData(0.25f, -0.5f, -0.425866783f)] + [InlineData(12.25f, -4.5f, 0.258312f)] + [InlineData(536.5106f, 536.5106f, -0.6901103f)] + public void SimplexNoiseMatchesUnityMathematics(float x, float y, float expected) + { + Assert.Equal(expected, math.simplex_noise(new float2(x, y)), precision: 6); + } + [Fact] public void VoronoiBatchProducesToneMappedColors() {