I've been working on writing functions for 5 and 6 dimensional simplex noise, working off FastNoise's 4D function as a base. 2D, 3D and 4D noise all follow a very similar and recognizable pattern which can be easily extended to higher dimensions, however the output isn't looking so great:
Visible diagonal areas (lines) where values are all very similar, as well as diamond shapes where values don't blend together. (Does that count as discontinuous?)
The full 5D Noise function is:
static const FN_DECIMAL F5 = (sqrt(FN_DECIMAL(6)) - 1) / 5;
static const FN_DECIMAL G5 = (6 - sqrt(FN_DECIMAL(6))) / 30;
FN_DECIMAL FastNoise::SingleSimplex(unsigned char offset, FN_DECIMAL x, FN_DECIMAL y, FN_DECIMAL z, FN_DECIMAL w, FN_DECIMAL v) const
{
FN_DECIMAL n0, n1, n2, n3, n4, n5;
FN_DECIMAL t = (x + y + z + w + v) * F5;
int i = FastFloor(x + t);
int j = FastFloor(y + t);
int k = FastFloor(z + t);
int l = FastFloor(w + t);
int h = FastFloor(v + t);
t = (i + j + k + l + h) * G5;
FN_DECIMAL X0 = i - t;
FN_DECIMAL Y0 = j - t;
FN_DECIMAL Z0 = k - t;
FN_DECIMAL W0 = l - t;
FN_DECIMAL V0 = h - t;
FN_DECIMAL x0 = x - X0;
FN_DECIMAL y0 = y - Y0;
FN_DECIMAL z0 = z - Z0;
FN_DECIMAL w0 = w - W0;
FN_DECIMAL v0 = v - V0;
int rankx = 0;
int ranky = 0;
int rankz = 0;
int rankw = 0;
int rankv = 0;
if (x0 > y0) rankx++; else ranky++;
if (x0 > z0) rankx++; else rankz++;
if (x0 > w0) rankx++; else rankw++;
if (x0 > v0) rankx++; else rankv++;
if (y0 > z0) ranky++; else rankz++;
if (y0 > w0) ranky++; else rankw++;
if (y0 > v0) ranky++; else rankv++;
if (z0 > w0) rankz++; else rankw++;
if (z0 > v0) rankz++; else rankv++;
if (w0 > v0) rankw++; else rankv++;
int i1 = rankx >= 4 ? 1 : 0;
int j1 = ranky >= 4 ? 1 : 0;
int k1 = rankz >= 4 ? 1 : 0;
int l1 = rankw >= 4 ? 1 : 0;
int h1 = rankv >= 4 ? 1 : 0;
int i2 = rankx >= 3 ? 1 : 0;
int j2 = ranky >= 3 ? 1 : 0;
int k2 = rankz >= 3 ? 1 : 0;
int l2 = rankw >= 3 ? 1 : 0;
int h2 = rankv >= 3 ? 1 : 0;
int i3 = rankx >= 2 ? 1 : 0;
int j3 = ranky >= 2 ? 1 : 0;
int k3 = rankz >= 2 ? 1 : 0;
int l3 = rankw >= 2 ? 1 : 0;
int h3 = rankv >= 2 ? 1 : 0;
int i4 = rankx >= 1 ? 1 : 0;
int j4 = ranky >= 1 ? 1 : 0;
int k4 = rankz >= 1 ? 1 : 0;
int l4 = rankw >= 1 ? 1 : 0;
int h4 = rankv >= 1 ? 1 : 0;
FN_DECIMAL x1 = x0 - i1 + G5;
FN_DECIMAL y1 = y0 - j1 + G5;
FN_DECIMAL z1 = z0 - k1 + G5;
FN_DECIMAL w1 = w0 - l1 + G5;
FN_DECIMAL v1 = v0 - h1 + G5;
FN_DECIMAL x2 = x0 - i2 + 2*G5;
FN_DECIMAL y2 = y0 - j2 + 2*G5;
FN_DECIMAL z2 = z0 - k2 + 2*G5;
FN_DECIMAL w2 = w0 - l2 + 2*G5;
FN_DECIMAL v2 = v0 - h2 + 2*G5;
FN_DECIMAL x3 = x0 - i3 + 3*G5;
FN_DECIMAL y3 = y0 - j3 + 3*G5;
FN_DECIMAL z3 = z0 - k3 + 3*G5;
FN_DECIMAL w3 = w0 - l3 + 3*G5;
FN_DECIMAL v3 = v0 - h3 + 3*G5;
FN_DECIMAL x4 = x0 - i4 + 4*G5;
FN_DECIMAL y4 = y0 - j4 + 4*G5;
FN_DECIMAL z4 = z0 - k4 + 4*G5;
FN_DECIMAL w4 = w0 - l4 + 4*G5;
FN_DECIMAL v4 = v0 - h4 + 4*G5;
FN_DECIMAL x5 = x0 - 1 + 5*G5;
FN_DECIMAL y5 = y0 - 1 + 5*G5;
FN_DECIMAL z5 = z0 - 1 + 5*G5;
FN_DECIMAL w5 = w0 - 1 + 5*G5;
FN_DECIMAL v5 = v0 - 1 + 5*G5;
t = FN_DECIMAL(0.5) - x0*x0 - y0*y0 - z0*z0 - w0*w0 - v0*v0;
if (t < 0) n0 = 0;
else
{
t *= t;
n0 = t*t * GradCoord5D(offset, i, j, k, l, h, x0, y0, z0, w0, v0);
}
t = FN_DECIMAL(0.5) - x1*x1 - y1*y1 - z1*z1 - w1*w1 - v1*v1;
if (t < 0) n1 = 0;
else
{
t *= t;
n1 = t*t * GradCoord5D(offset, i + i1, j + j1, k + k1, l + l1, h + h1, x1, y1, z1, w1, v1);
}
t = FN_DECIMAL(0.5) - x2*x2 - y2*y2 - z2*z2 - w2*w2 - v2*v2;
if (t < 0) n2 = 0;
else
{
t *= t;
n2 = t*t * GradCoord5D(offset, i + i2, j + j2, k + k2, l + l2, h + h2, x2, y2, z2, w2, v2);
}
t = FN_DECIMAL(0.5) - x3*x3 - y3*y3 - z3*z3 - w3*w3 - v3*v3;
if (t < 0) n3 = 0;
else
{
t *= t;
n3 = t*t * GradCoord5D(offset, i + i3, j + j3, k + k3, l + l3, h + h3, x3, y3, z3, w3, v3);
}
t = FN_DECIMAL(0.5) - x4*x4 - y4*y4 - z4*z4 - w4*w4 - v4*v4;
if (t < 0) n4 = 0;
else
{
t *= t;
n4 = t*t * GradCoord5D(offset, i + i4, j + j4, k + k4, l + l4, h + h4, x4, y4, z4, w4, v4);
}
t = FN_DECIMAL(0.5) - x5*x5 - y5*y5 - z5*z5 - w4*w4 - v5*v5;
if (t < 0) n5 = 0;
else
{
t *= t;
n5 = t * t * GradCoord5D(offset, i + 1, j + 1, k + 1, l + 1, h + 1, x5, y5, z5, w5, v5);
}
return 8 * (n0 + n1 + n2 + n3 + n4 + n5); // TODO: Find value scaler
}
GradCoord5
consists of:
inline FN_DECIMAL FastNoise::GradCoord5D(unsigned char offset, int x, int y, int z, int w, int v, FN_DECIMAL xd, FN_DECIMAL yd, FN_DECIMAL zd, FN_DECIMAL wd, FN_DECIMAL vd) const
{
unsigned char lutPos = Index5D_80(offset, x, y, z, w, v) * 5;
return xd * GRAD_5D[lutPos] + yd * GRAD_5D[lutPos + 1] + zd * GRAD_5D[lutPos + 2] + wd * GRAD_5D[lutPos + 3] + vd * GRAD_5D[lutPos + 4];
}
And Index5D_80
consists of:
unsigned char FastNoise::Index5D_80(unsigned char offset, int x, int y, int z, int w, int v) const
{
return m_perm80[(x & 0xff) + m_perm[(y & 0xff) + m_perm[(z & 0xff) + m_perm[(w & 0xff) + m_perm[(v & 0xff) + offset]]]]];
}
The array GRAD_5D
is an array 400 items long, each 5 values representing a diagonal vector.
The array m_perm
is a 512 item long array of permutations, the values 0 to 255, in a random order, the second half of the array the same as the first half.
The array m_perm80
is also a 512 item long array, but the values of m_perm
have been modulo'd by 80. (Because there are 80 gradients in GRAD_5D
)
Currently I'm not sure if the FN_DECIMAL(0.5)
s should actually be FN_DECIMAL(0.6)
as is seen in the 3D & 4D simplex noise functions and SimplexNoise.java by Stefan Gustavson who wrote Simplex noise demystified in which he says the value should be changed to 0.5 (and yet doesn't do it in his own source code?). Ken Perlin uses 0.6 in his paper on Simplex Noise, but it's just a magic number which he doesn't explain the significance or origin of.
I am also not sure if the 8
used to "normalise" the sum of the n components when returning the final value is correct, though it's doing a reasonable job of keeping the values within a usable range.
I've puzzled over this math stackexchange question which purports that the normalisation value should actually be 67.6953. That value does make the noise closer to 4D noise in its variation in values but causes some values to go beyond -1 and 1. It also makes the diagonal areas more obvious and the diamond artifacts clearer.
The normalisation factor doesn't seem to be the issue however, as it just brings the values in the -1 to 1 range.
Whatever the FN_DECIMAL(0.5)
value is officially called (I think it might be the spherical kernel radius, I can't find any hard proof), reducing it brings the values closer to 0 (effectively making the image flatter, because it makes it more likely that t
will be less than 0 and therefore not use the contribution from that vertex). Ken Perlin does mention "the exact radius and amplitude of the hypersphere-shaped kernel centered at each simplex need to be tuned so as to produce the best visual results for each choice of n", and indeed pushing the value up to 0.8
(and bringing the normalisation value down to 6) does produce a more appealing image however it doesn't get rid of the odd diamond shapes and the diagonals can still sort of be seen as the slightly fuzzier areas under the diamond artifacts.
What could be causing these artifacts? Is it an indexing issue perhaps? Something else?