C/C++编程笔记:游戏开发中噪声算法的详细解释,游戏编程中的常用技术

噪音 噪声是游戏编程中的常见技术,广泛应用于地形生成、图形等方面。

噪音单位

那么为什么引入噪音的概念呢?在程序中,我们经常使用最简单的rand()来直接生成随机值,但是问题是生成的随机值太“随机”,并且获得的值总是不均匀的。下图使用随机值作为像素点的黑白度:

噪音单位

和使用噪声,我们得到的值看起来是随机的,但很柔和,而且图表看起来也更自然和舒适:

噪音单位

根据维基,噪音已经有很多种类型:256以上

噪音单位

本文主要描述三种常见噪声:值噪声、柏林噪声和单纯形噪声

随机性

随机性是噪音的基础,不用说

大麻

在《我的世界》,因为世界是无限的,它只以“组块”(16×16×256格)为单位装载玩家附近的方块换句话说,当玩家移动的时候,它会卸载远块,然后加载近块。

的一个问题是当玩家离开一个街区时,他进入第二个街区,然后回到第一个街区。此时,玩家期望看到的第一块与他之前看到的一致。例如,当输入1时获得0.3,当输入2时获得0.7,当再次输入1时期望获得0.3

因此,噪声的一个重要属性是散列(hashing)

虽然输入值被用作srand()的参数来设置rand()的种子,但是实现散列效果也是可行的

但是,最好花些时间编写一个自己的散列函数,使其易于使用,并且不破坏程序中其他地方使用的rand()的效果。

噪音单位

< p > 11 of p>hash11表示输入一维坐标和输出一维值相似的hash22表示输入的二维坐标和输出的二维值

要了解更多关于随机散列函数的实现,请参考以下两个着色器代码:

https://www . shader toy . com/view/4sc 3z 2

https://www . shader toy . com/view/4Djsrw

平滑度(连续性)

对于随机生成的地形,如果您简单地使用随机和散列组合,

很容易得到下图(以一维地图为例,x轴是位置,y轴是地形高度):

噪音单位

很容易看到的问题是,由于随机无序,地形非常不平坦,这不是自然地形。

我们期望的地形不仅要随机而且要平滑,这样才能显得自然,如下图所示:

噪音单位

值噪声

数值噪声是最简单的一种噪声。它的主要思想是定义几个顶点,每个顶点包含一个随机值。这些顶点将根据它们自己的随机值影响周围的坐标,并且顶点越靠近,越容易受到顶点的影响。当需要某个坐标的输出值时,需要叠加该坐标附近每个顶点产生的影响值,得到一个总值并输出

原则

1年。首先定义一个格结构,每个格顶点都有一个伪随机值对于二维值噪声,网格结构是平面网格(通常为正方形),三维是三维网格(通常为立方体)

2年。输入一个点(二维是二维坐标,三维是三维坐标,N-D是N坐标)。我们找到与其相邻的格顶点(二维4个,三维8个,二维2n个),并得到这些顶点的伪随机值

3年。使用缓和曲线来计算这些伪随机值的权重和原始柏林噪声实现中使用的松弛曲线是S (t) = 3T 22T 3。在2002年的论文中,Perlin被改进为S (t) = 6T 515T 4+10T 3

噪音单位

< p >如果直接使用线性插值,则其在晶格顶点(即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)

噪音单位

柏林噪音

谈到噪音,最著名和最常见的是柏林噪音,它的名字来自它的创始人肯·柏林。

在理解了上述值噪声之后,让我们来看一下柏林噪声的主要概念:

定义了几个顶点,每个顶点包含一个随机梯度向量。根据它们的梯度向量,这些顶点对周围的坐标有潜在的能量影响。沿着顶点的梯度方向越高,势能越高。当需要某个坐标的输出值时,需要叠加该坐标附近每个顶点产生的势能,得到总势能并输出。

噪音单位

我们给顶点一个随机散列函数,并输入一个坐标得到一个随机向量,它满足上述随机性和散列。

此外,由于势能沿梯度方向逐渐变化,很容易获得光滑度。

原则

与值噪声一样,是基于晶格的噪声,需要三个步骤:

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)

噪音单位

是另一种更快的实现方式,其与标准实现方式的不同之处在于晶体顶点从多个梯度向量中随机选择一个向量,而不是生成随机向量,该随机向量可以在预先计算梯度值时计算每个项目的系数所以我们只需要像这样重写梯度函数:

噪音单位

此处的示例提供了4个可选的随机向量。事实上,这个数字相对较小。如果您想要更多样化的效果,建议在实现中提供更多可选的随机向量。

单一噪声

单纯形噪声也是基于点阵的梯度噪声。它和柏林噪声的唯一区别是它的晶格不是正方形(2D是正方形,三维是立方体,我们在高纬度称它们为超立方体和超立方体),而是单形的

可以被认为是在n维空间中选择最简单和最紧凑的多边形来平铺整个n维空间,如果它被用来解释单形的话我们可以很容易地认为一维空间中的单形是等长的线段,并且整个一维空间可以通过连接这些线段的末端来铺设。在二维空间中,单形是三角形,我们可以连接等腰三角形来覆盖整个平面。三维空间中的单纯形是四面体。高维空间的简单性也存在

噪音单位

总结了在n维空间中,超立方体的顶点数是2n,而单纯形的顶点数是n+1,这使得在计算梯度噪声时可以大大减少要计算的顶点权重的数量。

噪音单位

的一个潜在问题是如何找到输入点所在的单纯形

在计算柏林噪声时,很容易判断输入点所在的正方形。我们只需要向下舍入输入点来找到它。

对于单形,我们需要倾斜单形的坐标,以将平铺空间的单形改变为新的网格结构,该网格结构由超立方体组成,并且每个超立方体由一定数量的单形组成:

噪音单位

我们前面谈到的单态网格在上图的红色网格中显示。它们由一些等边三角形组成(注意这些等边三角形是沿着空间的对角线排列的)坐标倾斜后,它们变成了背后的黑色网格。这些网格是由正方形组成的,每个正方形是由前面两个等边三角形变形的三角形组成的。在N维空间中将单形网格转换成新网格的公式如下:

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,直到所有组件都被添加这一步的算法复杂度是排序复杂度O(n2)

< p >例如,对于二维空间,如果xf和YF满足xf>yf,那么相应的三个单纯形坐标是:首先找到(0,0),因为x分量相对较大,下一个坐标是(1,0),然后是y分量,并且坐标是(1,1);对于三维空间,如果xf,yf,zf满足xf>zf>yf,那么相应的四个单形坐标位置是:首先,从(0,0,0)开始,然后对x分量(1,0,0)加1,然后对z分量(1,0,1)加1,最后对y分量(1,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纹理动画(4-D噪点)..

当我们需要一个可以无缝循环的动画时(参见下面的“柯坪铺”中的噪音),噪音需要增加一个维度。

可倾斜噪声

Tilable噪波是指可以平铺且无缝的噪波,因为很多时候我们希望噪波纹理无缝连接,例如在生成地形时。根据前面提到的方法,噪声是直接产生的,并且获得的噪声纹理不能被平铺。您可以看到生成的纹理的左、右、上、下实际上是不同的。那么,如何生成一个可平铺的噪声纹理呢?

翻转纹理

一个低开销的技巧是首先在X轴、Y轴和XY轴上分别翻转一个噪声纹理,从而获得三个新的噪声纹理,并将它们拼接成一个大纹理,此时该纹理是可平铺的和无缝的。

一个基本噪声纹理:

噪音单位

一个基本噪声纹理和三个其他生成纹理镶嵌城市的大纹理:

噪音单位

的缺点是这种纹理看起来太对称,影响美观。

高维圆采样

的一种方法是计算2n维中的n维可平铺噪声我们以二维噪声为例。如果我们想要获得二维无缝柏林噪声,我们需要使用四维噪声算法来生成它。

的想法是因为我们希望每个维度都是无缝的,也就是说,当维度的值从0变为1时,0和1之间的比较是平滑的,这让我们想起了“圆”。一个圆围绕一个圆是尺寸的采样过程,从而确保无缝

因此,对于二维噪声中的X轴,我们将在四维空间中的xz平面上的圆上采样,而二维噪声中的Y轴将在四维空间中的yw平面上的圆上采样。这个转换过程非常简单,我们只需要使用三角函数sin和cos将二维采样坐标转换成单位圆。同样,三维空间也是相似的,我们将在六维空间中计算。这种方法不仅适用于柏林噪声,也适用于沃利噪声。

2D可平铺单工噪声在

1维基:

中的实现

22-

其中,xyOffset指四维空间中平面上的偏移,即单位圆以xyOffset为中心

这种方法的缺点是计算量大大增加,而一般噪声的复杂度是O (2N)(除了单纯形噪声,O(n2))是指数增加的,所以它更适合于预计算,例如以编程方式生成噪声纹理。

< p >在关于游戏开发中可替换噪音的讨论中,许多人分享了他们自己的创意方法(非常有趣):

https://game dev . stackexchange . com/questions/23625/how-do-you-generate-tile ble-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

上述公式I的值取决于所需倍频基团的数量此外,为什么最好将频率乘以1,2,4,8...因为这种频率叠加更接近模拟自然自相似过程的

( wiki可以检查自相似性)

噪音单位

当然,增加倍频组的数量将线性增加代码执行时间。当游戏运行时,使用噪音算法。最好不要使用多个倍频组(例如,当您想要以60fps模拟火焰效果时,最好不要这样做)然而,在预处理数据时,非常适合使用多组倍频叠加来模拟更多的自然噪声(例如,预先生成游戏地形等)。)

大家都在看

相关专题