创建无缝噪声图

图片

在Photoshop中创建无缝图像很容易:裁剪图像,抓住修剪后的右侧和底部,然后使用“淡入淡出”工具将其粘贴到左侧和顶部。但是,为了正确实现无缝噪声图,您必须仔细考虑。

如果您对Perlin噪声基本的了解,那么您就会知道它由内插的随机数组成。它主要用于二维。但它在一个维度(例如,移动时),三个维度(3D对象的圆柱和球面变换),甚至四个或五个维度中也很有用。

二维噪声可用于创建无缝2D图像。我们从四个维度思考并不是很常见,因此我们一次只考虑一个维度。

我的示例中,我使用了两个八度音阶单纯形噪声大尺寸的单工噪声更快,并且由于其三角形性质,因此看起来更好。

我编写了一个小函数drawNoise来创建画布并循环处理像素数组。

一维无缝噪声


在一个维度上,噪声是一条无限平滑的线(我的噪声实现以2开头,因此我将常数用作第二个参数)。在这里,我们看到这些只是内插的随机数。

// one dimensional line
fNoiseScale = .02;
drawNoise(function(i,x,y){
    var v = Simplex.noise(
         123+x*fNoiseScale
        ,137 // we just need one dimension so this parameter is a constant
    );
    return v*iSize>y?255:0;
}).img();


一维噪声

您可以在动画中使用它,每毫秒重新计算噪声值,但是您也可以创建一个循环并预先计算所有值。上图中的值不在边缘周围循环。但是实现可重复性非常简单,为此仅需一个维度和一个循环或一个循环。

一维循环


对于大多数人来说,Perlin的噪音看起来像下图。


如果我们在这里画一个圆并计算该圆上的噪声值,就会得到一维循环。


带有圆圈的噪声会创建一维循环。

在代码中,它看起来像这样:

// one dimensional loop
drawNoise(function(i,x,y){
    var fNX = x/iSize // we let the x-offset define the circle
        ,fRdx = fNX*2*Math.PI // a full circle is two pi radians
        ,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);


您可能已经了解了我们要做什么。要循环二维图像,我们需要一个三维(至少)噪声图。

圆柱卡


Noise Perlin最初是为连续3D纹理(电影“ Tron”)而创建的。图像图不是包裹在对象上的一张纸,而是通过其在三维噪声场中的位置来计算的。因此,在切割对象时,我们仍然可以为新创建的曲面计算地图。

在达到无缝图像的最终目标之前,我们首先创建一个图像,该图像无缝地左右连接。这类似于一维循环的二维圆,但有一个附加的维:圆柱体。

// three dimensional cylindrical map
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 // similar to the one dimensional loop but we add a third dimension defined by the image y-offset
        )
    ;
    return v*255<<0;
}).img().div(2);


圆柱噪声图

球形地图图片


您可能会认为使用球体创建无缝图像会很方便,但您会误会。

我将做一个小题述,并说明如何计算球形图像图以及它的外观。

// three dimensional spherical map
document.body.addChild('h2').innerText = 'three dimensional spherical map';
fNoiseScale = .1;
var oSpherical = drawNoise(function(i,x,y){
    var  fNX = (x+.5)/iSize // added half a pixel to get the center of the pixel instead of the top-left
        ,fNY = (y+.5)/iSize
        ,fRdx = fNX*2*Math.PI
        ,fRdy = fNY*Math.PI // the vertical offset of a 3D sphere spans only half a circle, so that is one Pi radians
        ,fYSin = Math.sin(fRdy+Math.PI) // a 3D sphere can be seen as a bunch of cicles stacked onto each other, the radius of each of these is defined by the vertical position (again one Pi radians)
        ,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();


球形噪声图


噪声球

立方全景图


如果将相机放置在球体的中心,我们创建的球体也可以用作全景图。但是最好的方法是使用三次全景图,因为它的面孔少得多。如该草图所示,球体投影到立方体的六个侧面上。


在立方体上叠加球体对于立方体

表面上的每个像素,我们需要计算中心点C和球体之间的交点。它可能看起来很复杂,但实际上非常简单。

我们可以将CA线视为向量。可以对向量进行归一化,以便其方向不变,但长度减小为1。因此,所有向量在一起看起来像一个球体。

标准化也很简单,我们只需要将向量的值除以xyz除以向量的总长度即可。向量的长度可以使用勾股定理来计算。

在下面的代码中,首先在一个面上执行归一化计算。然后针对所有六个边缘同时计算噪声,因为要获取下一个面的位置,您只需要沿xyz翻转值即可。

// 3D panoramical cube map
document.body.addChild('h2').innerText = '3D panoramical cube map';
// we're not using the drawNoise function because our canvas is rectangular
var mCubemap = document.createElement('canvas')
    ,iW = 6*iSize;
mCubemap.width = iW;
mCubemap.height = iSize;
var  iHSize = iSize/2 // half the size of the cube
    ,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        // x position in image
        ,y = (i/iSize)<<0    // y position in image
        ,a = -iHSize + x+.5    // x position on the cube plane, the added .5 is to get the center of the pixel
        ,b = -iHSize + y+.5 // y position on the cube plane
        ,c = -iHSize        // z position of the cube plane
        ,fDistanceAB = Math.sqrt(a*a+b*b) // to calculate the vectors length we use Pythagoras twice
        ,fDistanceABC = Math.sqrt(fDistanceAB*fDistanceAB+c*c)
        ,fDrds = .5*fDistanceABC // adjust the distance a bit to get a better radius in the noise field
        ,v = 1
    ;
    a /= fDrds; // normalize the vector
    b /= fDrds; // normalize the vector
    c /= fDrds; // normalize the vector
    //
    // since we now know the spherical position for one plane we can derive the positions for the other five planes simply by switching the x, y and z values (the a, b and c variables)
    var aNoisePositions = [
         [a,b,c]    // back
        ,[-c,b,a]    // right
        ,[-a,b,-c]    // front
        ,[c,b,-a]    // left
        ,[a,c,-b]    // top
        ,[a,-c,b]    // bottom
    ];
    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); // the final position of the rgba pixel
        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")});

这是一张图片的六个侧面,以及从多维数据集中查看时的外观截图。源代码有一个用threejs编写的3D示例


立方全景图


无缝2D图像


似乎无缝2D图像很容易实现,但是在我看来,这是本文所描述的最困难的,因为要理解它,您需要从四个角度进行思考。与此最接近的是圆柱图(具有水平重复),因此我们将其作为基础。在圆柱图中,我们将图像的水平位置用作圆。也就是说,图像的水平位置在噪声场xyz中给出了两个坐标x和y。图像的垂直位置对应于噪声场中的z。

我们希望图像是无缝的和垂直的,因此,如果添加另一个尺寸,则可以使用它创建第二个圆并替换z字段的线性值。这类似于在四维字段中创建两个圆柱体。我试图在草图上将其可视化,这是不准确的,但是我试图传达一般原理,而不是绘制四维圆柱体。


四个维度上的两个圆柱的示意图

代码非常简单:它们只是一个二维噪声空间中的两个圆。

// four dimensional tile
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);

结果如下:

图片

All Articles