Creating a seamless image in Photoshop is easy: crop the image, grab the trimmed right and bottom parts, and then glue them to the left and top using the Fade tool. But for the proper implementation of seamless noise maps, you have to think carefully.If you have a basic understanding of Perlin noise , then you know that it consists of interpolated random numbers. It is mainly used in two dimensions. But it is also useful in one dimension (for example, when moving), in three dimensions (cylindrical and spherical transformation of 3D objects), and even in four or five dimensions.Four-dimensional noise can be used to create a seamless 2D image. It is not very common for us to think in four dimensions, so we will take one dimension at a time.In my examples, I used two-octave simplex noise . Simplex noise is faster in large dimensions, and due to its triangular nature, it looks better.I wrote a small function drawNoise
to create a canvas and process a pixel array in a loop.One-dimensional seamless noise
In one dimension, noise is an infinite smooth line (my implementation of noise starts with two, so I use a constant as the second parameter). Here we see that these are just interpolated random numbers.
fNoiseScale = .02;
drawNoise(function(i,x,y){
var v = Simplex.noise(
123+x*fNoiseScale
,137
);
return v*iSize>y?255:0;
}).img();
One-dimensional noiseYou can use this in animation, recalculating the noise value every millisecond, but you can also create a loop and calculate all the values โโin advance. The values โโin the image above do not loop around the edges. But implementing repeatability is quite simple, just one more dimension and loop ... or circle, for that.One-dimensional loop
For most of you, Perlinโs noise looks something like the image below.If we drew a circle here and counted the noise values โโon this circle, we would get a one-dimensional loop.Noise with a circle to create a one-dimensional loop.In code, it looks like this:
drawNoise(function(i,x,y){
var fNX = x/iSize
,fRdx = fNX*2*Math.PI
,a = fRdsSin*Math.sin(fRdx)
,b = fRdsSin*Math.cos(fRdx)
,v = Simplex.noise(
123+a*fNoiseScale
,132+b*fNoiseScale
)
;
return v*iSize>y?255:0;
}).img().div(2);
You probably already understood what we're going to. To loop a two-dimensional image, we need a three-dimensional (at least) noise map.Cylindrical card
Noise Perlin was originally created for continuous 3D-texturing (the film "Tron"). The image map is not a sheet of paper wrapped around an object, but is calculated by its location in a three-dimensional noise field. Therefore, when cutting the object, we can still calculate the map for the newly created surface.Before we reach our ultimate goal of a seamless image, we first create an image that seamlessly joins left and right. This is similar to a two-dimensional circle for a one-dimensional loop, but with one additional dimension: a cylinder.
drawNoise(function(i,x,y){
var fNX = x/iSize
,fRdx = fNX*2*Math.PI
,a = fRdsSin*Math.sin(fRdx)
,b = fRdsSin*Math.cos(fRdx)
,v = Simplex.noise(
123+a*fNoiseScale
,132+b*fNoiseScale
,312+y*fNoiseScale
)
;
return v*255<<0;
}).img().div(2);
Cylindrical noise mapSpherical map image
You might think that it would be convenient to use a sphere to create a seamless image, but you are mistaken.I will make a small digression and show how the spherical image map is calculated and what it looks like.
document.body.addChild('h2').innerText = 'three dimensional spherical map';
fNoiseScale = .1;
var oSpherical = drawNoise(function(i,x,y){
var fNX = (x+.5)/iSize
,fNY = (y+.5)/iSize
,fRdx = fNX*2*Math.PI
,fRdy = fNY*Math.PI
,fYSin = Math.sin(fRdy+Math.PI)
,a = fRdsSin*Math.sin(fRdx)*fYSin
,b = fRdsSin*Math.cos(fRdx)*fYSin
,c = fRdsSin*Math.cos(fRdy)
,v = Simplex.noise(
123+a*fNoiseScale
,132+b*fNoiseScale
,312+c*fNoiseScale
)
;
return v*255<<0;
}).img();
Spherical noise mapSphere with noiseCubic panoramic map
The sphere we created can also be used as a panorama if you place a camera in the center of the sphere. But the best way would be to use a cubic panorama, because it has much fewer faces. The sphere is projected onto the six sides of the cube, as shown in this sketch.Superimposing a sphere on a cubeFor each pixel on the surface of the cube, we need to calculate the intersection between the viewpoint C in the center and the sphere. It may seem complicated, but it's actually pretty simple.We can consider the CA line as a vector. And vectors can be normalized so that their direction does not change, but the length decreases to 1. Due to this, all vectors together will look like a sphere.Normalization is also quite simple, we just need to divide the values โโof the vector by xyz by the total length of the vector. The length of the vector can be calculated using the Pythagorean theorem.In the code below, the normalization calculation is first performed on one face. Then the noise is computed simultaneously for all six edges, because to get the position of the next face, you just need to flip the values โโalong xyz.
document.body.addChild('h2').innerText = '3D panoramical cube map';
var mCubemap = document.createElement('canvas')
,iW = 6*iSize;
mCubemap.width = iW;
mCubemap.height = iSize;
var iHSize = iSize/2
,oCtx = mCubemap.getContext('2d')
,oImgData = oCtx.getImageData(0,0,iW,iSize)
,aPixels = oImgData.data
,aa = 123
,bb = 231
,cc = 321
;
for (var i=0,l=iSize*iSize;i<l;i++) {
var x = i%iSize
,y = (i/iSize)<<0
,a = -iHSize + x+.5
,b = -iHSize + y+.5
,c = -iHSize
,fDistanceAB = Math.sqrt(a*a+b*b)
,fDistanceABC = Math.sqrt(fDistanceAB*fDistanceAB+c*c)
,fDrds = .5*fDistanceABC
,v = 1
;
a /= fDrds;
b /= fDrds;
c /= fDrds;
var aNoisePositions = [
[a,b,c]
,[-c,b,a]
,[-a,b,-c]
,[c,b,-a]
,[a,c,-b]
,[a,-c,b]
];
for (var j=0;j<6;j++) {
v = Simplex.noise(
aa + aNoisePositions[j][0]
,bb + aNoisePositions[j][1]
,cc + aNoisePositions[j][2]
);
var pos = 4*(y*iW+j*iSize+x);
aPixels[pos] = aPixels[pos+1] = aPixels[pos+2] = v*255<<0;
aPixels[pos+3] = 255;
}
}
oCtx.putImageData(oImgData,0,0);
document.body.addChild('img',{src:mCubemap.toDataURL("image/jpeg")});
Here are six sides in one image, plus a screenshot of how it looks when viewed from a cube. The source code has a 3D example written in threejs .Cubic panoramic mapSeamless 2D Image
It may seem that a seamless 2D image is easy to implement, but it seems to me that this is the most difficult of the described in the article, because to understand it you need to think in four dimensions. The closest thing to this was a cylindrical map (with horizontal repetition), so we will take it as a basis. In the cylindrical map, we used the horizontal position of the image for the circle; that is, the horizontal position of the image gives us two coordinates x and y in the noise field xyz. The vertical position of the image corresponds to z in the noise field.We want the image to be seamless and vertical, so if we add another dimension, we can use it to create a second circle and replace the linear value of the z field. This is similar to creating two cylinders in a four-dimensional field. I tried to visualize this on a sketch, it is inaccurate, but I tried to convey the general principle, and not draw a four-dimensional cylinder.A sketch of two cylinders in four dimensionsThe code is quite simple: these are just two circles in a four-dimensional noise space.
fNoiseScale = .003;
drawNoise(function(i,x,y){
var fNX = x/iSize
,fNY = y/iSize
,fRdx = fNX*2*Math.PI
,fRdy = fNY*2*Math.PI
,a = fRds*Math.sin(fRdx)
,b = fRds*Math.cos(fRdx)
,c = fRds*Math.sin(fRdy)
,d = fRds*Math.cos(fRdy)
,v = Simplex.noise(
123+a*fNoiseScale
,231+b*fNoiseScale
,312+c*fNoiseScale
,273+d*fNoiseScale
)
;
return (Math.min(Math.max(2*(v -.5)+.5,0),1)*255)<<0;
}).img().div(2,2);
And here is the result: