Is it possible to create seamless repeating noise in 3D

I'm very familiar with the following nodes for creating a 2D seamless repeating loop within procedural noise. I have been unable to find a method for repeating in 3 dimensions, this would be incredibly useful for me if anyone is familiar with a means to do so through nodes or OSL.

• I think you would need to map your coordinates to the surface of a 4d torus but I don’t know the maths of how you’d do that. Possibly see math.stackexchange.com/a/1073560 – Rich Sedman Mar 25 at 8:07
• – Allen Simpson Mar 25 at 17:33
• That might have worked had it not been for the fact that Z gets thrown out in the math and any addition to XYZW results in scrolling portions of the texture along X or Y, so rotation couldn't work either. @AllenSimpson – InTheBeef Mar 25 at 18:37

I was thinking about perhaps a 4d sphere and looked to https://en.wikipedia.org/wiki/N-sphere but this didn't produce satisfactory results due to considerable distortion at the poles.

However, on playing around with the nodes I came up with this setup :

Each of the X,Y,Z channels is passed through a Cosine function to make it cyclic and then to an Arc-cosine to make it linear again. The product of the Sine of each of the coordinates gives some travel through the 4th dimension of the noise which allow the noise to loop back to its start point without being a pure reflection of the noise - ie, straight 'bouncing' at the transition. This isn't perfect (there's some rotational symetry in the result) but is pretty close - and might be close enough for your needs.

Another option requires the use of a 6-dimensional noise texture - by generating cyclic coordinates from each of the X,Y,Z channels to produce X1,X2,Y1,Y2,Z1,Z2 (as suggested by @RobinBetts). In Is it possible to have a procedural texture radiate from the CENTER of a sphere? I did something similar using a Musgrave5D texture generated by combining two noise textures and a similar technique can be used here - but this time we have 4D noise available to us so we could theoretically generate a 7-dimensional musgrave-like texture (but we only need 6).

Consider the following nodes :

Here we're combining the output from two 4D noise textures to produce something that resembles a musgrave texture. Musgrave is very like Noise except that it has a different range of values (typically -1 to +1 instead of 0.25 to 0.75 that is typical of noise); the differing range isn't a problem since we can adjust for that using simple maths functions. The 6 inputs to the two Vector sockets can be considered as the 6 input dimensions of the noise while the two W inputs can be used as a 'seed' (you should ensure they are never set to the same value or the output will fall off to zero as they cancel each other out).

By using Sine and Cosine functions we can now take the original X,Y,Z coordinates and generate 3 sets of cyclic coordinates for input into the 6D musgrave.

Here's the complete node group :

The input coordinates are separated into X,Y,Z channels and Sine and Cosine used to generate pairs of cyclic coordinates. The result is Multiplied by a value to provide a means of scaling the noise and each set combined to feed into the associated Noise texture (note that I've opted to pass all of the cosines into one texture and all of the sines into the other - for neatness). Since I didn't need to use the W inputs separately, I've just calculated them as an opposing offset driven by a Value input node (so 0.770 results in 0.770 passed into one texture and -0.770 into the other). The outputs of the Texture nodes are combined and the Absolute allows the negative values to be seen.

This produces the following result :

Note how the noise is seamlessly repeated in each dimension. In this case the scaling of the first mapping node changes the Generated coordinates into the range 0.0 - 10.0. Since the 'cyclic nature of the Sine/Cosine functions is 2Pi (approx 6.3) this results in the pattern repeating about 2/3 of the way across the faces.

Note that if you want something closer to the output of 'Noise' (rather than 'Musgrave') you could add an Add Multiply node or a Map Range node to adjust the range as appropriate.

• This is a really great step in the right direction but the symmetry is definitely a significant bother for my needs. – InTheBeef Mar 27 at 19:39
• I think you need a noise with continuity in 6 dimensions, through which you can take X,Y,Z in 3 entirely independent 2D loops? – Robin Betts Mar 27 at 22:31
• I agree @robinbetts - that would certainly be the ideal. I think it should be possible with the available 4 dimensions (but probably with some distortion). – Rich Sedman Mar 28 at 0:08
• I did create a musgrave5d in blender.stackexchange.com/a/119604/29586.... something along those lines might work here... – Rich Sedman Mar 28 at 0:40
• This has pretty great application for noise and musgrave, do you know if the same can be done for Voronoi? (perhaps with a slight tweak) I notice that while it repeats fine the result is still far from actual voronoi. – InTheBeef Apr 1 at 20:44

I made an OSL script for periodic 4D noise a while back. It's a slight modification of an older version of Blender's own OSL implementation of the noise node.

#include "stdosl.h"

float safe_unoise(point p, float time, point pp, float timep) {
float f = pnoise(p, time, pp, timep);

if (!isfinite(f))
return 0.5;

return f;
}

float safe_snoise(point p, float time, point pp, float timep) {
float f = psnoise(p, time, pp, timep);

if (!isfinite(f))
return 0.5;

return f;
}

float noise_turbulence(point p, point pp, float details, float time, float timep, float lacunarity, float persistence) {
float frequency = 1.0;
float amplitude = 1.0;

float sum = 0.0;
float divisor = 0.0;

int whole_octaves = (int)clamp(details, 0.0, 16.0);
float partial_octave = details - floor(details);

for (int i = 0; i <= whole_octaves; i++) {
float contribution = safe_unoise(frequency * p, time, pp, timep);

divisor += amplitude;
sum += contribution * amplitude;
amplitude *= persistence;
frequency *= lacunarity;
}

sum /= divisor;

if (partial_octave != 0.0) {
float sum2 = sum + safe_unoise(frequency * p, time, pp, timep) * amplitude;

sum2 /= divisor + amplitude;
return (1.0 - partial_octave) * sum + partial_octave * sum2;
}
return sum;
}

float noise(point p, point pp,float time, float timep,  float distortion, float detail, float lacunarity, float persistence, color Color) {
point r;

if (distortion != 0.0) {
r[0] = safe_snoise(p + point(13.5), time, pp, timep) * distortion;
r[1] = safe_snoise(p, time,  pp, timep) * distortion;
r[2] = safe_snoise(p - point(13.5), time,  pp, timep) * distortion;

p += r;
}

Color = color(
noise_turbulence(p,                       pp, detail, time, timep, lacunarity, persistence),
noise_turbulence(point(p[1], p[0], p[2]), pp, detail, time, timep, lacunarity, persistence),
noise_turbulence(point(p[1], p[2], p[0]), pp, detail, time, timep, lacunarity, persistence)
);

return noise_turbulence(p, pp, detail, time, timep, lacunarity, persistence);
}

vector Vector = P,
vector PeriodV = point(16.0, 16.0, 16.0),
float Time = 0.0,
float PeriodT = 1.0/0.0,
float Scale = 5.0,
float Detail = 2.0,
float Distortion = 0.0,
float Lacunarity = 2.0,
float Persistence = 0.5,
output color Color = color(0.0, 0.0, 0.0),
output float Fac = 0.0
) {
point p = Vector;

Fac = noise(p * Scale, PeriodV, Time, PeriodT, Distortion, Detail, Lacunarity, Persistence, Color);
}

• Hello :). Would it be possible to add an image/gif illustrating the result? For those of us who don't speak python :). – Jachym Michal Apr 1 at 9:54
• Result You can see the repeating pattern generated along the Y axis because that period is set low. The face on the right however does not exhibit the same pattern since it's period is larger than the dimensions of the cube. Also, OSL is unrelated to Python. If you're looking for a programming language which is similar to the Open Shading Language, the most similar would be C. – Avereniect Apr 1 at 16:02
• I was just wondering how the result would look like. I'm not a programmer :). – Jachym Michal Apr 1 at 16:03