在Photoshop中创建无缝图像很容易:裁剪图像,抓住修剪后的右侧和底部,然后使用“淡入淡出”工具将其粘贴到左侧和顶部。但是,为了正确实现无缝噪声图,您必须仔细考虑。如果您对Perlin噪声有基本的了解,那么您就会知道它由内插的随机数组成。它主要用于二维。但它在一个维度(例如,移动时),三个维度(3D对象的圆柱和球面变换),甚至四个或五个维度中也很有用。二维噪声可用于创建无缝2D图像。我们从四个维度思考并不是很常见,因此我们一次只考虑一个维度。在我的示例中,我使用了两个八度音阶单纯形噪声。大尺寸的单工噪声更快,并且由于其三角形性质,因此看起来更好。我编写了一个小函数drawNoise
来创建画布并循环处理像素数组。一维无缝噪声
在一个维度上,噪声是一条无限平滑的线(我的噪声实现以2开头,因此我将常数用作第二个参数)。在这里,我们看到这些只是内插的随机数。
fNoiseScale = .02;
drawNoise(function(i,x,y){
var v = Simplex.noise(
123+x*fNoiseScale
,137
);
return v*iSize>y?255:0;
}).img();
一维噪声您可以在动画中使用它,每毫秒重新计算噪声值,但是您也可以创建一个循环并预先计算所有值。上图中的值不在边缘周围循环。但是实现可重复性非常简单,为此仅需一个维度和一个循环或一个循环。一维循环
对于大多数人来说,Perlin的噪音看起来像下图。如果我们在这里画一个圆并计算该圆上的噪声值,就会得到一维循环。带有圆圈的噪声会创建一维循环。在代码中,它看起来像这样:
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);
您可能已经了解了我们要做什么。要循环二维图像,我们需要一个三维(至少)噪声图。圆柱卡
Noise Perlin最初是为连续3D纹理(电影“ Tron”)而创建的。图像图不是包裹在对象上的一张纸,而是通过其在三维噪声场中的位置来计算的。因此,在切割对象时,我们仍然可以为新创建的曲面计算地图。在达到无缝图像的最终目标之前,我们首先创建一个图像,该图像无缝地左右连接。这类似于一维循环的二维圆,但有一个附加的维:圆柱体。
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);
圆柱噪声图球形地图图片
您可能会认为使用球体创建无缝图像会很方便,但您会误会。我将做一个小题述,并说明如何计算球形图像图以及它的外观。
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();
球形噪声图噪声球立方全景图
如果将相机放置在球体的中心,我们创建的球体也可以用作全景图。但是最好的方法是使用三次全景图,因为它的面孔少得多。如该草图所示,球体投影到立方体的六个侧面上。在立方体上叠加球体对于立方体表面上的每个像素,我们需要计算中心点C和球体之间的交点。它可能看起来很复杂,但实际上非常简单。我们可以将CA线视为向量。可以对向量进行归一化,以便其方向不变,但长度减小为1。因此,所有向量在一起看起来像一个球体。标准化也很简单,我们只需要将向量的值除以xyz除以向量的总长度即可。向量的长度可以使用勾股定理来计算。在下面的代码中,首先在一个面上执行归一化计算。然后针对所有六个边缘同时计算噪声,因为要获取下一个面的位置,您只需要沿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")});
这是一张图片的六个侧面,以及从多维数据集中查看时的外观截图。源代码有一个用threejs编写的3D示例。立方全景图无缝2D图像
似乎无缝2D图像很容易实现,但是在我看来,这是本文所描述的最困难的,因为要理解它,您需要从四个角度进行思考。与此最接近的是圆柱图(具有水平重复),因此我们将其作为基础。在圆柱图中,我们将图像的水平位置用作圆。也就是说,图像的水平位置在噪声场xyz中给出了两个坐标x和y。图像的垂直位置对应于噪声场中的z。我们希望图像是无缝的和垂直的,因此,如果添加另一个尺寸,则可以使用它创建第二个圆并替换z字段的线性值。这类似于在四维字段中创建两个圆柱体。我试图在草图上将其可视化,这是不准确的,但是我试图传达一般原理,而不是绘制四维圆柱体。四个维度上的两个圆柱的示意图代码非常简单:它们只是一个二维噪声空间中的两个圆。
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);
结果如下: