I am making a game in which I procedurally generate planets using a compute shader which builds a deformed sphere based on noise from a noise function I downloaded. The whole system works fine in the shader. I however wanted to be able to sample the noise function in c# without having to interact with the GPU, so I have attempted to convert the function into something that works in c#, it appears to work at first glance and functions perfectly as simplex noise but it is consistently different in shape to the GPU version. I am not sure if I have some math the wrong way around somewhere or indeed any idea as to what the issue might be.
Here is the code for the function in HLSL
float3 mod289(float3 x)
{
return x - floor(x / 289.0) * 289.0;
}
float4 mod289(float4 x)
{
return x - floor(x / 289.0) * 289.0;
}
float4 permute(float4 x)
{
return mod289((x * 34.0 + 1.0) * x);
}
float4 taylorInvSqrt(float4 r)
{
return 1.79284291400159 - r * 0.85373472095314;
}
float snoise(float3 v)
{
const float2 C = float2(1.0 / 6.0, 1.0 / 3.0);
// First corner
float3 i = floor(v + dot(v, C.yyy));
float3 x0 = (v - i) + dot(i, C.xxx);
// Other corners
float3 g = step(x0.yzx, x0);
float3 l = 1.0 - g;
float3 i1 = min(g, l.zxy);
float3 i2 = max(g, l.zxy);
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
float3 x1 = x0 - i1 + C.xxx;
float3 x2 = x0 - i2 + C.yyy;
float3 x3 = x0 - 0.5;
// Permutations
i = mod289(i); // Avoid truncation effects in permutation
float4 p =
permute(permute(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));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7)
float4 x_ = floor(j / 7.0);
float4 y_ = floor(j - 7.0 * x_); // mod(j,N)
float4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0;
float4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0;
float4 h = 1.0 - abs(x) - abs(y);
float4 b0 = float4(x.xy, y.xy);
float4 b1 = float4(x.zw, y.zw);
//float4 s0 = float4(lessThan(b0, 0.0)) * 2.0 - 1.0;
//float4 s1 = float4(lessThan(b1, 0.0)) * 2.0 - 1.0;
float4 s0 = floor(b0) * 2.0 + 1.0;
float4 s1 = floor(b1) * 2.0 + 1.0;
float4 sh = -step(h, 0.0);
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
float3 g0 = float3(a0.xy, h.x);
float3 g1 = float3(a0.zw, h.y);
float3 g2 = float3(a1.xy, h.z);
float3 g3 = float3(a1.zw, h.w);
// Normalise gradients
float4 norm = taylorInvSqrt(float4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3)));
g0 *= norm.x;
g1 *= norm.y;
g2 *= norm.z;
g3 *= norm.w;
// Mix final noise value
float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
m = m * m;
m = m * m;
float4 px = float4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3));
return 42.0 * dot(m, px);
}
And Here is my attempt to adapt it into something that can be sampled in c#. I am aware that this is far from ideal formatting but I was attempting to do a one to one conversion and was not really paying too much attention to best practices.
using UnityEngine;
using float4 = UnityEngine.Vector4;
using float3 = UnityEngine.Vector3;
using float2 = UnityEngine.Vector2;
public static class NoiseLibrary
{
private static readonly float2 C = new float2(1.0f / 6.0f, 1.0f / 3.0f);
private static float4 floor(float4 input)
{
return new float4(Mathf.Floor(input.x), Mathf.Floor(input.y), Mathf.Floor(input.z), Mathf.Floor(input.w));
}
private static float3 floor(float3 input)
{
return new float3(Mathf.Floor(input.x), Mathf.Floor(input.y), Mathf.Floor(input.z));
}
private static float4 scale(float4 a, float4 b)
{
return new float4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w);
}
private static float3 mod289(float3 x)
{
return x - floor(x / 289.0f) * 289.0f;
}
private static float4 mod289(float4 x)
{
return x - floor(x / 289.0f) * 289.0f;
}
private static float4 permute(float4 x)
{
return mod289(scale((x * 34.0f + float4.one), x));
}
private static float4 taylorInvSqrt(float4 r)
{
return (float4.one * 1.79284291400159f) - r * 0.85373472095314f;
}
private static float dot(float3 a, float3 b)
{
return float3.Dot(a, b);
}
private static float3 xxx(float3 input)
{
return new float3(input.x, input.x, input.x);
}
private static float3 yyy(float3 input)
{
return new float3(input.y, input.y, input.y);
}
private static float3 yzx(float3 input)
{
return new float3(input.y, input.z, input.x);
}
private static float3 zxy(float3 input)
{
return new float3(input.z, input.x, input.y);
}
private static float3 add(float a, float3 b)
{
return float3.one * a + b;
}
private static float3 add(float3 b, float a)
{
return float3.one * a + b;
}
private static float4 add(float a, float4 b)
{
return float4.one * a + b;
}
private static float4 add(float4 b, float a)
{
return float4.one * a + b;
}
private static float3 step(float3 a, float3 x)
{
return new float3(x.x > a.x ? 1 : 0, x.y > a.y ? 1 : 0, x.z > a.z ? 1 : 0);
}
private static float3 min(float3 a, float3 b)
{
return new float3(Mathf.Min(a.x, b.x), Mathf.Min(a.y, b.y), Mathf.Min(a.z, b.z));
}
private static float3 max(float3 a, float3 b)
{
return new float3(Mathf.Max(a.x, b.x), Mathf.Max(a.y, b.y), Mathf.Max(a.z, b.z));
}
private static float4 max(float4 a, float4 b)
{
return new float4(Mathf.Max(a.x, b.x), Mathf.Max(a.y, b.y), Mathf.Max(a.z, b.z), Mathf.Max(a.w, b.w));
}
private static float4 abs(float4 a)
{
return new float4(Mathf.Abs(a.x), Mathf.Abs(a.y), Mathf.Abs(a.z), Mathf.Abs(a.w));
}
private static float4 xzyw(float4 input)
{
return new float4(input.x, input.z, input.y, input.w);
}
private static float4 xxyy(float4 input)
{
return new float4(input.x, input.x, input.y, input.y);
}
private static float4 zzww(float4 input)
{
return new float4(input.z, input.z, input.w, input.w);
}
private static float dot(float4 a, float4 b)
{
return float4.Dot(a, b);
}
private static float4 step(float4 a, float4 x)
{
return new float4(x.x > a.x ? 1 : 0, x.y > a.y ? 1 : 0, x.z > a.z ? 1 : 0, x.w > a.w ? 1 : 0);
}
public static float snoise(float3 v)
{
// First corner
float3 i = floor(add(dot(v, yyy(C)), v));
float3 x0 = add(v - i, dot(i, xxx(C)));
// Other corners
float3 g = step(yzx(x0), x0);
float3 l = float3.one - g;
float3 i1 = min(g, zxy(l));
float3 i2 = max(g, zxy(l));
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
float3 x1 = x0 - i1 + xxx(C);
float3 x2 = x0 - i2 + yyy(C);
float3 x3 = x0 - (float3.one * 0.5f);
// Permutations
i = mod289(i); // Avoid truncation effects in permutation
float4 p =
permute(add(permute(add(permute(add(i.z, new float4(0, i1.z, i2.z, 1.0f))), i.y) + new float4(0, i1.y, i2.y, 1.0f)), i.x) + new float4(0, i1.x, i2.x, 1.0f));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float4 j = p - 49.0f * floor(p / 49.0f); // mod(p,7*7)
float4 x_ = floor(j / 7.0f);
float4 y_ = floor(j - 7.0f * x_); // mod(j,N)
float4 x = add(add(x_ * 2.0f, 0.5f) / 7.0f, -1.0f);
float4 y = add(add(y_ * 2.0f, 0.5f) / 7.0f, -1.0f);
float4 h = float4.one - abs(x) - abs(y);
float4 b0 = new float4(x.x, x.y, y.x, y.y);
float4 b1 = new float4(x.z, x.w, y.z, y.w);
//float4 s0 = float4(lessThan(b0, 0.0)) * 2.0 - 1.0;
//float4 s1 = float4(lessThan(b1, 0.0)) * 2.0 - 1.0;
float4 s0 = floor(b0) * 2.0f + float4.one;
float4 s1 = floor(b1) * 2.0f + float4.one;
float4 sh = -step(h, float4.zero);
float4 a0 = xzyw(b0) + scale(xzyw(s0), xxyy(sh));
float4 a1 = xzyw(b1) + scale(xzyw(s1), zzww(sh));
float3 g0 = new float3(a0.x, a0.y, h.x);
float3 g1 = new float3(a0.z, a0.w, h.y);
float3 g2 = new float3(a1.x, a1.y, h.z);
float3 g3 = new float3(a1.z, a1.w, h.w);
// Normalise gradients
float4 norm = taylorInvSqrt(new float4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3)));
g0 *= norm.x;
g1 *= norm.y;
g2 *= norm.z;
g3 *= norm.w;
// Mix final noise value
float4 m = max((float4.one * 0.6f) - new float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), float4.zero);
m = scale(m, m);
m = scale(m, m);
float4 px = new float4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3));
return 42.0f * dot(m, px);
}
}
At first I thought I may have gotten the different vector types confused in the different functions and I went through to make sure that all of them match. The result is still a perfectly working noise function, but I need it to be the same as the one produced by the gpu.
As an addendum: the reason i redefined the unity vectors to be float2, float3, and float4 was because i was having issues with visual studio's find and replace feature but that should have no bearing on the code or the result.
While I was not able to make this work, if anyone runs into a similar issue This Noise Library on the Unity asset store can be made to perform the same function with minimal modification, and is what I would recommend looking into if anyone has similar needs.