如何生成可平铺的Perlin噪声?


127

有关:

我想生成可平铺的Perlin噪声。我正在使用Paul Bourke的 PerlinNoise*()功能,如下所示:

// alpha is the "division factor" (how much to damp subsequent octaves with (usually 2))
// beta is the factor that multiplies your "jump" into the noise (usually 2)
// n is the number of "octaves" to add in
double PerlinNoise2D(double x,double y,double alpha,double beta,int n)
{
   int i;
   double val,sum = 0;
   double p[2],scale = 1;

   p[0] = x;
   p[1] = y;
   for (i=0;i<n;i++) {
      val = noise2(p);
      sum += val / scale;
      scale *= alpha;
      p[0] *= beta;
      p[1] *= beta;
   }
   return(sum);
}

使用如下代码:

real val = PerlinNoise2D( x,y, 2, 2, 12 ) ; // test

return val*val*skyColor + 2*val*(1-val)*gray + (1-val)*(1-val)*cloudColor ;

给天空像

不合格的

哪个是不平铺的。

像素值为0-> 256(宽度和高度),像素(0,0)使用(x,y)=(0,0),像素(256,256)使用(x,y)=(1,1)

我如何使其平铺?


14
仅供参考,您没有Perlin的喧嚣;它是分形噪声。Perlin噪声很可能是“ noise2”函数,它会生成分形噪声的每个八度。
内森·里德

Answers:


80

制作无缝可平铺的fBm噪声有两个部分。首先,您需要使Perlin杂讯功能本身可以平铺。以下是一些简单的Perlin噪声函数的Python代码,该函数可在256个周期内任意工作(您可以通过修改第一部分来随意扩展它):

import random
import math
from PIL import Image

perm = range(256)
random.shuffle(perm)
perm += perm
dirs = [(math.cos(a * 2.0 * math.pi / 256),
         math.sin(a * 2.0 * math.pi / 256))
         for a in range(256)]

def noise(x, y, per):
    def surflet(gridX, gridY):
        distX, distY = abs(x-gridX), abs(y-gridY)
        polyX = 1 - 6*distX**5 + 15*distX**4 - 10*distX**3
        polyY = 1 - 6*distY**5 + 15*distY**4 - 10*distY**3
        hashed = perm[perm[int(gridX)%per] + int(gridY)%per]
        grad = (x-gridX)*dirs[hashed][0] + (y-gridY)*dirs[hashed][1]
        return polyX * polyY * grad
    intX, intY = int(x), int(y)
    return (surflet(intX+0, intY+0) + surflet(intX+1, intY+0) +
            surflet(intX+0, intY+1) + surflet(intX+1, intY+1))

Perlin噪声是由小的“ surflets”的总和产生的,这些surflets是随机定向的梯度和可分离的多项式衰减函数的乘积。这给出一个正区域(黄色)和负区域(蓝色)

核心

这些surflets的范围为2x2,并以整数晶格点为中心,因此,空间中每个点的Perlin噪声值是通过将其占据的单元角处的surflets相加而产生的。

求和

如果使梯度方向以一定的周期环绕,则噪声本身将以相同的周期无缝环绕。这就是为什么上面的代码将晶格坐标取模为周期,然后再将其散列通过排列表的原因。

另一个步骤是,在对八度进行求和时,您将需要使用八度的频率来缩放周期。本质上,您将希望每个八度将一次完整的图像平铺一次,而不是多次:

def fBm(x, y, per, octs):
    val = 0
    for o in range(octs):
        val += 0.5**o * noise(x*2**o, y*2**o, per*2**o)
    return val

放在一起,你会得到像这样的东西:

size, freq, octs, data = 128, 1/32.0, 5, []
for y in range(size):
    for x in range(size):
        data.append(fBm(x*freq, y*freq, int(size*freq), octs))
im = Image.new("L", (size, size))
im.putdata(data, 128, 128)
im.save("noise.png")

花木fBm噪声

如您所见,这确实可以无缝平铺:

fBm噪声,平铺

通过一些小的调整和颜色映射,下面是一个2x2平铺的云图像:

乌云!

希望这可以帮助!


3
我不是一个python家伙,所以我问,如何x*2**o转换为C?是:x*pow(2,o)还是pow(x*2,o)
idev

7
x*pow(2, o),因为幂运算的优先级高于乘法。
约翰·卡尔斯贝克

1
有人可以将其转换为C吗?我在理解此代码时遇到了很多问题,因为我从未使用python做任何事情。例如什么是a价值?而且我不确定函数如何转换为C ...我仅在输出中获得直线。
idev

1
只要您可以将噪声域与图块的形状相关联,这绝对是最好的解决方案。例如,这不允许任意旋转。但是,如果您不需要任何此类东西,这就是理想的答案。
约翰·卡尔斯贝克

1
注意:如果要生成128以外的其他大小,请不要更改行上的数值im.putdata(data, 128, 128)。(对于不熟悉python或PIL的用户:它们表示缩放比例和偏移量,而不是图像大小。)
Antti Kissaniemi

87

这是一种使用4D Perlin噪声的相当巧妙的方法。

基本上,将像素的X坐标映射到2D圆,将像素的Y坐标映射到第二个2D圆,然后将这两个圆彼此正交放置在4D空间中。生成的纹理是可平铺的,没有明显的变形,并且不会像镜像纹理那样重复。

本文中的粘贴代码:

for x=0,bufferwidth-1,1 do
    for y=0,bufferheight-1,1 do
        local s=x/bufferwidth
        local t=y/bufferheight
        local dx=x2-x1
        local dy=y2-y1

        local nx=x1+cos(s*2*pi)*dx/(2*pi)
        local ny=y1+cos(t*2*pi)*dy/(2*pi)
        local nz=x1+sin(s*2*pi)*dx/(2*pi)
        local nw=y1+sin(t*2*pi)*dy/(2*pi)

        buffer:set(x,y,Noise4D(nx,ny,nz,nw))
    end
end

3
这绝对是正确的答案。添加尺寸是数学家的老把戏。奥林德·罗德里格斯(Olinde Rodrigues docet)(西汉密尔顿爵士(WR Hamilton docet)先生也可,但略少一些)
FxIII '02

@FxIII,您知道应该如何实现Noise4D()吗?我想尝试一下,但我不知道此Noise4D()应该如何工作。
idev

4
您可以使用任何4D噪波功能。我建议单面噪声。webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
John Calsbeek 2012年

2
谢谢约翰!正常工作,甜蜜!没有人说,但是:x1,y1,x2,y2似乎是某种缩放比例,距离越大,噪声越细。如果这可以帮助任何人。
idev

5
请注意,这在拓扑学上等同于bobobobo的答案:您的映射将2个torus嵌入到ℝ⁴中,而没有将它嵌入ℝ³时不可避免地出现的度量失真的情况,这是可能的。
leftaboutabout

22

好,我知道了。答案是走在一个圆环在3D噪声,生成2D纹理出来。

圆环包裹2 dirs

码:

Color Sky( double x, double y, double z )
{
  // Calling PerlinNoise3( x,y,z ),
  // x, y, z _Must be_ between 0 and 1
  // for this to tile correctly
  double c=4, a=1; // torus parameters (controlling size)
  double xt = (c+a*cos(2*PI*y))*cos(2*PI*x);
  double yt = (c+a*cos(2*PI*y))*sin(2*PI*x);
  double zt = a*sin(2*PI*y);
  double val = PerlinNoise3D( xt,yt,zt, 1.5, 2, 12 ) ; // torus

  return val*val*cloudWhite + 2*val*(1-val)*gray + (1-val)*(1-val)*skyBlue ;
}

结果:

一旦:

平铺的天空

并平铺:

显示瓷砖


6
有点用,但是由于圆环的曲率,您看起来像一堆失真。
内森·里德

1
您确实可以对职位进行取模,但是我喜欢这个问题的所有出色/创意性答案。有很多不同的方法可以做同样的事情。

我注意到您实际上不想使用0-1值,但是使用0-0.9999 ...值!因此您将使用:x / width,y / height等。否则,接缝将不匹配(使相对边缘的像素完全相同)。而且看起来PerlinNoise3D()函数也需要对结果值进行钳位,否则某些像素值会溢出。
idev 2012年

@Nathan,您知道如何解决变形吗?
idev

2
@idev我相信解决失真的方法是在此问题的最高答案中使用4D方法。;)
内森·里德

16

我能想到的一种简单方法是获取噪声函数的输出并将其镜像/翻转为两倍大小的图像。很难解释,所以这是一张图片: 在此处输入图片说明

现在,在这种情况下,当您查看此内容时很明显。我可以想到两种方法(可能是:-)来解决这个问题:

  1. 您可以拍摄更大的图像,然后在其上方产生更多的噪点,但(我不确定是否可能)聚焦在中间(因此边缘保持不变)。它可能会增加额外的差异,使您的大脑认为这不仅仅是镜像。

  2. (我也不确定这是否可行)。您可以尝试调整噪波功能的输入,以不同方式生成初始图像。您必须通过反复试验来做到这一点,但是要在平铺/镜像它时寻找吸引您眼球的功能,然后尝试使其不产生那些。

希望这可以帮助。


3
很好,但是太对称了!
bobobobo 2012年

1
@bobobobo这就是我在想其他步骤可以缓解的问题。因此,您可以使用此方法生成“基础”,然后在整个过程中添加更多详细信息,以使其看起来像没有被镜像。
理查德·马斯克

当您做这种事情时,您开始得到一些奇怪的模式。特别是这只看起来像只蝴蝶。不过,简单的解决方案。
notlesh 2012年

这也是我的第一种方法,但是它有一个问题,在这里可以看到: dl.dropbox.com/u/6620757/noise_seam.png 当您越过翻转边界时,会通过立即反转像素的斜率而导致噪波函数不相交。功能。即使在顶部应用第二个噪波功能,该噪点在输出中仍然可见。
Jherico 2012年

好想法。这可以使用三角波功能在像素着色器中轻松完成:tex2d(abs(abs(uv.x)%2.0-1.0), abs(abs(uv.y)%2.0-1.0))
tigrou

10

此答案的第一个版本实际上是错误的,我已对其进行了更新

我成功使用的一种方法是平铺噪声。换句话说,使您的基本noise2()功能具有周期性。如果noise2()为且beta为整数,则产生的噪声与的周期相同noise2()

我们如何进行noise2()定期?在大多数实现中,此功能使用某种晶格噪声。即,它在整数坐标处获取随机数,并对其进行插值。例如:

function InterpolatedNoise_1D(float x)

  integer_X    = int(x)
  fractional_X = x - integer_X

  v1 = SmoothedNoise1(integer_X)
  v2 = SmoothedNoise1(integer_X + 1)

  return Interpolate(v1 , v2 , fractional_X)

end function

可以对该函数进行微不足道的修改,使其变为具有整数周期的周期性。只需添加一行:

integer_X = integer_X % Period

在计算v1v2。这样,整数坐标处的值将以每个“周期”单位重复,并且插值将确保结果函数是平滑的。

但是请注意,这仅在Period大于1时有效。因此,要在制作无缝纹理时实际使用它,您必须对Period x Period Square(而非1x1)进行采样。


但是,您如何使它成为noise2周期性的(短周期,例如1个单位)?我认为这就是问题的根本所在。标准Perlin噪声是周期性的,每根轴上的周期为256,但是您需要具有较小周期的修改后的噪声。
内森·里德

@Nathan Reed如果noise2按照建议进行调用,则无论函数本身是否具有周期性,都获得周期性结果。因为参数每1个单位环绕一次。
没关系

1
但是,然后在网格线上出现了接缝,不是吗?由于无法保证noise2(0,0.999)接近noise2(0,0),除非我错过了什么。
内森·里德

1
@Nathan Reed很好。实际上,我只是重新检查了我的旧代码,事实证明我错了。我现在将编辑答案。
没关系

大!现在,这实际上是一个很好的答案。+1 :)
内森·里德

6

另一种选择是使用libnoise库生成噪声。您可以无缝地在理论上无限的空间上生成噪声。

看一下以下内容:http : //libnoise.sourceforge.net/tutorials/tutorial3.html#tile

上面还有一个XNA端口:http : //bigblackblock.com/tools/libnoisexna

如果最终使用了XNA端口,则可以执行以下操作:

Perlin perlin = new Perlin();
perlin.Frequency = 0.5f;                //height
perlin.Lacunarity = 2f;                 //frequency increase between octaves
perlin.OctaveCount = 5;                 //Number of passes
perlin.Persistence = 0.45f;             //
perlin.Quality = QualityMode.High;
perlin.Seed = 8;

//Create our 2d map
Noise2D _map = new Noise2D(CHUNKSIZE_WIDTH, CHUNKSIZE_HEIGHT, perlin);

//Get a section
_map.GeneratePlanar(left, right, top, down);

GeneratePlanar是用于在每个方向上获取将与其余纹理无缝连接的部分的函数。

当然,与仅具有可在多个表面上使用的单个纹理相比,此方法的成本更高。如果您想创建一些随机的可平铺纹理,那么您可能会感兴趣。


6

尽管这里有一些可行的答案,但大多数答案都很复杂,缓慢且存在问题。

您真正需要做的就是使用周期性噪声生成功能。而已!

此处可以找到基于Perlin的“高级”噪声算法的出色公共领域实现。您需要的功能是pnoise2。该代码是由Stefan Gustavson编写的,他在此处就此问题以及其他人如何采取错误的方法发表了明确的评论。听古斯塔夫森,他知道他在说什么。

关于这里提出的各种球形投影:好吧,它们本质上工作缓慢(但是),但它们还会产生2D纹理,它是一个扁平的球体,因此边缘会更加凝结,可能会产生不良效果。当然,如果您打算将2D纹理投影到球体上,那就是要走的路,但这并不是我们要的。


4

这是处理平铺噪声的简单得多的方法:

阴影代码中的平铺佩林噪声

您可以针对每种噪声等级使用模块化环绕。无论您使用哪种频率标度,它们都适合区域的边缘。因此,您只需要使用更快的普通2D噪点即可。这是可在ShaderToy上找到的实时WebGL代码:https ://www.shadertoy.com/view/4dlGW2

前三个函数完成所有工作,并且fBM传递了一个介于0.0到1.0范围内的向量x / y。

// Tileable noise, for creating useful textures. By David Hoskins, Sept. 2013.
// It can be extrapolated to other types of randomised texture.

#define SHOW_TILING
#define TILES 2.0

//----------------------------------------------------------------------------------------
float Hash(in vec2 p, in float scale)
{
    // This is tiling part, adjusts with the scale...
    p = mod(p, scale);
    return fract(sin(dot(p, vec2(35.6898, 24.3563))) * 353753.373453);
}

//----------------------------------------------------------------------------------------
float Noise(in vec2 x, in float scale )
{
    x *= scale;

    vec2 p = floor(x);
    vec2 f = fract(x);
    f = f*f*(3.0-2.0*f);
    //f = (1.0-cos(f*3.1415927)) * .5;
    float res = mix(mix(Hash(p,                  scale),
        Hash(p + vec2(1.0, 0.0), scale), f.x),
        mix(Hash(p + vec2(0.0, 1.0), scale),
        Hash(p + vec2(1.0, 1.0), scale), f.x), f.y);
    return res;
}

//----------------------------------------------------------------------------------------
float fBm(in vec2 p)
{
    float f = 0.4;
    // Change starting scale to any integer value...
    float scale = 14.0;
    float amp = 0.55;
    for (int i = 0; i < 8; i++)
    {
        f += Noise(p, scale) * amp;
        amp *= -.65;
        // Scale must be multiplied by an integer value...
        scale *= 2.0;
    }
    return f;
}

//----------------------------------------------------------------------------------------
void main(void)
{
    vec2 uv = gl_FragCoord.xy / iResolution.xy;

#ifdef SHOW_TILING
    uv *= TILES;
#endif

    // Do the noise cloud (fractal Brownian motion)
    float bri = fBm(uv);

    bri = min(bri * bri, 1.0); // ...cranked up the contrast for no reason.
    vec3 col = vec3(bri);

#ifdef SHOW_TILING
    vec2 pixel = (TILES / iResolution.xy);
    // Flash borders...
    if (uv.x > pixel.x && uv.y > pixel.y                                        // Not first pixel
    && (fract(uv.x) < pixel.x || fract(uv.y) < pixel.y) // Is it on a border?
    && mod(iGlobalTime-2.0, 4.0) < 2.0)                 // Flash every 2 seconds
    {
        col = vec3(1.0, 1.0, 0.0);
    }
#endif
    gl_FragColor = vec4(col,1.0);
}

1
您的图片链接已失效。我做出了一个最佳猜测,并用您发布的shadertoy代码的输出的屏幕截图替换了它。如果不正确,请直接将您想要的图像直接上传到Stack Exchange服务器。
皮卡列克18'Aug

3

我在瓦片的边缘(边缘包裹)附近插值了一些不错的结果,但这取决于您要实现什么效果以及确切的噪声参数。对于有些模糊的噪声效果很好,而对于尖峰/细粒度的噪声效果不佳。


0

我正在检查该线程以寻找类似问题的答案,然后从此python代码的开发人员那里得到了一种干净紧凑的解决方案,可以从perlin / simplex噪声中生成分形噪声。此(封闭的)发行版中提供更新的代码,可以通过将“发电机”右侧的梯度设置为与左侧的梯度相同(对于顶部和底部相同来恢复操作,例如

# Gradients
angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1)
gradients = np.dstack((np.cos(angles), np.sin(angles)))
# Make the noise tileable
gradients[-1,:] = gradients[0,:]
gradients[:,-1] = gradients[:,0]

似乎是一个优雅而干净的解决方案,我避免在这里复制整个代码(因为这不是我自己的解决方案),但是可以在上面给出的链接中找到它。希望这对希望产生像我需要的这种可平铺的分形2d图像的人有用,并且没有伪影或失真。

花木分形地形

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.