Simplex噪声,保持简单

在本教程中,您将创建Value 和 Perlin噪声的替代品,被称为Simplex噪声。你将学会使用基于距离衰减函数转换一个正方形和一个三角形网格;
立方体和四面体网格之间的转换;
计算Simplex Value噪声,三维,用导数。
计算Simplex Gradient噪声,三维,用导数。
本教程是Noise 和 Noise Derivatives的后边的教程。我想你还是先看那些,因为我们会使用其中代码。
本教程是 4.5.2。它可能不会为旧版本工作。
简化噪声
在Noise 和 Noise Derivatives的教程我们使用伪随机噪声的彩色纹理,变形,表面平整,粒子流和动画。我们创建了两种形式的方格噪声,插值的网格线的交点之间的。我们选择使用一个超立方体网格,一维的网格,二维的正方形网格,三维的立方体网格,因为它是一个明显的和简单的分区空间的方法。
我们创建的Value噪声,通过定义每个方格点的散列值,并顺利地内插它们之间。我们通过插值梯度代替固定的值创建Gradient噪声,这是最经常被称为Perlin噪声。
你可以产生非常好的效果,使用这些噪声类型,但他们有一定的局限性。当他们是基于一个超立方体网格,你将能够检测到方形图案,如果你仔细看一看。此外,当移动一个轴对齐的二维切片通过三维噪声,你会看到一个明显的变化,用噪声替换的立方体边缘和中心。由于三次插值,一个立方体的中心是更加模糊。最后,分析导数工具是很难计算和更高的尺寸得到更快。4D噪声需要精确的网格,这意味着你要每样16点插值。
用Simplex网格
我们不需要使用超立方体网格,我们所需要的是一种方法来划分空间到规则块。理想情况下,我们会使用最小的可能的形状为我们的网格单元,这将是一个单一的。对于一维,这是一个线,所以它没有什么区别。对于二维,它是一个三角形,而不是一个正方形。3D是一个四面体代替立方体。和4D是一个五胞体而不是一个精确的图形。这意味着,只有尺寸考虑1 + N点,而不是为2的n次方。
递减替换插值
我们怎么会有一个一个简单图像角之间的插值?事实上,我们不需要插值,我们可以减少一个角落,根据其从采样点的距离。2D,这将是一个径向衰减功能,应降为零达到三角形侧时。3D将球面衰减,等等。
使用衰减而不是插值的一大优势是,每个点是独立的。他们只是添加在一起,以获得最终的值。这简化了导数工具的计算。
你可以使用这种方法,一个超立方体网格,但径向衰减在四边形不如三角形效果好。

Simplex噪声
用衰减函数的一个单一的网格计算梯度噪声首先是由Ken Perlin提出的作为他之前发明的一种梯度噪声替代。大多数人把这个称为单纯的噪音,所以让我们以相同的名称为我们的基于单一的梯度噪声。我们用Simplex Value噪音来表示从基于超立方体的Value噪声。
我们将在前面的教程的后边,所以开始Noise Derivatives的项目教程。
Simplex Value噪声
让我们再次开始Value噪声,因为它是简单的比梯度噪声。然而,在我们开始记住,我们已经到目前为止,作为一种特殊的情况下的噪声。这是因为它有一个范围为0 - 1,而梯度噪声有一个范围为- 1 - 1。因此,我们应该更新我们的代码,也使单一的值噪声的例外。另外,我们可以做的特殊情况下,完全如果我们改变我们的价值的范围- 1 - 1。让我们这样做,只是因为我们可以。
.
第一, 调整的Value1D,Value2D结果,在噪声Value3D方法。去掉一个。
[C#]
纯文本查看
复制代码
|
return
sample * (2f / hashMask) - 1f;
|
然后移除在TextureCreator, SurfaceCreator, and SurfaceFlow检测对于值噪声。在采取了噪音样品后,我们现在总是取值一半。
[C#]
纯文本查看
复制代码
1
|
NoiseSample sample = …;
sample = sample * 0.5f;
|
为了保持texturecreator在0–1范围内,添加½。
[C#]
纯文本查看
复制代码
|
sample = sample * 0.5f + 0.5f;
|
用这种方法,在噪声脚本的顶部添加我们的新的噪声类型。
[C#]
纯文本查看
复制代码
1
2
3
4
|
public
enum
NoiseMethodType {
Value,
Perlin,
SimplexValue
}
|
现在我们可以添加占位符方法及其阵列的方法,包括它的方法收集。这允许使用在我们可以选择Simplex Value噪声。
[C#]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
static
NoiseMethod[] simplexValueMethods = {
SimplexValue1D,
SimplexValue2D,
SimplexValue3D
};
public
static
NoiseMethod[][] methods = {
valueMethods,
perlinMethods,
simplexValueMethods
};
public
static
NoiseSample SimplexValue1D (Vector3 point,
float
frequency) {
return
new
NoiseSample();
}
public
static
NoiseSample SimplexValue2D (Vector3 point,
float
frequency) {
return
new
NoiseSample();
}
public
static
NoiseSample SimplexValue3D (Vector3 point,
float
frequency) {
return
new
NoiseSample();
}
|
1D
我们开始考虑只是一个维度,它保持简单。在这种情况下,单一和超立方体网格是相等的,所以我们只抓住样本点的整数部分,以得到它的左边的格点。
[C#]
纯文本查看
复制代码
1
2
3
4
|
public
static
NoiseSample SimplexValue1D (Vector3 point,
float
frequency) {
point *= frequency;
int
ix = Mathf.FloorToInt(point.x);
return
new
NoiseSample();
}
|
让我们看左边的线性片段。它应该开始是一个值,之后下降到零,当我们达到右边时,就像有规律的Value噪声。再一次,我们要确保衰减函数下降到零和一阶导数和二阶导数。不同的是,导数不需要在开始时为零,他们只是需要连续。
除了这些考虑,我们希望一个函数,可以工作在任何维度,基于网格的交叉点的距离。虽然我们可以计算实际的距离,这将需要执行一个更高的维度的平方根操作,我们避免这样做。因此,我们用距离平方,不是吗?如果是这样的话,我们会自动得到一个径向对称衰减。
最简单的衰减会1-X2。一阶和二阶导师是-2x和-2,而不是零当X是一个一阶函数。如果我们平方的函数的话怎么办?然后我们得到(1- x2)2,仍降到零的时候。它的导数是 -4x(1 - x2),并降为零,-4(1 - x2) + 8x2不是。我们试试立方版,(1 - x2)3。一个导数是-6x(1 - x2)2 and -6(1 - x2)2 + 24x2(1 - x2)都降到零。

所以(1 x2)3是我们的衰减函数。让我们一步一步来计算它,并将它可视化。也别忘了,我们必须将最终结果转换为- 1 - 1范围。
[C#]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
|
public
static
NoiseSample SimplexValue1D (Vector3 point,
float
frequency) {
point *= frequency;
int
ix = Mathf.FloorToInt(point.x);
float
x = point.x - ix;
float
f = 1f - x * x;
float
f2 = f * f;
float
f3 = f * f2;
NoiseSample sample =
new
NoiseSample();
sample.value = f3;
return
sample * 2f - 1f;
}
|

现在的想法是,我们可以计算这两个结束点,简单地相加结果。因此,让我们把代码来计算一个单独的方法的结果的一部分,并用两次,所以我们得到了两端。
[C#]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
private
static
NoiseSample SimplexValue1DPart (Vector3 point,
int
ix) {
float
x = point.x - ix;
float
f = 1f - x * x;
float
f2 = f * f;
float
f3 = f * f2;
NoiseSample sample =
new
NoiseSample();
sample.value = f3;
return
sample;
}
public
static
NoiseSample SimplexValue1D (Vector3 point,
float
frequency) {
point *= frequency;
int
ix = Mathf.FloorToInt(point.x);
NoiseSample sample = SimplexValue1DPart(point, ix);
sample += SimplexValue1DPart(point, ix + 1);
return
sample * 2f - 1f;
}
|

正如你所看到的,结果是在线段的终点处的全部强度和他们之间最薄弱的一半。这种差异变得更加明显,在更高的维度。
所有剩下的将它变成Value噪声是用哈希值。
[C#]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
private
static
NoiseSample SimplexValue1DPart (Vector3 point,
int
ix) {
float
x = point.x - ix;
float
f = 1f - x * x;
float
f2 = f * f;
float
f3 = f * f2;
float
h = hash[ix & hashMask];
NoiseSample sample =
new
NoiseSample();
sample.value = h * f3;
return
sample;
}
public
static
NoiseSample SimplexValue1D (Vector3 point,
float
frequency) {
point *= frequency;
int
ix = Mathf.FloorToInt(point.x);
NoiseSample sample = SimplexValue1DPart(point, ix);
sample += SimplexValue1DPart(point, ix + 1);
return
sample * (2f / hashMask) - 1f;
}
|

最终的结果看起来很像普通插值噪声,但衰减函数产生更多条带。
导数
那么,导数呢?幸运的是,他们是相当简单的。衰减函数的导数(1 - x2)3 是 -6x(1 - x2)2。因子的哈希值,你有了一维导数。
[C#]
纯文本查看
复制代码
当然,我们仍然需要调整的频率,所以做这个事情,然后返回的最终结果。事实上,这样做的二维和3D的事情一样,所以我们不会忘记。
[C#]
纯文本查看
复制代码
![]()
对于两个维度,我们可以使用完全相同的方法。导数组件之间的唯一区别是乘法的组件。由于公式的其余部分是相同的,我们不妨计算一次。
[C#]
纯文本查看
复制代码
这就像三个维度一样简单。
[C#]
纯文本查看
复制代码
![]() ![]()
梯度噪声
是时候处理Gradient Simplex噪声,我们命名Simplex噪声。因此,我们添加了一个新的噪声类型。
[C#]
纯文本查看
复制代码
当然,我们调整方法。
[C#]
纯文本查看
复制代码
现在复制和重命名Simplex Value方法。确保你有他们自己的部分方法调用。我只显示了一维的情况下的变化。
[C#]
纯文本查看
复制代码
1D
继续与一维,我们现在必须检索一个一维的梯度,而不是只是的散列值。与Perlin噪声,我们计算梯度值的梯度向量的点积和从角落里我们的样本点的向量。对于一维,这是一个简单的乘法。
再一次,正如Perlin噪声,梯度现在已经包括衰减乘以梯度矢量
[C#]
纯文本查看
复制代码
现在我们必须确定噪声的最大值。像Perlin的声音,并达到最大值一半时沿线段两端的梯度指向对方。这意味着最大的 2x(1 - x2)3, x = ½,这是27 / 64。因此,我们必须用这个值来划分最终的结果,这意味着乘以64 / 27。
[C#]
纯文本查看
复制代码
![]() ![]() ![]()
2D
它是相同的对于二维,得到的梯度向量,计算点的数据,包括他们的值和导数。
[C#]
纯文本查看
复制代码
问题是现在哪里是最大值,在边缘还是中心?两个都计算一下
记住,边缘的长度是√⅔或√6 / 3。所以在边缘得到2x(½ - x2)3 , x = √6 / 6,是√6 / 81。
其次,从一个角落一个等边三角形的中心的距离等于它的边长乘以√3 / 3 or √⅓。所以在中心得到3x(½ - x2)3 x =√2 / 3,这给了我们125√2 / 5832。由于这个值是比另一个小的一点,它是我们理论最大值。其乘法逆可以写成2916√2 / 125,所以这是我们最后的大小。
![]()
[C#]
纯文本查看
复制代码
现在,我们需要最后的因子,我们就完成了。
[C#]
纯文本查看
复制代码
![]() ![]() ![]() ![]() ![]() ![]()
一个额外的问题是,我们是否实际上涵盖了整个- 1 - 1范围,因为我们没有考虑我们只使用八个梯度向量。事实证明,我们的45度旋转梯度向量产生的最大值,可以得到非常接近- 1和1。所以是的,我们有效地覆盖了整个范围。
![]()
3D
这也应该在三维中能行
[C#]
纯文本查看
复制代码
为了获得最大的值,在我们扭曲的四面体短边中间是好的。我们显然不会发现在更长的边缘上的最大值。在一个规则的三角形的情况下,中间和中间的边缘之间的差异是非常小的。把它变成一个直角三角形,只增加了两个角之间的距离,所以面中部也在外面。对四面体的中心的距离是更大的,所以我们也可以忽略它。
![]()
最短边长度是√3 / 2,我们得到2x(½- X2)3 x =√3 / 4,最终被125√3 / 8192。倒数可以写为8192√3 / 375,所以这是我们的尺度因子。
事实上,因为我们的三维梯度阵列包含√2向量的长度必须通过我们的分母补偿。
[C#]
纯文本查看
复制代码
在做了乘法以后,我们可以检查出噪音。
[C#]
纯文本查看
复制代码
它看起来很好,但事实证明,我们目前的设置的梯度向量不填充整个范围。最大振幅似乎是大约0.85,而不是1。我们怎么能解决这个问题?
我们知道,所有的四面体有一个自己的短边与主对角线对齐。因此,如果我们包括沿主对角线的两个相反的向量,我们可以覆盖整个- 1 - 1范围。
梯度阵列我们目前使用的是由Ken Perlin设计的。它包含指向多维数据集的十二个边缘中间的向量。其中的四个被复制,以增加数组的大小到16。
要包括主对角线,我们必须向多维数据集的两个角点添加向量。为了保持对称,我们应该添加所有的八个角矢量。十二个边加八个角给我们二十个向量,这不是两个因素。然而,如果我们包括两次边缘,我们最终有32个向量。要保持他们所有的相同的长度,我们必须使他们标准化。
[C#]
纯文本查看
复制代码
现在我们可以使用这些新的梯度,而不是那些由Perlin噪声。
[C#]
纯文本查看
复制代码
我们不再需要除以√2。
[C#]
纯文本查看
复制代码
有了这个,我们的噪音覆盖了整个范围。
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]()
这就是怎样做Simplex噪声
|