Add Unity-compatible simplex noise semantics

This commit is contained in:
Meta Vens 2026-07-17 07:45:28 +01:00
commit cb481b4c75
5 changed files with 101 additions and 0 deletions

View file

@ -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: <https://github.com/ashima/webgl-noise>
- License: MIT.
## Advanced Terrain Erosion Filter
`src/CultMath/AdvancedErosionFilter.cs` and

View file

@ -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); }

View file

@ -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);

View file

@ -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",

View file

@ -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()
{