噪音 噪声是游戏编程中常见的技术,广泛应用于地形生成、图形等方面。
那为什么引入噪音的概念?在程序中,我们经常使用最简单的rand()来直接生成随机值,但问题是生成的随机值太“随机”,并且获得的值总是不均匀的。下图使用随机值作为像素点的黑白程度:
并且使用噪声,我们得到的值看起来随机但温和,并且图表看起来也更自然和舒适:
根据维基百科,已经有许多类型的噪音:
本文主要描述三种常见噪声:值噪声、柏林噪声和单纯形噪声
随机性随机性是噪音的基础,不用说
大麻在《我的世界》,因为世界是无限的,所以它只以“组块”(16×16×256网格)为单位加载玩家附近的块换句话说,当玩家移动时,它将卸载远块,然后装载近块。
的一个问题是,当玩家离开一个街区时,他进入第二个街区,然后返回第一个街区。此时,玩家期望看到的第一个方块与他之前看到的一致。例如,当输入1时获得0.3,当输入2时获得0.7,当再次输入1时期望获得0.3
因此,噪声的一个重要属性是散列(hashing)
虽然输入值被用作srand()的参数来设置rand()的种子,但是实现散列效果也是可行的
但是,最好花些时间编写自己的散列函数,以便于使用,并且不破坏程序中其他地方使用的rand()的效果。
11代表输入一维坐标和输出一维值相似hash22表示输入二维坐标并输出二维值
要了解关于随机散列函数实现的更多信息,请参考以下两个散列码:
https://www . shader toy . com/view/4s C3 z 2
https://www . shader toy . com/view/4Djsrw
平滑度(连续性)对于随机生成的地形,如果您简单地使用随机和散列组合,
很容易得到下图(以一维地图为例,x轴是位置,y轴是地形高度):
容易看到的问题是,由于随机无序,地形非常不平坦,这不是自然地形。
我们期望的地形不仅要随机,还要平滑,这样才能显得自然,如下图所示:
值噪声
值噪声是最简单的一种噪声。它的主要思想是定义几个顶点,每个顶点包含一个随机值。这些顶点将根据它们自己的随机值影响周围的坐标,并且顶点越近,越容易受到顶点的影响。当需要某个坐标的输出值时,需要叠加坐标附近每个顶点引起的影响值,得到一个总值并输出
原则
1。首先定义一个网格结构,每个网格顶点都有一个伪随机值对于二维值噪声,网格结构是平面网格(通常是正方形),三维是三维网格(通常是立方体)
2。输入一个点(二维是二维坐标,三维是三维坐标,三维是N坐标)。我们找到与其相邻的晶格顶点(二维4个,三维8个,二维2n个),并获得这些顶点的伪随机值
3。使用缓和曲线计算这些伪随机值的权重和原始柏林噪声实现中使用的弛豫曲线是S (t) = 3T 22T 3。在2002年的论文中,柏林被改进为6T 515T 4+10T 3
如果直接使用线性插值,它在网格顶点的一阶导数(即t = 0或t = 1)不是0,这将导致明显的不连续性。s(t)=3t^2−2t^3
满足一阶导数的连续性,s (t) = 6t 515t 4+10t 3仍然满足二阶导数的连续性
所以实际上这两种过渡曲线都是可用的。如果需要压缩成本,则使用s (t) = 3t 22t 3
对于预计算,例如,以编程方式生成凹凸纹理(置换纹理),最好使用S (t) = 6t515t4+10t3
实施(2D)柏林噪音
谈到噪音,最著名和最常见的是柏林噪音,其名字来自其创始人肯·柏林(Ken Perlin)。
在理解了上述值噪声之后,让我们来看看柏林噪声的主要思想:
定义了几个顶点,每个顶点包含一个随机梯度向量。这些顶点将根据它们的梯度向量对周围的坐标产生势能影响。沿着顶点的梯度方向越高,势能就越高。当需要某个坐标的输出值时,需要叠加坐标附近每个顶点产生的势能,以获得总势能并输出。
我们给顶点一个随机散列函数,并输入一个坐标来获得一个满足上述随机性和散列的随机向量。
此外,由于势能沿梯度方向逐渐变化,因此容易获得平滑度。
原则和值噪声一样,是基于网格的噪声,需要三个步骤:
1。首先定义一个网格结构,每个网格顶点有一个随机梯度向量对于二维柏林噪声,网格结构是平面网格(通常是正方形),三维网格(通常是立方体)
2。输入点坐标(二维是二维,三维是三维,三维是三维)。我们找到与之相邻的晶格顶点(二维4个,三维8个,二维2n个)。计算点到每个网格顶点的距离矢量,并将距离矢量分别与顶点上的梯度矢量相乘,得到2n个点相乘结果
//点乘以floatdot(矢量2v1,矢量2v 2){ return v1 . x * v2 . x+v1 . y * v2 . y;}
3。使用过渡曲线计算它们的权重和(类似地,它可以是S (t) = 3T22T3
),或S (t) = 6t515t4+10t3
)
下图显示了2D柏林噪声通过色差产生的每个像素点的值:
实施(2D)
是另一种更快的实现,其与标准实现的不同之处在于晶体顶点从多个梯度向量中随机选择向量,而不是生成随机向量,该随机向量可以在预先计算梯度值时计算每个项目的系数所以我们只需要像这样重写grad函数:
这里的例子提供了4个可选的随机向量。事实上,这个数字相对较小。如果您想要更多样化的效果,建议在实现中提供更多可选的随机向量。
单一噪声单纯形噪声也是基于格的梯度噪声。它和柏林噪声的唯一区别是它的晶格不是正方形(2D是正方形,3D是立方体,我们称它们为超立方体和高纬度的超立方体),而是单形的
可以被认为是在n维空间中选择最简单和最紧凑的多边形来平铺整个n维空间,如果它用于解释单纯形的话我们可以很容易地认为一维空间中的单形是等长的线段,整个一维空间可以通过连接这些线段的末端来铺砌。在二维空间中,单纯形是三角形,我们可以连接等腰三角形来覆盖整个平面。三维空间中的单纯形是四面体。高维空间的简单性也存在
总结了在n维空间中,超立方体的顶点数是2n,而单纯形的顶点数是n+1,这使得在计算梯度噪声时可以大大减少要计算的顶点权重数。
的一个潜在问题是如何找到输入点所在的单纯形
计算柏林噪声时,很容易判断输入点所在的平方。我们只需要舍入输入点就可以找到它。
对于单纯形,我们需要倾斜单纯形的坐标,以将平铺空间的单纯形变为由超立方体组成的新网格结构,每个超立方体由一定数量的单纯形组成:
上图中红色网格显示了我们前面讨论过的单态网格。它们由一些等边三角形组成(注意这些等边三角形是沿着空间的对角线排列的)坐标倾斜后,它们变成后面的黑色网格。这些网格由正方形组成,每个正方形由从前面两个等边三角形变形的三角形组成。在一维空间中,将单纯形网格转换为新网格的公式如下:
x'=x+(x+y+)...)⋅K1
y'=y+(x+y+)...)⋅K1
,其中k1 = n+1 √ 1n
在二维空间中,取n为2在这个变换之后,我们可以根据前面的方法判断这个点所在的超立方体,它是二维的正方形。
原则1。坐标偏转:输入点坐标的坐标偏转
x'=x+(x+y+)...)⋅K1
y'=y+(x+y+)...)⋅K1
,其中k1 = n+1 √ 1n
2。寻找顶点:舍入偏斜坐标以获得超立方体Xi =底面(x’)
,yi =楼层(y’),...我们也可以得到分数xf = x' Xi,YF = y' yi,...
我们按(xf,yf,...)以确定变形后输入点位于哪个单纯形中。这个单纯形的顶点由从(0,0,...,0)到(1,1,...,1)按顺序排列,总共n!一种可能性
我们可以根据以下过程得到这n+1个顶点:从零坐标(0,0,...,0),找到当前最大的组件,将1添加到组件位置,直到添加完所有组件这个步骤的算法复杂度是排序复杂度0(N2)
例如,对于二维空间,如果xf和YF满足xf,那么相应的三个单纯形坐标是:首先找到(0,0),因为x分量相对较大,下一个坐标是(1,0),然后是y分量,坐标是(1,1);对于三维空间,如果xf,yf,zf满足xf>zf>yf,那么相应的四个单纯形坐标位置是:首先,从(0,0,0)开始,然后将1加到x分量(1,0,0),然后将1加到z分量(1,0,1),最后将1加到y分量(1,1,1)
3。梯度选择:我们得到偏斜超立方体网格上单纯形的每个顶点的伪随机梯度向量
4。变换单纯形网格中的顶点:我们需要首先将单纯形的顶点变回以前由单纯形组成的单纯形网格这一步需要使用第一步公式的反函数得到:
x=x'+(x'+y'+...)⋅K2
y=y'+(x'+y'+...)⋅K2
,其中k2 = 1n+1 √ 1n
5。贡献总和:由此我们可以获得从输入点到这些单形顶点的位移向量这些向量有两个目的,一个是与顶点梯度向量相乘,另一个是获得前面提到的距离值dist,从而获得每个顶点对结果的贡献度:
(R2 )| dist | 2)4×dot(dist,grad)
实施(2D)
r 2取0.5,因为第一次坐标偏转后得到的网格宽度要求为1,所以我们可以推导出变形前单纯形网格中每个单纯形边的边长为23 √,从而单纯形的每个顶点到相对边的距离(即高度)为2√2,其平方为0.5令人惊讶的是,不仅在二维,而且在其他维中,从每个单纯形顶点到相对边/面的距离是0.5
虽然单纯形噪声比柏林噪声更难理解,但由于其更好的效果和更快的速度,它将在许多情况下取代柏林噪声。
和高维噪声并不少见,例如,对于常见的二维噪声纹理,我们可以在二维纹理动画(三维噪声)中额外引入时间分量,用于火焰纹理动画等..
对于常见的3D噪声纹理,通过引入额外的时间成分,可以成为3D云动画等的3D纹理动画(四维噪声)..
当我们需要一个可以无缝循环的动画时(见下面开坪铺的噪声),噪声需要增加另一个维度。
起伏的噪音可平铺噪声是指可以平铺和无缝的噪声,因为很多时候我们希望噪声纹理无缝连接,例如在生成地形时。根据前面提到的方法,直接产生噪声,并且所获得的噪声纹理不能平铺。您可以看到,生成的纹理的左、右、顶和底实际上是不同的。那么,如何生成可平铺的噪声纹理呢?
翻转纹理一个低开销技巧是首先分别翻转X轴、Y轴和XY轴上的噪声纹理,从而获得三个新的噪声纹理并将它们拼接成一个大纹理,此时该纹理是可平铺的和无缝的。
单基底噪声纹理:
一个基本噪声纹理和三个其他生成纹理镶嵌城市的大纹理:
的缺点是这种纹理看起来太对称,会影响美观。
高维圆采样的一种方法是计算2n维的n维可平铺噪声我们以二维噪声为例。如果我们想要获得二维无缝柏林噪声,我们需要使用四维噪声算法来生成它。
的思想是因为我们希望每个维度是无缝的,也就是说,当维度的值从0变为1时,0和1之间的比较是平滑的,这让我们想起了“圆”。围绕圆的一个圆是尺寸的采样过程,从而确保无缝
因此,对于二维噪声中的X轴,我们将在四维空间中的xz平面上的圆上采样,而二维噪声中的Y轴将在四维空间中的yw平面上的圆上采样。这个转换过程非常简单,我们只需要使用三角函数sin和cos将二维采样坐标转换为单位圆。同样,三维空间是相似的,我们将在六维空间中计算。该方法不仅适用于柏林噪声,也适用于沃利噪声。
2D可平铺单纯形噪声在1统一维基百科:
中的实现
其中xyOffset指的是四维空间中平面上的偏移,即单位圆以xyOffset为中心
这种方法的缺点是计算量大大增加,一般噪声的复杂度为O (2N)(单纯形噪声除外,O(n2))呈指数级增加,因此更适合于预先计算,例如以编程方式生成噪声纹理。
< p >在关于游戏开发中的可平铺噪声的讨论中,许多人分享了他们自己的创造性方法(非常有趣):
https://game dev . stackexchange . com/questions/23625/how-do-you-generate-tilable-Perlin-noise
分形噪声当我们使用基于晶格的噪声时,主要有两个参数可以调整:
(1)频率:晶格的边长越长(即采样间隔,例如频率越高,单位面积(尤其是二维)中晶格的数量越多),噪声纹理就显得“越密集”。)
(2)振幅:返回值的振幅范围为
在地形生成过程中,地形可能会有大的连续高耸的山脉、丘陵和凹坑、较小的岩石甚至较小的卵石。为了模拟这种自然噪声特性,我们可以使用不同的参数多次计算柏林噪声,然后将结果相加。
叠加不同频率和振幅参数下的柏林噪声结果,我们可以得到以下结果:
显然,这样的噪音结果更有说服力。以上六组噪声被称为不同的八度噪声。随着倍频的增加,噪声对最终叠加噪声的影响变小。
我们应该选择什么频率和振幅来分别计算噪声?这可以由持久性参数决定雨果·埃利亚斯对持久性的定义如下:
frequency=2^i
amplitude=persistence^i
简单地说,对于一维噪声,合适的组合是噪声(x)+12噪声(2x)+14噪声(4x)+...
2D噪声是噪声(x,y)+12噪声(2x,2y)+14噪声(4x,4y)+....
公式:∑I = 0噪声(2点)2i
上述公式1的值取决于所需倍频组的数量另外,关于为什么最好将频率乘以1,2,4,8...因为这种频率叠加接近模拟自然自相似过程的
(维基可以检查自相似性)
当然,增加倍频组的数量将线性增加代码执行时间。游戏运行时,使用噪声算法。最好不要使用多个倍频组(例如,当你想以60fps模拟火焰效果时,最好不要这样做)但是,在预处理数据时,非常适合使用多组倍频叠加来模拟更自然的噪声(例如,预先生成游戏地形等)。)