2026-06-10 21:22:43 +01:00
|
|
|
#ifndef CULTMATH_HLSL
|
|
|
|
|
#define CULTMATH_HLSL
|
|
|
|
|
|
|
|
|
|
// CultMath HLSL mirror.
|
|
|
|
|
//
|
|
|
|
|
// HLSL already owns the float2/float3/float4 type names and most primitive
|
|
|
|
|
// intrinsics. This include names the CultMath semantic surface explicitly so
|
|
|
|
|
// shader code, C# wrappers, and Rust/native parity fixtures can share the same
|
|
|
|
|
// small vocabulary without local helper drift.
|
|
|
|
|
|
|
|
|
|
static const float CULTMATH_PI = 3.14159265358979323846;
|
|
|
|
|
static const float CULTMATH_TAU = 6.28318530717958647692;
|
|
|
|
|
static const float CULTMATH_HALF_PI = 1.57079632679489661923;
|
|
|
|
|
|
|
|
|
|
float cultmath_radians(float degrees_value) { return degrees_value * (CULTMATH_PI / 180.0); }
|
|
|
|
|
float cultmath_degrees(float radians_value) { return radians_value * (180.0 / CULTMATH_PI); }
|
|
|
|
|
|
|
|
|
|
float cultmath_frac(float value) { return value - floor(value); }
|
|
|
|
|
float2 cultmath_frac(float2 value) { return value - floor(value); }
|
|
|
|
|
float3 cultmath_frac(float3 value) { return value - floor(value); }
|
|
|
|
|
float4 cultmath_frac(float4 value) { return value - floor(value); }
|
|
|
|
|
|
|
|
|
|
float cultmath_clamp(float value, float minimum, float maximum) { return min(max(value, minimum), maximum); }
|
|
|
|
|
float2 cultmath_clamp(float2 value, float2 minimum, float2 maximum) { return min(max(value, minimum), maximum); }
|
|
|
|
|
float3 cultmath_clamp(float3 value, float3 minimum, float3 maximum) { return min(max(value, minimum), maximum); }
|
|
|
|
|
float4 cultmath_clamp(float4 value, float4 minimum, float4 maximum) { return min(max(value, minimum), maximum); }
|
|
|
|
|
|
Match float min, max, clamp, and saturate to DXIL FMin/FMax/Saturate
dxc lowers float min/max/clamp to dx.op FMin/FMax (36/35) and saturate to
dx.op Saturate (7). DXIL.rst defines them: FMin(a, b) is a < b ? a : b,
FMax(a, b) is a >= b ? a : b, a NaN operand returns the other, and
Saturate is FMin(1, FMax(0, x)). C# used MathF.Min/Max, which pass NaN
through and order signed zeros, so saturate(NaN) was NaN instead of 0.
Every float overload now follows the spec, normalize uses the same max as
its mirror, and the mirror's saturate spells Saturate's operand order
(which maps -0 to +0, where clamp(x, 0, 1) keeps -0). Non-NaN results
change only in the sign of a zero. design.md records the rules and that
dxc marks float math fast, so GPU NaN results are not guaranteed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:32:19 +02:00
|
|
|
// DXIL Saturate is FMin(1, FMax(0, x)); dxc lowers min/max to the same FMin/FMax, NaN rules included.
|
|
|
|
|
float cultmath_saturate(float value) { return min(1.0, max(0.0, value)); }
|
|
|
|
|
float2 cultmath_saturate(float2 value) { return min(float2(1.0, 1.0), max(float2(0.0, 0.0), value)); }
|
|
|
|
|
float3 cultmath_saturate(float3 value) { return min(float3(1.0, 1.0, 1.0), max(float3(0.0, 0.0, 0.0), value)); }
|
|
|
|
|
float4 cultmath_saturate(float4 value) { return min(float4(1.0, 1.0, 1.0, 1.0), max(float4(0.0, 0.0, 0.0, 0.0), value)); }
|
2026-06-10 21:22:43 +01:00
|
|
|
|
|
|
|
|
float cultmath_lerp(float start, float end, float amount) { return start + (end - start) * amount; }
|
|
|
|
|
float2 cultmath_lerp(float2 start, float2 end, float2 amount) { return start + (end - start) * amount; }
|
|
|
|
|
float3 cultmath_lerp(float3 start, float3 end, float3 amount) { return start + (end - start) * amount; }
|
|
|
|
|
float4 cultmath_lerp(float4 start, float4 end, float4 amount) { return start + (end - start) * amount; }
|
|
|
|
|
|
2026-09-14 14:09:49 +02:00
|
|
|
float cultmath_step(float edge, float value) { return value < edge ? 0.0 : 1.0; }
|
2026-06-10 21:22:43 +01:00
|
|
|
float2 cultmath_step(float2 edge, float2 value) { return float2(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y)); }
|
|
|
|
|
float3 cultmath_step(float3 edge, float3 value) { return float3(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z)); }
|
|
|
|
|
float4 cultmath_step(float4 edge, float4 value) { return float4(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z), cultmath_step(edge.w, value.w)); }
|
|
|
|
|
|
|
|
|
|
float cultmath_smoothstep(float minimum, float maximum, float value)
|
|
|
|
|
{
|
|
|
|
|
float t = cultmath_saturate((value - minimum) / (maximum - minimum));
|
|
|
|
|
return t * t * (3.0 - 2.0 * t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float2 cultmath_smoothstep(float2 minimum, float2 maximum, float2 value)
|
|
|
|
|
{
|
|
|
|
|
float2 t = cultmath_saturate((value - minimum) / (maximum - minimum));
|
|
|
|
|
return t * t * (float2(3.0, 3.0) - float2(2.0, 2.0) * t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float3 cultmath_smoothstep(float3 minimum, float3 maximum, float3 value)
|
|
|
|
|
{
|
|
|
|
|
float3 t = cultmath_saturate((value - minimum) / (maximum - minimum));
|
|
|
|
|
return t * t * (float3(3.0, 3.0, 3.0) - float3(2.0, 2.0, 2.0) * t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 cultmath_smoothstep(float4 minimum, float4 maximum, float4 value)
|
|
|
|
|
{
|
|
|
|
|
float4 t = cultmath_saturate((value - minimum) / (maximum - minimum));
|
|
|
|
|
return t * t * (float4(3.0, 3.0, 3.0, 3.0) - float4(2.0, 2.0, 2.0, 2.0) * t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_smoothstep01(float value) { return cultmath_smoothstep(0.0, 1.0, value); }
|
|
|
|
|
|
|
|
|
|
float cultmath_smootherstep(float value)
|
|
|
|
|
{
|
|
|
|
|
value = cultmath_saturate(value);
|
|
|
|
|
return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
|
|
|
|
|
}
|
|
|
|
|
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
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; }
|
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>
2026-09-29 11:13:34 +02:00
|
|
|
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); }
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
|
|
|
|
|
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), same float32 evaluation
|
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>
2026-10-04 03:07:02 +02:00
|
|
|
// order as the C# math.snoise(float3) mirror. Ties resolve in simplexnoise1234's
|
|
|
|
|
// total order; see math.snoise.
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
float cultmath_snoise(float3 value)
|
|
|
|
|
{
|
2026-09-14 13:57:03 +02:00
|
|
|
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
float3 i = floor(value + dot(value, c.yyy));
|
|
|
|
|
float3 x0 = value - i + dot(i, c.xxx);
|
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>
2026-10-04 03:07:02 +02:00
|
|
|
float3 g = float3(step(x0.y, x0.x), step(x0.z, x0.y), 1.0 - step(x0.z, x0.x));
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
float3 l = 1.0 - g;
|
|
|
|
|
float3 i1 = min(g.xyz, l.zxy);
|
|
|
|
|
float3 i2 = max(g.xyz, l.zxy);
|
|
|
|
|
float3 x1 = x0 - i1 + c.xxx;
|
|
|
|
|
float3 x2 = x0 - i2 + c.yyy;
|
|
|
|
|
float3 x3 = x0 - 0.5;
|
|
|
|
|
|
|
|
|
|
i = cultmath_snoise_mod289(i);
|
|
|
|
|
float4 p = cultmath_snoise_permute(cultmath_snoise_permute(cultmath_snoise_permute(
|
|
|
|
|
i.z + float4(0.0, i1.z, i2.z, 1.0)) + i.y + float4(0.0, i1.y, i2.y, 1.0)) + i.x + float4(0.0, i1.x, i2.x, 1.0));
|
|
|
|
|
|
|
|
|
|
const float n_ = 0.142857142857;
|
|
|
|
|
float3 ns = float3(2.0 * n_, 0.5 * n_ - 1.0, n_);
|
|
|
|
|
float4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
|
|
|
float4 x_ = floor(j * ns.z);
|
|
|
|
|
float4 y_ = floor(j - 7.0 * x_);
|
|
|
|
|
float4 x = x_ * ns.x + ns.yyyy;
|
|
|
|
|
float4 y = y_ * ns.x + ns.yyyy;
|
|
|
|
|
float4 h = 1.0 - abs(x) - abs(y);
|
|
|
|
|
|
|
|
|
|
float4 b0 = float4(x.xy, y.xy);
|
|
|
|
|
float4 b1 = float4(x.zw, y.zw);
|
|
|
|
|
float4 s0 = floor(b0) * 2.0 + 1.0;
|
|
|
|
|
float4 s1 = floor(b1) * 2.0 + 1.0;
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
float4 sh = -step(h, float4(0.0, 0.0, 0.0, 0.0));
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
|
|
|
|
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
|
|
|
|
|
|
|
|
|
float3 p0 = float3(a0.xy, h.x);
|
|
|
|
|
float3 p1 = float3(a0.zw, h.y);
|
|
|
|
|
float3 p2 = float3(a1.xy, h.z);
|
|
|
|
|
float3 p3 = float3(a1.zw, h.w);
|
|
|
|
|
float4 norm = 1.79284291400159 - 0.85373472095314 * float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3));
|
|
|
|
|
p0 *= norm.x;
|
|
|
|
|
p1 *= norm.y;
|
|
|
|
|
p2 *= norm.z;
|
|
|
|
|
p3 *= norm.w;
|
|
|
|
|
|
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>
2026-09-29 11:13:34 +02:00
|
|
|
float4 m = max(0.5 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
m = m * m;
|
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>
2026-09-29 11:13:34 +02:00
|
|
|
return 105.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
}
|
2026-07-17 07:45:28 +01:00
|
|
|
|
Make CultMath HLSL-shaped: writable components, bool-vector comparisons
HLSL is now CultMath's semantic authority where it defines one.
- float2/3/4, int2, double2/3, bool2 and quaternion are plain structs with
public mutable fields instead of record structs. Indexers and swizzles
(float2.yx; float3 xy xz yx yz zy xyz xzy yzx zyx; float4 xy xyz xyw yzx)
have setters.
- ==, !=, <, >, <=, >= are component-wise and return bool2/3/4. Equals and
GetHashCode stay value-based so vectors remain dictionary keys.
quaternion is not an HLSL type and keeps a bool ==.
- Scalar/vector arithmetic and comparisons get explicit overloads on both
sides instead of leaning on implicit splat.
- New types: bool3, bool4, int3, int4, float2x2, float3x3 (row-major like
HLSL, _mRC fields, Rotate / RotateX/Y/Z / Euler with math.RotationOrder).
- New intrinsics: mul (matrix-vector both sides, matrix-matrix), any, all,
select (HLSL argument order), log, vector pow/sqrt/sin/cos/acos/atan2,
double min/max, int3/int4/bool3/bool4/float2x2/float3x3 constructor
functions.
- simplex_noise is renamed snoise and gains the Ashima 3D variant; the HLSL
mirror follows as cultmath_snoise(float2/float3).
- Random gains NextFloat3() and NextFloat3(float3 max) on the same draw order.
Tests pin snoise(float3), float3x3.Euler and float2x2.Rotate to
Unity.Mathematics reference values, and an HLSL-shaped source block compiles
under using static CultMath.math.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 13:29:59 +02:00
|
|
|
float cultmath_snoise(float2 value)
|
2026-07-17 07:45:28 +01:00
|
|
|
{
|
2026-09-14 13:57:03 +02:00
|
|
|
float4 c = float4(
|
2026-07-17 07:45:28 +01:00
|
|
|
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);
|
|
|
|
|
|
2026-09-14 13:57:03 +02:00
|
|
|
i = cultmath_snoise_mod289(i);
|
|
|
|
|
float3 p = cultmath_snoise_permute(
|
|
|
|
|
cultmath_snoise_permute(i.y + float3(0.0, i1.y, 1.0)) + i.x + float3(0.0, i1.x, 1.0));
|
2026-07-17 07:45:28 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
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>
2026-09-29 11:13:34 +02:00
|
|
|
// 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.
|
2026-09-26 02:36:59 +02:00
|
|
|
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);
|
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>
2026-10-04 03:07:02 +02:00
|
|
|
float3 g = float3(step(x0.y, x0.x), step(x0.z, x0.y), 1.0 - step(x0.z, x0.x));
|
2026-09-26 02:36:59 +02:00
|
|
|
float3 l = 1.0 - g;
|
|
|
|
|
float3 i1 = min(g.xyz, l.zxy);
|
|
|
|
|
float3 i2 = max(g.xyz, l.zxy);
|
|
|
|
|
float3 x1 = x0 - i1 + c.xxx;
|
|
|
|
|
float3 x2 = x0 - i2 + c.yyy;
|
|
|
|
|
float3 x3 = x0 - 0.5;
|
|
|
|
|
|
|
|
|
|
i = cultmath_snoise_mod289(i);
|
|
|
|
|
float4 p = cultmath_snoise_permute(cultmath_snoise_permute(cultmath_snoise_permute(
|
|
|
|
|
i.z + float4(0.0, i1.z, i2.z, 1.0)) + i.y + float4(0.0, i1.y, i2.y, 1.0)) + i.x + float4(0.0, i1.x, i2.x, 1.0));
|
|
|
|
|
|
|
|
|
|
const float n_ = 0.142857142857;
|
|
|
|
|
float3 ns = float3(2.0 * n_, 0.5 * n_ - 1.0, n_);
|
|
|
|
|
float4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
|
|
|
float4 x_ = floor(j * ns.z);
|
|
|
|
|
float4 y_ = floor(j - 7.0 * x_);
|
|
|
|
|
float4 x = x_ * ns.x + ns.yyyy;
|
|
|
|
|
float4 y = y_ * ns.x + ns.yyyy;
|
|
|
|
|
float4 h = 1.0 - abs(x) - abs(y);
|
|
|
|
|
|
|
|
|
|
float4 b0 = float4(x.xy, y.xy);
|
|
|
|
|
float4 b1 = float4(x.zw, y.zw);
|
|
|
|
|
float4 s0 = floor(b0) * 2.0 + 1.0;
|
|
|
|
|
float4 s1 = floor(b1) * 2.0 + 1.0;
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
float4 sh = -step(h, float4(0.0, 0.0, 0.0, 0.0));
|
2026-09-26 02:36:59 +02:00
|
|
|
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
|
|
|
|
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
|
|
|
|
|
|
|
|
|
float3 p0 = float3(a0.xy, h.x);
|
|
|
|
|
float3 p1 = float3(a0.zw, h.y);
|
|
|
|
|
float3 p2 = float3(a1.xy, h.z);
|
|
|
|
|
float3 p3 = float3(a1.zw, h.w);
|
|
|
|
|
float4 norm = 1.79284291400159 - 0.85373472095314 * float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3));
|
|
|
|
|
p0 *= norm.x;
|
|
|
|
|
p1 *= norm.y;
|
|
|
|
|
p2 *= norm.z;
|
|
|
|
|
p3 *= norm.w;
|
|
|
|
|
|
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>
2026-09-29 11:13:34 +02:00
|
|
|
float4 m0 = max(0.5 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
|
2026-09-26 02:36:59 +02:00
|
|
|
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));
|
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>
2026-09-29 11:13:34 +02:00
|
|
|
float value2 = 105.0 * dot(m4, px);
|
2026-09-26 02:36:59 +02:00
|
|
|
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;
|
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>
2026-09-29 11:13:34 +02:00
|
|
|
grad *= 105.0;
|
2026-09-26 02:36:59 +02:00
|
|
|
|
|
|
|
|
return float4(grad, value2);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-14 06:31:43 +02:00
|
|
|
// Analytic Hessian of cultmath_snoise(float3); see the C# math.snoise_hessian comment for the
|
2026-10-14 06:54:48 +02:00
|
|
|
// derivation. Every local up through p0..p3 and x0..x3 is cultmath_snoise_grad's own. The matrix is
|
|
|
|
|
// built from nine scalars, which read as rows in HLSL and as columns in GLSL; it is symmetric, so the
|
|
|
|
|
// two agree.
|
2026-10-14 06:31:43 +02:00
|
|
|
float3x3 cultmath_snoise_hessian(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 = 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);
|
|
|
|
|
float3 x1 = x0 - i1 + c.xxx;
|
|
|
|
|
float3 x2 = x0 - i2 + c.yyy;
|
|
|
|
|
float3 x3 = x0 - 0.5;
|
|
|
|
|
|
|
|
|
|
i = cultmath_snoise_mod289(i);
|
|
|
|
|
float4 p = cultmath_snoise_permute(cultmath_snoise_permute(cultmath_snoise_permute(
|
|
|
|
|
i.z + float4(0.0, i1.z, i2.z, 1.0)) + i.y + float4(0.0, i1.y, i2.y, 1.0)) + i.x + float4(0.0, i1.x, i2.x, 1.0));
|
|
|
|
|
|
|
|
|
|
const float n_ = 0.142857142857;
|
|
|
|
|
float3 ns = float3(2.0 * n_, 0.5 * n_ - 1.0, n_);
|
|
|
|
|
float4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
|
|
|
float4 x_ = floor(j * ns.z);
|
|
|
|
|
float4 y_ = floor(j - 7.0 * x_);
|
|
|
|
|
float4 x = x_ * ns.x + ns.yyyy;
|
|
|
|
|
float4 y = y_ * ns.x + ns.yyyy;
|
|
|
|
|
float4 h = 1.0 - abs(x) - abs(y);
|
|
|
|
|
|
|
|
|
|
float4 b0 = float4(x.xy, y.xy);
|
|
|
|
|
float4 b1 = float4(x.zw, y.zw);
|
|
|
|
|
float4 s0 = floor(b0) * 2.0 + 1.0;
|
|
|
|
|
float4 s1 = floor(b1) * 2.0 + 1.0;
|
|
|
|
|
float4 sh = -step(h, float4(0.0, 0.0, 0.0, 0.0));
|
|
|
|
|
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
|
|
|
|
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
|
|
|
|
|
|
|
|
|
float3 p0 = float3(a0.xy, h.x);
|
|
|
|
|
float3 p1 = float3(a0.zw, h.y);
|
|
|
|
|
float3 p2 = float3(a1.xy, h.z);
|
|
|
|
|
float3 p3 = float3(a1.zw, h.w);
|
|
|
|
|
float4 norm = 1.79284291400159 - 0.85373472095314 * float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3));
|
|
|
|
|
p0 *= norm.x;
|
|
|
|
|
p1 *= norm.y;
|
|
|
|
|
p2 *= norm.z;
|
|
|
|
|
p3 *= norm.w;
|
|
|
|
|
|
|
|
|
|
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 px = float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3));
|
|
|
|
|
float4 wide = 48.0 * m2 * px;
|
|
|
|
|
float4 narrow = 8.0 * m3;
|
|
|
|
|
|
|
|
|
|
float4 cxx = float4(x0.x, x1.x, x2.x, x3.x);
|
|
|
|
|
float4 cxy = float4(x0.y, x1.y, x2.y, x3.y);
|
|
|
|
|
float4 cxz = float4(x0.z, x1.z, x2.z, x3.z);
|
|
|
|
|
float4 cpx = float4(p0.x, p1.x, p2.x, p3.x);
|
|
|
|
|
float4 cpy = float4(p0.y, p1.y, p2.y, p3.y);
|
|
|
|
|
float4 cpz = float4(p0.z, p1.z, p2.z, p3.z);
|
|
|
|
|
|
|
|
|
|
float hxx = 105.0 * (dot(wide * cxx, cxx) - dot(narrow, 2.0 * cpx * cxx + px));
|
|
|
|
|
float hyy = 105.0 * (dot(wide * cxy, cxy) - dot(narrow, 2.0 * cpy * cxy + px));
|
|
|
|
|
float hzz = 105.0 * (dot(wide * cxz, cxz) - dot(narrow, 2.0 * cpz * cxz + px));
|
|
|
|
|
float hxy = 105.0 * (dot(wide * cxx, cxy) - dot(narrow, cpx * cxy + cpy * cxx));
|
|
|
|
|
float hxz = 105.0 * (dot(wide * cxx, cxz) - dot(narrow, cpx * cxz + cpz * cxx));
|
|
|
|
|
float hyz = 105.0 * (dot(wide * cxy, cxz) - dot(narrow, cpy * cxz + cpz * cxy));
|
|
|
|
|
return float3x3(hxx, hxy, hxz, hxy, hyy, hyz, hxz, hyz, hzz);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The integral over [t0, t1] of clamp(a + b t, lo, hi); see the C# math.integral_clamp_linear comment.
|
|
|
|
|
float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float t0, float t1)
|
|
|
|
|
{
|
2026-10-14 20:42:04 +02:00
|
|
|
lo = min(lo, hi);
|
2026-10-14 06:31:43 +02:00
|
|
|
if (b == 0.0)
|
2026-10-16 01:01:54 +02:00
|
|
|
return t1 != t0 ? clamp(a, lo, hi) * (t1 - t0) : 0.0;
|
2026-10-14 06:31:43 +02:00
|
|
|
|
|
|
|
|
float direction = t1 < t0 ? -1.0 : 1.0;
|
|
|
|
|
float start = min(t0, t1);
|
|
|
|
|
float stop = max(t0, t1);
|
|
|
|
|
float atLo = (lo - a) / b;
|
|
|
|
|
float atHi = (hi - a) / b;
|
|
|
|
|
float enter = clamp(min(atLo, atHi), start, stop);
|
|
|
|
|
float leave = clamp(max(atLo, atHi), start, stop);
|
|
|
|
|
float before = b > 0.0 ? lo : hi;
|
|
|
|
|
float after = b > 0.0 ? hi : lo;
|
2026-10-14 20:39:27 +02:00
|
|
|
float held = enter > start ? before * (enter - start) : 0.0;
|
|
|
|
|
float kept = stop > leave ? after * (stop - leave) : 0.0;
|
|
|
|
|
float total = held + (a + b * (0.5 * (enter + leave))) * (leave - enter) + kept;
|
2026-10-14 06:31:43 +02:00
|
|
|
return direction * total;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-26 02:36:59 +02:00
|
|
|
// Octave sum of cultmath_snoise_grad (invariant 8); see the C# math.fbm_grad comment for the
|
|
|
|
|
// per-octave frequency/amplitude derivation and why octaves is clamped.
|
|
|
|
|
float4 cultmath_fbm_grad(float3 p, int octaves, float lacunarity, float gain)
|
|
|
|
|
{
|
|
|
|
|
octaves = clamp(octaves, 0, 16);
|
|
|
|
|
float amplitude = 1.0;
|
|
|
|
|
float frequency = 1.0;
|
|
|
|
|
float value = 0.0;
|
|
|
|
|
float3 gradient = float3(0.0, 0.0, 0.0);
|
|
|
|
|
for (int i = 0; i < octaves; i++)
|
|
|
|
|
{
|
|
|
|
|
float4 n = cultmath_snoise_grad(p * frequency);
|
|
|
|
|
value += amplitude * n.w;
|
|
|
|
|
gradient += amplitude * frequency * n.xyz;
|
|
|
|
|
frequency *= lacunarity;
|
|
|
|
|
amplitude *= gain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return float4(gradient, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ridged multifractal noise (Musgrave, Texturing and Modeling, ch. 16); see the C# math.ridged_grad
|
|
|
|
|
// comment for the fold/sign derivation, the deliberate crease at n = 0, and why octaves is clamped.
|
|
|
|
|
float4 cultmath_ridged_grad(float3 p, int octaves, float lacunarity, float gain)
|
|
|
|
|
{
|
|
|
|
|
octaves = clamp(octaves, 0, 16);
|
|
|
|
|
float amplitude = 1.0;
|
|
|
|
|
float frequency = 1.0;
|
|
|
|
|
float value = 0.0;
|
|
|
|
|
float3 gradient = float3(0.0, 0.0, 0.0);
|
|
|
|
|
for (int i = 0; i < octaves; i++)
|
|
|
|
|
{
|
|
|
|
|
float4 n = cultmath_snoise_grad(p * frequency);
|
|
|
|
|
float s = sign(n.w);
|
|
|
|
|
value += amplitude * (1.0 - abs(n.w));
|
|
|
|
|
gradient += -amplitude * frequency * s * n.xyz;
|
|
|
|
|
frequency *= lacunarity;
|
|
|
|
|
amplitude *= gain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return float4(gradient, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 21:22:43 +01:00
|
|
|
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); }
|
|
|
|
|
|
|
|
|
|
float cultmath_distance(float2 left, float2 right) { return length(left - right); }
|
|
|
|
|
float cultmath_distance(float3 left, float3 right) { return length(left - right); }
|
|
|
|
|
float cultmath_distance(float4 left, float4 right) { return length(left - right); }
|
|
|
|
|
|
|
|
|
|
float2 cultmath_reflect(float2 incident, float2 normal_value)
|
|
|
|
|
{
|
|
|
|
|
return incident - 2.0 * dot(normal_value, incident) * normal_value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float3 cultmath_reflect(float3 incident, float3 normal_value)
|
|
|
|
|
{
|
|
|
|
|
return incident - 2.0 * dot(normal_value, incident) * normal_value;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 19:33:59 +01:00
|
|
|
float2 cultmath_rotate(float2 value, float radians_value)
|
|
|
|
|
{
|
|
|
|
|
float s = sin(radians_value);
|
|
|
|
|
float c = cos(radians_value);
|
|
|
|
|
return float2(value.x * c - value.y * s, value.x * s + value.y * c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float2 cultmath_rotate_degrees(float2 value, float degrees_value)
|
|
|
|
|
{
|
|
|
|
|
return cultmath_rotate(value, cultmath_radians(degrees_value));
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 21:22:43 +01:00
|
|
|
float cultmath_csum(float2 value) { return value.x + value.y; }
|
|
|
|
|
float cultmath_csum(float3 value) { return value.x + value.y + value.z; }
|
|
|
|
|
float cultmath_csum(float4 value) { return value.x + value.y + value.z + value.w; }
|
|
|
|
|
|
|
|
|
|
float cultmath_decay(float source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); }
|
|
|
|
|
float2 cultmath_decay(float2 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); }
|
|
|
|
|
float3 cultmath_decay(float3 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); }
|
|
|
|
|
float4 cultmath_decay(float4 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); }
|
|
|
|
|
|
|
|
|
|
float cultmath_damp(float start, float end, float lambda_value, float dt)
|
|
|
|
|
{
|
|
|
|
|
return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float2 cultmath_damp(float2 start, float2 end, float lambda_value, float dt)
|
|
|
|
|
{
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
float amount = 1.0 - exp(-lambda_value * dt);
|
|
|
|
|
return cultmath_lerp(start, end, float2(amount, amount));
|
2026-06-10 21:22:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float3 cultmath_damp(float3 start, float3 end, float lambda_value, float dt)
|
|
|
|
|
{
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
float amount = 1.0 - exp(-lambda_value * dt);
|
|
|
|
|
return cultmath_lerp(start, end, float3(amount, amount, amount));
|
2026-06-10 21:22:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 cultmath_damp(float4 start, float4 end, float lambda_value, float dt)
|
|
|
|
|
{
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
float amount = 1.0 - exp(-lambda_value * dt);
|
|
|
|
|
return cultmath_lerp(start, end, float4(amount, amount, amount, amount));
|
2026-06-10 21:22:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_catmullrom(float p0, float p1, float p2, float p3, float t)
|
|
|
|
|
{
|
|
|
|
|
float t2 = t * t;
|
|
|
|
|
float t3 = t2 * t;
|
|
|
|
|
return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float2 cultmath_catmullrom(float2 p0, float2 p1, float2 p2, float2 p3, float t)
|
|
|
|
|
{
|
|
|
|
|
float t2 = t * t;
|
|
|
|
|
float t3 = t2 * t;
|
|
|
|
|
return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float3 cultmath_catmullrom(float3 p0, float3 p1, float3 p2, float3 p3, float t)
|
|
|
|
|
{
|
|
|
|
|
float t2 = t * t;
|
|
|
|
|
float t3 = t2 * t;
|
|
|
|
|
return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 cultmath_catmullrom(float4 p0, float4 p1, float4 p2, float4 p3, float t)
|
|
|
|
|
{
|
|
|
|
|
float t2 = t * t;
|
|
|
|
|
float t3 = t2 * t;
|
|
|
|
|
return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_quadratic_bezier(float p0, float p1, float p2, float t)
|
|
|
|
|
{
|
|
|
|
|
t = cultmath_saturate(t);
|
|
|
|
|
float inv = 1.0 - t;
|
|
|
|
|
return inv * inv * p0 + 2.0 * inv * t * p1 + t * t * p2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float3 cultmath_quadratic_bezier(float3 p0, float3 p1, float3 p2, float t)
|
|
|
|
|
{
|
|
|
|
|
t = cultmath_saturate(t);
|
|
|
|
|
float inv = 1.0 - t;
|
|
|
|
|
return inv * inv * p0 + 2.0 * inv * t * p1 + t * t * p2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_cubic_bezier(float p0, float p1, float p2, float p3, float t)
|
|
|
|
|
{
|
|
|
|
|
t = cultmath_saturate(t);
|
|
|
|
|
float inv = 1.0 - t;
|
|
|
|
|
return inv * inv * inv * p0 + 3.0 * inv * inv * t * p1 + 3.0 * inv * t * t * p2 + t * t * t * p3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float3 cultmath_cubic_bezier(float3 p0, float3 p1, float3 p2, float3 p3, float t)
|
|
|
|
|
{
|
|
|
|
|
t = cultmath_saturate(t);
|
|
|
|
|
float inv = 1.0 - t;
|
|
|
|
|
return inv * inv * inv * p0 + 3.0 * inv * inv * t * p1 + 3.0 * inv * t * t * p2 + t * t * t * p3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_hash(float value) { return cultmath_frac(sin(value) * 43758.5453); }
|
|
|
|
|
float cultmath_hash(float2 value) { return cultmath_hash(dot(value, float2(127.1, 311.7))); }
|
|
|
|
|
float cultmath_hash(float3 value) { return cultmath_hash(dot(value, float3(127.1, 311.7, 74.7))); }
|
|
|
|
|
|
Add PCG integer hashes to CultMath and release 0.2.3
CultMath's hash is the shader-style frac(sin(x) * 43758.5453) and
returns float. Unity.Mathematics' hash returned a well-mixed uint, and
seeding from the float hash collapses: a 100x100 position grid gave 1625
distinct values in Aetheria's zone generator.
- math.pcg(uint), pcg3d(int3/float2/float3), and pcg4d(int4/float4)
follow Jarzynski and Olano, "Hash Functions for GPU Rendering"
(JCGT 9(3), 2020). pcg3d and pcg4d are the paper's code from page 32.
pcg is O'Neill's RXS-M-XS 32/32 output over one LCG step, with the
constants checked against pcg-c's pcg_variants.h.
- CultMath has no uint vectors, so pcg3d and pcg4d carry uint bit
patterns in int3/int4. The float overloads hash IEEE-754 bits via the
HLSL intrinsic asuint(float), which is new. pcg3d(float2) holds z at
0, the paper's route for 2 -> N inputs.
- The HLSL mirror gains the same functions, written over scalar uints
so the text compiles both in dxc 1.9.0.5402 (lib_6_3, HV 2021, clean)
and in C#. HlslSourceCompatibilityTests now compares int and uint
arguments and results by value; a wrong shift in the mirror's pcg4d
gives 208 mismatches.
- Known-answer vectors come from an independent Python transcription.
pcg3d separates a 100x100 half-unit grid (the full int3). One
component alone has one birthday collision there, as a 32-bit value
can. The same grid on the integer lattice has none.
- design.md states that the hashes are integer-exact in HLSL and on the
CPU: dxc wraps uint multiply and add modulo 2^32, as C# unchecked does.
- Bump to 0.2.3 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:19:53 +02:00
|
|
|
// PCG hashes (Jarzynski and Olano, JCGT 2020), written over scalar uints so CultMath's int3/int4 carry the bits.
|
|
|
|
|
uint cultmath_pcg(uint value)
|
|
|
|
|
{
|
|
|
|
|
uint state = value * 747796405u + 2891336453u;
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
uint word = ((state >> (int)((state >> 28) + 4u)) ^ state) * 277803737u;
|
Add PCG integer hashes to CultMath and release 0.2.3
CultMath's hash is the shader-style frac(sin(x) * 43758.5453) and
returns float. Unity.Mathematics' hash returned a well-mixed uint, and
seeding from the float hash collapses: a 100x100 position grid gave 1625
distinct values in Aetheria's zone generator.
- math.pcg(uint), pcg3d(int3/float2/float3), and pcg4d(int4/float4)
follow Jarzynski and Olano, "Hash Functions for GPU Rendering"
(JCGT 9(3), 2020). pcg3d and pcg4d are the paper's code from page 32.
pcg is O'Neill's RXS-M-XS 32/32 output over one LCG step, with the
constants checked against pcg-c's pcg_variants.h.
- CultMath has no uint vectors, so pcg3d and pcg4d carry uint bit
patterns in int3/int4. The float overloads hash IEEE-754 bits via the
HLSL intrinsic asuint(float), which is new. pcg3d(float2) holds z at
0, the paper's route for 2 -> N inputs.
- The HLSL mirror gains the same functions, written over scalar uints
so the text compiles both in dxc 1.9.0.5402 (lib_6_3, HV 2021, clean)
and in C#. HlslSourceCompatibilityTests now compares int and uint
arguments and results by value; a wrong shift in the mirror's pcg4d
gives 208 mismatches.
- Known-answer vectors come from an independent Python transcription.
pcg3d separates a 100x100 half-unit grid (the full int3). One
component alone has one birthday collision there, as a 32-bit value
can. The same grid on the integer lattice has none.
- design.md states that the hashes are integer-exact in HLSL and on the
CPU: dxc wraps uint multiply and add modulo 2^32, as C# unchecked does.
- Bump to 0.2.3 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:19:53 +02:00
|
|
|
return (word >> 22) ^ word;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int3 cultmath_pcg3d(int3 value)
|
|
|
|
|
{
|
|
|
|
|
uint x = (uint)value.x * 1664525u + 1013904223u, y = (uint)value.y * 1664525u + 1013904223u, z = (uint)value.z * 1664525u + 1013904223u;
|
|
|
|
|
x += y * z; y += z * x; z += x * y;
|
|
|
|
|
x ^= x >> 16; y ^= y >> 16; z ^= z >> 16;
|
|
|
|
|
x += y * z; y += z * x; z += x * y;
|
|
|
|
|
return int3((int)x, (int)y, (int)z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int3 cultmath_pcg3d(float3 value) { return cultmath_pcg3d(int3((int)asuint(value.x), (int)asuint(value.y), (int)asuint(value.z))); }
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
int3 cultmath_pcg3d(float2 value) { return cultmath_pcg3d(float3(value, 0.0)); }
|
Add PCG integer hashes to CultMath and release 0.2.3
CultMath's hash is the shader-style frac(sin(x) * 43758.5453) and
returns float. Unity.Mathematics' hash returned a well-mixed uint, and
seeding from the float hash collapses: a 100x100 position grid gave 1625
distinct values in Aetheria's zone generator.
- math.pcg(uint), pcg3d(int3/float2/float3), and pcg4d(int4/float4)
follow Jarzynski and Olano, "Hash Functions for GPU Rendering"
(JCGT 9(3), 2020). pcg3d and pcg4d are the paper's code from page 32.
pcg is O'Neill's RXS-M-XS 32/32 output over one LCG step, with the
constants checked against pcg-c's pcg_variants.h.
- CultMath has no uint vectors, so pcg3d and pcg4d carry uint bit
patterns in int3/int4. The float overloads hash IEEE-754 bits via the
HLSL intrinsic asuint(float), which is new. pcg3d(float2) holds z at
0, the paper's route for 2 -> N inputs.
- The HLSL mirror gains the same functions, written over scalar uints
so the text compiles both in dxc 1.9.0.5402 (lib_6_3, HV 2021, clean)
and in C#. HlslSourceCompatibilityTests now compares int and uint
arguments and results by value; a wrong shift in the mirror's pcg4d
gives 208 mismatches.
- Known-answer vectors come from an independent Python transcription.
pcg3d separates a 100x100 half-unit grid (the full int3). One
component alone has one birthday collision there, as a 32-bit value
can. The same grid on the integer lattice has none.
- design.md states that the hashes are integer-exact in HLSL and on the
CPU: dxc wraps uint multiply and add modulo 2^32, as C# unchecked does.
- Bump to 0.2.3 and rebuild the tracked Unity DLL and pdb.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:19:53 +02:00
|
|
|
|
|
|
|
|
int4 cultmath_pcg4d(int4 value)
|
|
|
|
|
{
|
|
|
|
|
uint x = (uint)value.x * 1664525u + 1013904223u, y = (uint)value.y * 1664525u + 1013904223u;
|
|
|
|
|
uint z = (uint)value.z * 1664525u + 1013904223u, w = (uint)value.w * 1664525u + 1013904223u;
|
|
|
|
|
x += y * w; y += z * x; z += x * y; w += y * z;
|
|
|
|
|
x ^= x >> 16; y ^= y >> 16; z ^= z >> 16; w ^= w >> 16;
|
|
|
|
|
x += y * w; y += z * x; z += x * y; w += y * z;
|
|
|
|
|
return int4((int)x, (int)y, (int)z, (int)w);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int4 cultmath_pcg4d(float4 value) { return cultmath_pcg4d(int4((int)asuint(value.x), (int)asuint(value.y), (int)asuint(value.z), (int)asuint(value.w))); }
|
|
|
|
|
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
// Inigo Quilez, "smooth minimum": quadratic-polynomial smin carried to value-and-gradient form. See
|
2026-09-25 19:18:01 +02:00
|
|
|
// math.smin_grad's comment (math.cs) for why the gradient blend is exact, not an approximation.
|
|
|
|
|
float4 cultmath_smin_grad(float4 a, float4 b, float k)
|
|
|
|
|
{
|
|
|
|
|
float h = saturate(0.5 + 0.5 * (b.w - a.w) / k);
|
|
|
|
|
float value = lerp(b.w, a.w, h) - k * h * (1.0 - h);
|
|
|
|
|
float3 gradient = lerp(b.xyz, a.xyz, h);
|
|
|
|
|
return float4(gradient, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Invariant 8's one struct return shape (design.md, "HLSL Target: Source Transformations"):
|
|
|
|
|
// compared field by field and bit for bit against CultMath.CultCellular, same field order.
|
|
|
|
|
struct CultCellular
|
|
|
|
|
{
|
|
|
|
|
float4 nearest;
|
|
|
|
|
float4 edge;
|
|
|
|
|
float id;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Worley, "A Cellular Texture Basis Function" (SIGGRAPH 1996). See math.cellular's comment
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
// (math.cs) for the hashing, gradient, search-radius exactness proof, id source and
|
|
|
|
|
// degenerate-point conventions this mirrors exactly, including the 5x5x5 lower-bound prune.
|
2026-09-25 19:18:01 +02:00
|
|
|
CultCellular cultmath_cellular(float3 p)
|
|
|
|
|
{
|
|
|
|
|
float3 cell = floor(p);
|
|
|
|
|
float f1 = 1.0e30;
|
|
|
|
|
float f2 = 1.0e30;
|
|
|
|
|
float3 c1 = float3(0.0, 0.0, 0.0);
|
|
|
|
|
float3 c2 = float3(0.0, 0.0, 0.0);
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
float3 cellId = float3(0.0, 0.0, 0.0);
|
2026-09-25 19:18:01 +02:00
|
|
|
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
for (int dz = -2; dz <= 2; dz++)
|
2026-09-25 19:18:01 +02:00
|
|
|
{
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
float lz = max(0.0, abs((float)dz) - 1.0);
|
|
|
|
|
for (int dy = -2; dy <= 2; dy++)
|
2026-09-25 19:18:01 +02:00
|
|
|
{
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
float ly = max(0.0, abs((float)dy) - 1.0);
|
|
|
|
|
for (int dx = -2; dx <= 2; dx++)
|
2026-09-25 19:18:01 +02:00
|
|
|
{
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
float lx = max(0.0, abs((float)dx) - 1.0);
|
|
|
|
|
float lowerBound = sqrt(lx * lx + ly * ly + lz * lz);
|
|
|
|
|
if (lowerBound >= f2)
|
|
|
|
|
continue;
|
|
|
|
|
|
2026-09-25 19:18:01 +02:00
|
|
|
float3 neighbor = cell + float3(dx, dy, dz);
|
cellular: hash the integer cell coordinate, correct the F6 near-side proof
Soul (cut 2a-i, pass 2) found two defects in cellular's own math.cs source
and its HLSL mirror:
- cellular hashed pcg3d/pcg4d over the FLOAT32 BIT PATTERN of each
integer-valued cell coordinate (pcg3d(neighbor), pcg4d(float4(cellId, 0)))
instead of the integer coordinate itself. Two adjacent cells' bit patterns
share more structure (same exponent, mantissa one increment apart) than
the integers do going into pcg3d's own mixing, and that structure survived
into the jitter: measured chi-square 203.8 for jitter.x over 1,000,000
cells against 15 degrees of freedom (critical value 37.7). Both call sites
now hash int3(neighbor)/int4(int3(cellId), 0) directly, in C# and in the
HLSL mirror (both shaders/CultMath.hlsl and the Unity copy, byte-identical
as before). This changes cellular's noise output — nothing has shipped, so
that is acceptable, but it is a real behavioural change, not a comment fix.
- The F6 exactness proof's "near-side neighbour" bound was wrong as written:
picking the near-side neighbour on a FIXED axis is not always within
sqrt(3) of the query (counterexample: u = (0.5, 0, 0) with the x-neighbour
jittered to (-1, 1-2^-24, 1-2^-24) gives ~2.06, past the radius-2
exclusion). The comment is rewritten to the correct argument: choose the
axis whose u_i sits closest to a cell boundary (m = min(u_i, 1-u_i) <=
1/2 on that axis), and the near-side neighbour on THAT axis has squared
distance at most (1+m)^2 + 2*(1-m)^2 = 3 - 2m + 3m^2 <= 3 on [0, 1/2],
strictly below 3 since the jitter never reaches the boundary. Margin
2 - sqrt(3) ~= 0.268 (Soul's adversarial check: worst in-box F2 is exactly
sqrt(3), only in the unattainable corner limit).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 23:13:09 +02:00
|
|
|
int3 hash = cultmath_pcg3d(int3(neighbor));
|
2026-09-25 19:18:01 +02:00
|
|
|
float3 jitter = float3(
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
(float)((uint)hash.x >> 8) * (1.0 / 16777216.0),
|
|
|
|
|
(float)((uint)hash.y >> 8) * (1.0 / 16777216.0),
|
|
|
|
|
(float)((uint)hash.z >> 8) * (1.0 / 16777216.0));
|
2026-09-25 19:18:01 +02:00
|
|
|
float3 feature = neighbor + jitter;
|
|
|
|
|
float d = cultmath_distance(p, feature);
|
|
|
|
|
|
|
|
|
|
if (d < f1)
|
|
|
|
|
{
|
|
|
|
|
f2 = f1; c2 = c1;
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
f1 = d; c1 = feature; cellId = neighbor;
|
2026-09-25 19:18:01 +02:00
|
|
|
}
|
|
|
|
|
else if (d < f2)
|
|
|
|
|
{
|
|
|
|
|
f2 = d; c2 = feature;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
// F1 = 0 and F2 = 0 share the same degenerate-gradient guard; see math.cellular's comment
|
|
|
|
|
// (math.cs) for why F2 = 0 is reachable past roughly |p| = 2^23.
|
2026-09-25 19:18:01 +02:00
|
|
|
float3 grad1 = f1 > 0.0 ? (p - c1) / f1 : float3(0.0, 0.0, 0.0);
|
Fix batch 1 (cellular/smin_grad): exact search, decorrelated id, finite F2=0
Cut 2a-i fix batch 1, findings F1, F3, F4, F5, F6, F7 (Soul falsification of
the first landing):
- cellular now searches a provably exact 5x5x5 neighbourhood (radius 2), not
3x3x3: 3x3x3 missed the true F2 for 9 of 400,000 sampled points. The
comment in math.cs proves 5x5x5 always contains the true F1 and F2 (both
are always < sqrt(3), every excluded cell is always > 2). A cheap
position-only lower-bound prune skips the pcg3d/jitter/distance work for
cells that provably cannot beat the running F2, without changing any
output bit (F6).
- F2 = 0 now gets the same degenerate-gradient guard F1 = 0 already had,
fixing a NaN that was reachable past |p| of about 2^23 (F3).
- id now comes from pcg4d over the winning cell alone, never from the pcg3d
hash that produced that cell's jitter, so id and jitter are no longer
deterministic functions of each other (F4).
- cellular_unit maps the top 24 bits instead of a plain divide-by-2^32, so
the mapping is always inside [0, 1); the old formula rounded
0xFFFFFF80..0xFFFFFFFF up to exactly 1.0f (F7).
- Central-difference tolerances tightened from 0.02 to 3e-4 (measured float
noise at the committed eps is about 6e-5), and cellular's own gradient
test now differentiates cellular's RETURNED .w values instead of a
duplicate FindF1F2 search, so a mutant that corrupts only the returned
gradient can't hide behind an independently-computed value (F1, F5).
- The struct-mirror comparison in HlslSourceCompatibilityTests now checks
field name and declared type in declaration order, not just position, so
a reordered-and-relabelled mirror struct (H2) or a renamed/retyped one
(H3) both fail the walk instead of surviving on values alone (F2).
- FindF1F2 widened to a true independent 7x7x7 brute-force oracle; new tests
pin production's exact agreement with it, the pruned search's bit-for-bit
equivalence to an unpruned 5x5x5 reference, no NaN at 2^24, and id's
correlation with jitter under 0.05.
- Comment/doc fix: smin_grad is Quilez's quadratic-polynomial smin, not
cubic.
Both shaders/CultMath.hlsl and unity/.../Shaders/CultMath.hlsl stay
byte-identical.
Manually verified mutants M9 (grad1 x 1.01), M16 (grad2 x 1.01), and M18 (id
from hash.y) all die against these tests; H2/H3 struct mutants die via the
new comparator tests. Local dotnet test: 191/191 passing. FXC cs_5_0 compile
of a scratch kernel using both cultmath_smin_grad and cultmath_cellular
succeeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 20:40:28 +02:00
|
|
|
float3 grad2 = f2 > 0.0 ? (p - c2) / f2 : float3(0.0, 0.0, 0.0);
|
|
|
|
|
|
cellular: hash the integer cell coordinate, correct the F6 near-side proof
Soul (cut 2a-i, pass 2) found two defects in cellular's own math.cs source
and its HLSL mirror:
- cellular hashed pcg3d/pcg4d over the FLOAT32 BIT PATTERN of each
integer-valued cell coordinate (pcg3d(neighbor), pcg4d(float4(cellId, 0)))
instead of the integer coordinate itself. Two adjacent cells' bit patterns
share more structure (same exponent, mantissa one increment apart) than
the integers do going into pcg3d's own mixing, and that structure survived
into the jitter: measured chi-square 203.8 for jitter.x over 1,000,000
cells against 15 degrees of freedom (critical value 37.7). Both call sites
now hash int3(neighbor)/int4(int3(cellId), 0) directly, in C# and in the
HLSL mirror (both shaders/CultMath.hlsl and the Unity copy, byte-identical
as before). This changes cellular's noise output — nothing has shipped, so
that is acceptable, but it is a real behavioural change, not a comment fix.
- The F6 exactness proof's "near-side neighbour" bound was wrong as written:
picking the near-side neighbour on a FIXED axis is not always within
sqrt(3) of the query (counterexample: u = (0.5, 0, 0) with the x-neighbour
jittered to (-1, 1-2^-24, 1-2^-24) gives ~2.06, past the radius-2
exclusion). The comment is rewritten to the correct argument: choose the
axis whose u_i sits closest to a cell boundary (m = min(u_i, 1-u_i) <=
1/2 on that axis), and the near-side neighbour on THAT axis has squared
distance at most (1+m)^2 + 2*(1-m)^2 = 3 - 2m + 3m^2 <= 3 on [0, 1/2],
strictly below 3 since the jitter never reaches the boundary. Margin
2 - sqrt(3) ~= 0.268 (Soul's adversarial check: worst in-box F2 is exactly
sqrt(3), only in the unattainable corner limit).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 23:13:09 +02:00
|
|
|
// id comes from pcg4d over the winning cell's integer coordinate alone (same int-hashing rule
|
|
|
|
|
// as the jitter above), never from the pcg3d hash that produced its jitter (F4).
|
|
|
|
|
int4 idHash = cultmath_pcg4d(int4(int3(cellId), 0));
|
2026-09-25 19:18:01 +02:00
|
|
|
|
|
|
|
|
CultCellular result;
|
|
|
|
|
result.nearest = float4(grad1, f1);
|
|
|
|
|
result.edge = float4(grad2 - grad1, f2 - f1);
|
CultMath HLSL: write the shader in the subset GLSL ES 3.00 shares, bit for bit
Each edit is legal HLSL that computes the same bits, and closes one dialect gap the GLSL lowering
would otherwise have to special-case:
- snoise and snoise_grad: step(h, 0.0) -> step(h, float4(0.0, 0.0, 0.0, 0.0)); GLSL has no
step(genType, float).
- pcg: (state >> 28) + 4 -> + 4u; GLSL has no implicit int -> uint.
- cellular and phacelle jitter, cellular id: (float)((uint)hash.x >> 8) before the float
multiply; GLSL has no implicit uint -> float.
- damp (float2/3/4): the scalar amount is splatted explicitly; GLSL has no lerp(vecN, vecN, float)
promotion.
- pcg3d(float3) is defined before pcg3d(float2), which calls it; GLSL needs a declaration before use.
Both tracked copies; the C# mirror stays green, FXC cs_5_0 compiles both copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:09:44 +02:00
|
|
|
result.id = (float)((uint)idHash.w >> 8) * (1.0 / 16777216.0);
|
2026-09-25 19:18:01 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 16:09:18 +02:00
|
|
|
// Sites: a site is float4(position.xyz, weight). A shader function cannot take a span (design.md, "Sites"), so a span is a
|
|
|
|
|
// fixed-capacity array and a count (cultmath_power_border, below), or a loop over a per-site step:
|
2026-10-16 18:17:40 +02:00
|
|
|
// CultSiteCells cells = cultmath_site_cells_empty();
|
|
|
|
|
// for (int i = 0; i < count; i++)
|
|
|
|
|
// cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i);
|
2026-10-17 16:09:18 +02:00
|
|
|
// The exact power cell border is cultmath_power_border, the second loop over the sites that skips the owner cells.nearestSite.
|
2026-10-16 21:01:16 +02:00
|
|
|
// The step trusts the owner: a p outside the owner's cell reports distance 0 with that plane's normal, not an error.
|
2026-10-16 18:17:40 +02:00
|
|
|
// Statement for statement with math.Sites.cs (power_score, multiplicative_score, site_cells_*, gabriel_margin).
|
|
|
|
|
struct CultSiteCells
|
|
|
|
|
{
|
|
|
|
|
float4 nearest;
|
|
|
|
|
float4 second;
|
|
|
|
|
float nearestSite;
|
|
|
|
|
float secondSite;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
float4 cultmath_power_score(float3 p, float4 site)
|
|
|
|
|
{
|
|
|
|
|
float3 offset = p - site.xyz;
|
|
|
|
|
return float4(offset * 2.0, dot(offset, offset) - site.w * site.w);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 cultmath_multiplicative_score(float3 p, float4 site)
|
|
|
|
|
{
|
|
|
|
|
float3 offset = p - site.xyz;
|
|
|
|
|
float r = length(offset);
|
|
|
|
|
float3 gradient = r > 0.0 ? offset / (r * site.w) : float3(0.0, 0.0, 0.0);
|
|
|
|
|
return float4(gradient, r / site.w);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CultSiteCells cultmath_site_cells_empty()
|
|
|
|
|
{
|
|
|
|
|
CultSiteCells result;
|
|
|
|
|
result.nearest = float4(0.0, 0.0, 0.0, 1.0e30);
|
|
|
|
|
result.second = float4(0.0, 0.0, 0.0, 1.0e30);
|
|
|
|
|
result.nearestSite = -1.0;
|
|
|
|
|
result.secondSite = -1.0;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CultSiteCells cultmath_site_cells_insert(CultSiteCells cells, float4 score, int index)
|
|
|
|
|
{
|
|
|
|
|
CultSiteCells result = cells;
|
|
|
|
|
if (score.w < cells.nearest.w)
|
|
|
|
|
{
|
|
|
|
|
result.second = cells.nearest;
|
|
|
|
|
result.secondSite = cells.nearestSite;
|
|
|
|
|
result.nearest = score;
|
|
|
|
|
result.nearestSite = (float)index;
|
|
|
|
|
}
|
|
|
|
|
else if (score.w < cells.second.w)
|
|
|
|
|
{
|
|
|
|
|
result.second = score;
|
|
|
|
|
result.secondSite = (float)index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 cultmath_site_cells_border(CultSiteCells cells)
|
|
|
|
|
{
|
|
|
|
|
float3 n = cells.second.xyz - cells.nearest.xyz;
|
|
|
|
|
float len = length(n);
|
|
|
|
|
if (cells.secondSite < 0.0 || len <= 0.0)
|
|
|
|
|
return float4(0.0, 0.0, 0.0, 1.0e30);
|
|
|
|
|
return float4(n / len, (cells.second.w - cells.nearest.w) / len);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-16 19:15:55 +02:00
|
|
|
float4 cultmath_power_border_step(float3 p, float4 owner, float4 other, float4 border)
|
|
|
|
|
{
|
|
|
|
|
float3 d = other.xyz - owner.xyz;
|
|
|
|
|
float len = length(d);
|
|
|
|
|
if (len <= 0.0)
|
|
|
|
|
return border;
|
|
|
|
|
float plane = max((dot(owner.xyz - p, d) + 0.5 * (dot(d, d) + (owner.w - other.w) * (owner.w + other.w))) / len, 0.0);
|
|
|
|
|
return plane < border.w ? float4(-d / len, plane) : border;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-16 18:17:40 +02:00
|
|
|
float cultmath_gabriel_margin(float3 a, float3 b, float3 other)
|
|
|
|
|
{
|
|
|
|
|
float r = cultmath_distance(a, b) * 0.5;
|
|
|
|
|
if (r <= 0.0)
|
|
|
|
|
return -1.0;
|
|
|
|
|
return (cultmath_distance(other, (a + b) * 0.5) - r) / r;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 16:09:18 +02:00
|
|
|
// Weighted lattice cells: the four per-cell steps of math.Lattice.cs, statement for statement; cultmath_weighted_lattice_cells,
|
|
|
|
|
// after them, is the whole function.
|
2026-10-17 11:31:46 +02:00
|
|
|
float3 cultmath_lattice_site(float2 cell, float time, float drift)
|
|
|
|
|
{
|
|
|
|
|
int3 h = cultmath_pcg3d(int3((int)cell.x, (int)cell.y, 0));
|
|
|
|
|
float2 phase = float2((float)((uint)h.x >> 8) * (1.0 / 16777216.0), (float)((uint)h.y >> 8) * (1.0 / 16777216.0));
|
|
|
|
|
float2 site = cell + 0.5 + 0.5 * float2(sin(drift * time + CULTMATH_TAU * phase.x), sin(drift * time + CULTMATH_TAU * phase.y));
|
|
|
|
|
return float3(site, 0.5 + 0.5 * ((float)((uint)h.z >> 8) * (1.0 / 16777216.0)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_lattice_weight(float base_weight, float border, float boost, float falloff)
|
|
|
|
|
{
|
|
|
|
|
float t = border / falloff;
|
|
|
|
|
return base_weight * (1.0 + boost * exp(-(t * t)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_lattice_distance(float2 q, float2 site, float weight, float2 normal, float stretch)
|
|
|
|
|
{
|
|
|
|
|
float2 v = site - q;
|
|
|
|
|
float a = dot(v, normal);
|
|
|
|
|
float c = v.x * normal.y - v.y * normal.x;
|
|
|
|
|
return weight * length(float2(stretch * a, c));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_lattice_smooth_step(float m, float d, float f1, float smoothing)
|
|
|
|
|
{
|
|
|
|
|
if (d >= f1 + 2.0 * smoothing)
|
|
|
|
|
return m;
|
|
|
|
|
float h = cultmath_smoothstep(0.0, 1.0, 0.5 + 0.5 * (m - d) / smoothing);
|
|
|
|
|
float next = m + (d - m) * h - h * (1.0 - h) * smoothing / (1.0 + 3.0 * smoothing);
|
|
|
|
|
return min(next, f1 + smoothing);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-17 16:09:18 +02:00
|
|
|
// Sites by value: a shader function cannot take a span, so these take float4 sites[CULTMATH_MAX_SITES] and the count in use, with
|
|
|
|
|
// count in [0, CULTMATH_MAX_SITES]. A consumer defines CULTMATH_MAX_SITES before including this file to change the capacity; loops
|
2026-10-17 16:50:15 +02:00
|
|
|
// run to count, so the capacity costs uniform space, not iterations. A caller whose sites live in a buffer copies them into a local array first.
|
2026-10-17 16:09:18 +02:00
|
|
|
// The preconditions the C# functions throw for are the caller's in a shader: finite arguments, cellSize > 0, boost in [0, 4],
|
|
|
|
|
// falloff > 0, smoothing in (0, 1], stretch in [1, 2], p within 2^23 cells. A zero stretchNormal means (1, 0) and needs stretch == 1.
|
|
|
|
|
// cultmath_weighted_lattice_cells is math.weighted_lattice_cells: the ring pass is bounded by k <= 37 and exits as C# does, and the
|
|
|
|
|
// box pass recomputes each distance instead of caching it (a 75 x 75 private array is not a shader option), with the same values.
|
|
|
|
|
#ifndef CULTMATH_MAX_SITES
|
|
|
|
|
#define CULTMATH_MAX_SITES 128
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
float4 cultmath_power_border(float3 p, float4 sites[CULTMATH_MAX_SITES], int count)
|
|
|
|
|
{
|
|
|
|
|
CultSiteCells cells = cultmath_site_cells_empty();
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
|
cells = cultmath_site_cells_insert(cells, cultmath_power_score(p, sites[i]), i);
|
|
|
|
|
float4 border = float4(0.0, 0.0, 0.0, 1.0e30);
|
|
|
|
|
int owner = (int)cells.nearestSite;
|
|
|
|
|
if (owner < 0)
|
|
|
|
|
return border;
|
|
|
|
|
for (int k = 0; k < count; k++)
|
|
|
|
|
if (k != owner)
|
|
|
|
|
border = cultmath_power_border_step(p, sites[owner], sites[k], border);
|
|
|
|
|
return border;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_lattice_cell_distance(float2 q, float2 cell, float time, float drift, float4 bodies[CULTMATH_MAX_SITES], int count,
|
|
|
|
|
float cellSize, float boost, float falloff, float2 normal, float stretch)
|
|
|
|
|
{
|
|
|
|
|
float3 site = cultmath_lattice_site(cell, time, drift);
|
|
|
|
|
float border = cultmath_power_border(float3((cell + 0.5) * cellSize, 0.0), bodies, count).w;
|
|
|
|
|
return cultmath_lattice_distance(q, site.xy, cultmath_lattice_weight(site.z, border, boost, falloff), normal, stretch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 cultmath_weighted_lattice_cells(float2 p, float time, float4 bodies[CULTMATH_MAX_SITES], int count, float cellSize, float drift,
|
|
|
|
|
float boost, float falloff, float smoothing, float2 stretchNormal, float stretch)
|
|
|
|
|
{
|
|
|
|
|
float scale = max(abs(stretchNormal.x), abs(stretchNormal.y));
|
|
|
|
|
float2 normal = float2(1.0, 0.0);
|
|
|
|
|
if (scale > 0.0)
|
|
|
|
|
{
|
|
|
|
|
normal = stretchNormal / scale;
|
|
|
|
|
normal = normal / length(normal);
|
|
|
|
|
}
|
|
|
|
|
float2 q = p / cellSize;
|
|
|
|
|
float2 origin = floor(q);
|
|
|
|
|
int cx0 = (int)origin.x;
|
|
|
|
|
int cz0 = (int)origin.y;
|
|
|
|
|
float f1 = 1.0e30;
|
|
|
|
|
float f2 = 1.0e30;
|
|
|
|
|
int reach = 0;
|
|
|
|
|
for (int k = 0; k <= 37; k++)
|
|
|
|
|
{
|
2026-10-17 16:23:43 +02:00
|
|
|
if (k > 1 && 0.5 * (float)(k - 1) >= max(f2, f1 + 2.0 * smoothing))
|
2026-10-17 16:09:18 +02:00
|
|
|
break;
|
|
|
|
|
for (int dz = -k; dz <= k; dz++)
|
|
|
|
|
{
|
|
|
|
|
int step = dz == -k || dz == k || k == 0 ? 1 : 2 * k;
|
|
|
|
|
for (int dx = -k; dx <= k; dx += step)
|
|
|
|
|
{
|
|
|
|
|
float d = cultmath_lattice_cell_distance(q, float2(cx0 + dx, cz0 + dz), time, drift, bodies, count, cellSize, boost, falloff, normal, stretch);
|
|
|
|
|
if (d < f1) { f2 = f1; f1 = d; }
|
|
|
|
|
else if (d < f2) f2 = d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
reach = k;
|
|
|
|
|
}
|
|
|
|
|
float m = f1 + smoothing;
|
|
|
|
|
float best = 1.0e30;
|
|
|
|
|
float second = 1.0e30;
|
|
|
|
|
int winDx = 0;
|
|
|
|
|
int winDz = 0;
|
|
|
|
|
for (int dz = -reach; dz <= reach; dz++)
|
|
|
|
|
for (int dx = -reach; dx <= reach; dx++)
|
|
|
|
|
{
|
|
|
|
|
float d = cultmath_lattice_cell_distance(q, float2(cx0 + dx, cz0 + dz), time, drift, bodies, count, cellSize, boost, falloff, normal, stretch);
|
|
|
|
|
if (d < best) { second = best; best = d; winDx = dx; winDz = dz; }
|
|
|
|
|
else if (d < second) second = d;
|
|
|
|
|
m = cultmath_lattice_smooth_step(m, d, f1, smoothing);
|
|
|
|
|
}
|
|
|
|
|
int4 id = cultmath_pcg4d(int4(cx0 + winDx, cz0 + winDz, 0, 0));
|
|
|
|
|
return float4(best, second, m, (float)((uint)id.w >> 8) * (1.0 / 16777216.0));
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-16 21:01:16 +02:00
|
|
|
float2 cultmath_segment_closest(float2 point_value, float2 start, float2 end)
|
|
|
|
|
{
|
|
|
|
|
float2 segment = end - start;
|
|
|
|
|
float segmentLengthSq = cultmath_lengthsq(segment);
|
|
|
|
|
if (segmentLengthSq <= 0.0)
|
|
|
|
|
return start;
|
|
|
|
|
|
|
|
|
|
float t = cultmath_saturate(dot(point_value - start, segment) / segmentLengthSq);
|
|
|
|
|
return start + segment * t;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-16 18:17:40 +02:00
|
|
|
float3 cultmath_segment_closest(float3 point_value, float3 start, float3 end)
|
|
|
|
|
{
|
|
|
|
|
float3 segment = end - start;
|
|
|
|
|
float segmentLengthSq = cultmath_lengthsq(segment);
|
|
|
|
|
if (segmentLengthSq <= 0.0)
|
|
|
|
|
return start;
|
|
|
|
|
|
|
|
|
|
float t = cultmath_saturate(dot(point_value - start, segment) / segmentLengthSq);
|
|
|
|
|
return start + segment * t;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-16 21:01:16 +02:00
|
|
|
float cultmath_distance_to_segment(float2 point_value, float2 start, float2 end)
|
|
|
|
|
{
|
|
|
|
|
return cultmath_distance(point_value, cultmath_segment_closest(point_value, start, end));
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-16 18:17:40 +02:00
|
|
|
float cultmath_distance_to_segment(float3 point_value, float3 start, float3 end)
|
|
|
|
|
{
|
|
|
|
|
return cultmath_distance(point_value, cultmath_segment_closest(point_value, start, end));
|
|
|
|
|
}
|
|
|
|
|
|
CultMath: Phacelle port moves into MPL-2.0 files; prune cut pinned by boundary points
MPL-2.0 is file-level and the Phacelle port sat inside MIT files. phacelle and
CultPhasor now live in math.Phacelle.cs and CultPhasor.cs, and cultmath_phacelle
in CultMath.Phacelle.hlsl (included by CultMath.hlsl, both tracked copies), each
with the MPL header and Johansen / Luke Mitchell attribution. math stays one
partial class, so math.phacelle is called exactly as before. The GameCult.Math
package declares MIT AND MPL-2.0 and packs THIRD-PARTY-NOTICES.md and the new
include; the Unity LICENSE.md points at CultLib; build-unity-package.ps1
requires the new include.
The random sweeps never met a cell whose box bound sits just under the 2.25 cut
while its weight is still nonzero, so cuts of 2.245, 2.24 and 2.2 survived.
Six hard-coded points (found by an offline search) sit next to such cells; the
C# pruned-vs-unpruned test and the HLSL mirror test both use them, with a
witness check that re-derives the property from the hash.
Docs: the prune keeps about 45 of 64 cells (14 is the nonzero-weight count),
the support radius is 1.49998 (d^2 = 2.24995), normalization's range and
saturation, W = 0 only for adversarial hashes, and the support-edge gradient
seam size.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 00:34:27 +02:00
|
|
|
#include "CultMath.Phacelle.hlsl"
|
CultMath intervals: cultmath_iv_* over float2(lo, hi) in C# and HLSL, with snoise and fbm ball bounds
math.Interval.cs and shaders/CultMath.Interval.hlsl (included from CultMath.hlsl after Phacelle;
both tracked copies) add iv_point, iv_add, iv_sub, iv_neg, iv_mul, iv_scale, iv_abs, iv_min,
iv_max, iv_sqr, iv_sqrt, iv_exp, iv_clamp, iv_saturate, iv_smoothstep, iv_lerp, iv_snoise_ball
and iv_fbm_ball, and SNOISE_LIPSCHITZ = 10.099261 (1.10 x the refined max |snoise_grad| that
NoiseBoundTests.MeasureLipschitz prints). iv_exp and iv_smoothstep widen by one ulp through
asfloat(uint), added beside asuint as the HLSL intrinsic. The mirror test now compares 52
functions; the Unity package and the NuGet content files carry the new include.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:52:33 +02:00
|
|
|
#include "CultMath.Interval.hlsl"
|
CultMath: Cycles node semantics (Math, Map Range, Clamp, Mix, RGB to BW, ramp index) as math.Cycles.cs and CultMath.Cycles.hlsl under Apache-2.0
Ports of Blender v5.2.2's intern/cycles kernel functions, one function per node
operation, with the HLSL mirror, the Unity copy, the lowering table entry for a
separate Apache-2.0 GLSL file, the golden-fixture families, the package licence
expressions and the notices section.
Builds. Does not pass yet: CultMath.Cycles.glsl and the regenerated golden
fixture arrive in a later commit, so CommittedGlslEqualsLowering and
GoldenFixtureMatchesCSharp fail at this commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 03:59:59 +02:00
|
|
|
#include "CultMath.Cycles.hlsl"
|
2026-10-04 09:47:49 +02:00
|
|
|
#include "CultMath.Affine.hlsl"
|
CultMath: phacelle, Johansen's Phacelle noise on 3D cells with an exact gradient
Cut 2a-ii-b. phacelle(float3 p, float3 side, float offset, float normalization)
returns CultPhasor { float4 cos; float4 sin }, each float4(gradient.xyz, value.w).
- 4x4x4 cells, pcg3d-hashed jitter in [-0.5, 0.5), weight max(0, exp(-2 d^2) - 0.01111),
normalization as upstream (lpmitchell/AdvancedTerrainErosion PhacelleNoise). The
caller supplies the stripe wave vector; offset is in cycles.
- The gradient is exact: weight derivatives and the normalization are included, not
upstream's grad(cos) ~ -sin * side.
- Cells whose per-axis lower bound is at least 2.25 have weight exactly 0 and are
skipped. An internal overload switches the prune off so the test can pin bit
equality without a second copy of the loop.
- cultmath_phacelle and CultPhasor mirror it in both tracked CultMath.hlsl copies
(the same blob). The mirror test compares the struct field by field; it now covers
34 functions.
- PhacelleTests: value against an independent double-precision reference, quarter-cycle
phase rotation, gradient against central differences on both sides of the
normalization threshold (excluding the threshold and each cell's weight-support
edge, where the exact gradient is discontinuous), stripes along the flow on the unit
sphere (0.215 against the 0.25 bound), step-difference continuity across cell planes,
and pruned == unpruned bit for bit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:51:02 +02:00
|
|
|
|
2026-10-16 02:58:05 +02:00
|
|
|
// Span stages (invariant 8): the SpanPath, SpanStage and SpanNoise of math.Span.cs, field for field in
|
|
|
|
|
// C#'s order, and cultmath_span_stage, its octave stage, statement for statement so the text is a bit-for-bit
|
|
|
|
|
// mirror. They follow the includes because the bounds read CULTMATH_SNOISE_LIPSCHITZ, _HESSIAN and _THIRD.
|
|
|
|
|
// A shader caller builds a SpanStage itself (SpanStage(1.0, 1.0, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0))
|
|
|
|
|
// is the identity of math.SpanStage.Identity, which has no shader form); see math.span_stage's comments for
|
2026-10-16 04:49:12 +02:00
|
|
|
// the span, the transform, the bounds and the allowances. cultmath_span_rounding and cultmath_span_slack
|
|
|
|
|
// are math.span_rounding and math.span_slack, the owners of S_o and of the three allowances.
|
2026-10-16 02:58:05 +02:00
|
|
|
struct SpanPath
|
|
|
|
|
{
|
|
|
|
|
float3 midpoint;
|
|
|
|
|
float3 velocity;
|
|
|
|
|
float speed;
|
|
|
|
|
float acceleration;
|
|
|
|
|
float halfLength;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct SpanStage
|
|
|
|
|
{
|
|
|
|
|
float frequency;
|
|
|
|
|
float stretch;
|
|
|
|
|
float3 axis;
|
|
|
|
|
float3 shift;
|
|
|
|
|
float3 offset;
|
|
|
|
|
float3 octaveStep;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct SpanNoise
|
|
|
|
|
{
|
|
|
|
|
float4 field;
|
|
|
|
|
float curvature;
|
|
|
|
|
float fold;
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-16 04:49:12 +02:00
|
|
|
float cultmath_span_rounding(float scale, float3 mq, SpanStage stage, float3 octaveShift, float nu, float halfLength)
|
|
|
|
|
{
|
|
|
|
|
return 9.5367431640625e-7 * (abs(scale) * (cultmath_csum(abs(mq)) + cultmath_csum(abs(stage.shift))) + cultmath_csum(abs(stage.offset)) + cultmath_csum(abs(octaveShift)) + 1.7320508 * nu * halfLength);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float3 cultmath_span_slack(float rounding)
|
|
|
|
|
{
|
2026-10-16 06:34:21 +02:00
|
|
|
return float3(CULTMATH_SNOISE_READ_VALUE + CULTMATH_SNOISE_LIPSCHITZ * rounding,
|
|
|
|
|
CULTMATH_SNOISE_READ_GRADIENT + CULTMATH_SNOISE_HESSIAN * rounding,
|
2026-10-16 04:49:12 +02:00
|
|
|
9.765625e-4 + CULTMATH_SNOISE_THIRD * rounding);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-16 02:58:05 +02:00
|
|
|
// The octave stage behind cultmath_span_fbm and cultmath_span_ridged, as math.span_stage is behind span_fbm
|
|
|
|
|
// and span_ridged. The Frobenius norm of the Hessian is written out in C#'s order (GLSL has no _mRC member
|
|
|
|
|
// access); the C# local named step is octaveShift here (it would shadow GLSL's step()).
|
|
|
|
|
SpanNoise cultmath_span_stage(bool ridged, SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain)
|
|
|
|
|
{
|
|
|
|
|
float3 q = path.midpoint;
|
|
|
|
|
float halfLength = path.halfLength;
|
|
|
|
|
octaves = clamp(octaves, 0, 16);
|
|
|
|
|
bool stretched = dot(stage.axis, stage.axis) > 0.0 && stage.stretch != 1.0;
|
|
|
|
|
float3 direction = stretched ? normalize(stage.axis) : float3(0.0, 0.0, 0.0);
|
|
|
|
|
float squeeze = stretched ? 1.0 / stage.stretch - 1.0 : 0.0;
|
|
|
|
|
float norm = abs(stage.frequency) * (stretched ? max(1.0, 1.0 / stage.stretch) : 1.0);
|
|
|
|
|
float3 mq = stretched ? stage.frequency * (q + squeeze * dot(direction, q) * direction) : stage.frequency * q;
|
|
|
|
|
float3 mv = stretched ? stage.frequency * (path.velocity + squeeze * dot(direction, path.velocity) * direction) : stage.frequency * path.velocity;
|
|
|
|
|
float amplitude = 1.0;
|
|
|
|
|
float scale = 1.0;
|
|
|
|
|
float value = 0.0;
|
|
|
|
|
float3 gradient = float3(0.0, 0.0, 0.0);
|
|
|
|
|
float curvature = 0.0;
|
|
|
|
|
float fold = 0.0;
|
|
|
|
|
for (int i = 0; i < octaves; i++)
|
|
|
|
|
{
|
|
|
|
|
float3 octaveShift = (float)i * stage.octaveStep;
|
|
|
|
|
float3 x = scale * (mq + stage.shift) + (stage.offset + octaveShift);
|
|
|
|
|
float4 n = cultmath_snoise_grad(x);
|
|
|
|
|
float3 g = float3(n.x, n.y, n.z);
|
|
|
|
|
float3 mg = stretched ? stage.frequency * (g + squeeze * dot(direction, g) * direction) : stage.frequency * g;
|
|
|
|
|
if (ridged)
|
|
|
|
|
{
|
|
|
|
|
float s = sign(n.w);
|
|
|
|
|
value += amplitude * (1.0 - abs(n.w));
|
|
|
|
|
gradient += -amplitude * scale * s * mg;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
value += amplitude * n.w;
|
|
|
|
|
gradient += amplitude * scale * mg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float weight = abs(amplitude);
|
|
|
|
|
float factor = abs(scale) * norm;
|
|
|
|
|
float nu = factor * path.speed;
|
2026-10-16 04:49:12 +02:00
|
|
|
float rounding = cultmath_span_rounding(scale, mq, stage, octaveShift, nu, halfLength);
|
|
|
|
|
float3 slack = cultmath_span_slack(rounding);
|
|
|
|
|
float valueSlack = slack.x;
|
|
|
|
|
float gradientSlack = slack.y;
|
|
|
|
|
float hessianSlack = slack.z;
|
2026-10-16 02:58:05 +02:00
|
|
|
float3x3 hm = cultmath_snoise_hessian(x);
|
|
|
|
|
float frobenius = sqrt(hm[0][0] * hm[0][0] + hm[0][1] * hm[0][1] + hm[0][2] * hm[0][2] + hm[1][0] * hm[1][0] + hm[1][1] * hm[1][1] + hm[1][2] * hm[1][2]
|
|
|
|
|
+ hm[2][0] * hm[2][0] + hm[2][1] * hm[2][1] + hm[2][2] * hm[2][2]);
|
|
|
|
|
float hessianBound = min(CULTMATH_SNOISE_HESSIAN, frobenius + hessianSlack + CULTMATH_SNOISE_THIRD * nu * halfLength);
|
|
|
|
|
curvature += weight * factor * factor * hessianBound;
|
|
|
|
|
if (ridged)
|
|
|
|
|
{
|
|
|
|
|
float3 w = scale * mv;
|
|
|
|
|
float reach = hessianBound * nu * halfLength;
|
|
|
|
|
float gl = min(CULTMATH_SNOISE_LIPSCHITZ, length(g) + gradientSlack + reach);
|
|
|
|
|
float travel = (abs(dot(g, w)) + gradientSlack * length(w)) * halfLength + (hessianBound * nu * length(w) + gl * factor * path.acceleration) * (0.5 * halfLength * halfLength) + 2.0 * valueSlack;
|
|
|
|
|
fold += 2.0 * weight * max(travel - abs(n.w), 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scale *= lacunarity;
|
|
|
|
|
amplitude *= gain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SpanNoise result;
|
|
|
|
|
result.field = float4(gradient, value);
|
|
|
|
|
result.curvature = curvature;
|
|
|
|
|
result.fold = fold;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SpanNoise cultmath_span_fbm(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain)
|
|
|
|
|
{
|
|
|
|
|
return cultmath_span_stage(false, path, stage, octaves, lacunarity, gain);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SpanNoise cultmath_span_ridged(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain)
|
|
|
|
|
{
|
|
|
|
|
return cultmath_span_stage(true, path, stage, octaves, lacunarity, gain);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 21:22:43 +01:00
|
|
|
float cultmath_value_noise(float2 position)
|
|
|
|
|
{
|
|
|
|
|
float2 cell = floor(position);
|
|
|
|
|
float2 local = cultmath_frac(position);
|
|
|
|
|
float2 u = local * local * (float2(3.0, 3.0) - float2(2.0, 2.0) * local);
|
|
|
|
|
|
|
|
|
|
float a = cultmath_hash(cell);
|
|
|
|
|
float b = cultmath_hash(cell + float2(1.0, 0.0));
|
|
|
|
|
float c = cultmath_hash(cell + float2(0.0, 1.0));
|
|
|
|
|
float d = cultmath_hash(cell + float2(1.0, 1.0));
|
|
|
|
|
|
|
|
|
|
return cultmath_lerp(cultmath_lerp(a, b, u.x), cultmath_lerp(c, d, u.x), u.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_value_noise_bicubic(float2 position)
|
|
|
|
|
{
|
|
|
|
|
float2 cell = floor(position);
|
|
|
|
|
float2 local = cultmath_frac(position);
|
|
|
|
|
|
|
|
|
|
float y0 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, -1.0)), cultmath_hash(cell + float2(0.0, -1.0)), cultmath_hash(cell + float2(1.0, -1.0)), cultmath_hash(cell + float2(2.0, -1.0)), local.x);
|
|
|
|
|
float y1 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 0.0)), cultmath_hash(cell + float2(0.0, 0.0)), cultmath_hash(cell + float2(1.0, 0.0)), cultmath_hash(cell + float2(2.0, 0.0)), local.x);
|
|
|
|
|
float y2 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 1.0)), cultmath_hash(cell + float2(0.0, 1.0)), cultmath_hash(cell + float2(1.0, 1.0)), cultmath_hash(cell + float2(2.0, 1.0)), local.x);
|
|
|
|
|
float y3 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 2.0)), cultmath_hash(cell + float2(0.0, 2.0)), cultmath_hash(cell + float2(1.0, 2.0)), cultmath_hash(cell + float2(2.0, 2.0)), local.x);
|
|
|
|
|
|
|
|
|
|
return cultmath_catmullrom(y0, y1, y2, y3, local.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_value_noise_texture(Texture2D<float> noise_texture, SamplerState noise_sampler, float2 uv, float scale)
|
|
|
|
|
{
|
|
|
|
|
return noise_texture.SampleLevel(noise_sampler, uv * scale, 0.0).r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float cultmath_value_noise_texture_bicubic(Texture2D<float> noise_texture, SamplerState noise_sampler, float2 uv, float scale, float2 texel_size)
|
|
|
|
|
{
|
|
|
|
|
float2 position = uv * scale;
|
|
|
|
|
float2 cell = floor(position);
|
|
|
|
|
float2 local = cultmath_frac(position);
|
|
|
|
|
float2 base_uv = cell / scale;
|
|
|
|
|
|
|
|
|
|
float row0 = cultmath_catmullrom(
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, -1.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, -1.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, -1.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, -1.0), 0.0).r,
|
|
|
|
|
local.x);
|
|
|
|
|
float row1 = cultmath_catmullrom(
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 0.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 0.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 0.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 0.0), 0.0).r,
|
|
|
|
|
local.x);
|
|
|
|
|
float row2 = cultmath_catmullrom(
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 1.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 1.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 1.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 1.0), 0.0).r,
|
|
|
|
|
local.x);
|
|
|
|
|
float row3 = cultmath_catmullrom(
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 2.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 2.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 2.0), 0.0).r,
|
|
|
|
|
noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 2.0), 0.0).r,
|
|
|
|
|
local.x);
|
|
|
|
|
|
|
|
|
|
return cultmath_catmullrom(row0, row1, row2, row3, local.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|