Unity教程——Simplex噪声
来源:互联网 发布:linux nfs配置 编辑:程序博客网 时间:2024/06/06 07:03
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#] 纯文本查看 复制代码
1
2
3
NoiseSample sample =
new
NoiseSample();
sample.value = h * f3;
sample.derivative.x = -6f * h * x * f2;
return
sample;
当然,我们仍然需要调整的频率,所以做这个事情,然后返回的最终结果。事实上,这样做的二维和3D的事情一样,所以我们不会忘记。
[C#] 纯文本查看 复制代码
sample.derivative *= frequency;
对于两个维度,我们可以使用完全相同的方法。导数组件之间的唯一区别是乘法的组件。由于公式的其余部分是相同的,我们不妨计算一次。
[C#] 纯文本查看 复制代码
1
2
3
4
float
h = hash[hash[ix & hashMask] + iy & hashMask];
float
h6f2 = -6f * h * f2;
sample.value = h * f3;
sample.derivative.x = h6f2 * x;
sample.derivative.y = h6f2 * y;
这就像三个维度一样简单。
[C#] 纯文本查看 复制代码
1
2
3
4
5
float
h = hash[hash[hash[ix & hashMask] + iy & hashMask] + iz & hashMask];
float
h6f2 = -6f * h * f2;
sample.value = h * f3;
sample.derivative.x = h6f2 * x;
sample.derivative.y = h6f2 * y;
sample.derivative.z = h6f2 * z;
梯度噪声
是时候处理Gradient Simplex噪声,我们命名Simplex噪声。因此,我们添加了一个新的噪声类型。
[C#] 纯文本查看 复制代码
1
2
3
4
5
public
enum
NoiseMethodType {
Value,
Perlin,
SimplexValue,
Simplex
}
当然,我们调整方法。
[C#] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
public
static
NoiseMethod[] simplexMethods = {
Simplex1D,
Simplex2D,
Simplex3D
};
public
static
NoiseMethod[][] methods = {
valueMethods,
perlinMethods,
simplexValueMethods,
simplexMethods
};
现在复制和重命名Simplex Value方法。确保你有他们自己的部分方法调用。我只显示了一维的情况下的变化。
[C#] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private
static
NoiseSample Simplex1DPart (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;
sample.derivative.x = -6f * h * x * f2;
return
sample;
}
public
static
NoiseSample Simplex1D (Vector3 point,
float
frequency) {
point *= frequency;
int
ix = Mathf.FloorToInt(point.x);
NoiseSample sample = Simplex1DPart(point, ix);
sample += Simplex1DPart(point, ix + 1);
sample.derivative *= frequency;
return
sample * (2f / hashMask) - 1f;
}
1D
继续与一维,我们现在必须检索一个一维的梯度,而不是只是的散列值。与Perlin噪声,我们计算梯度值的梯度向量的点积和从角落里我们的样本点的向量。对于一维,这是一个简单的乘法。
再一次,正如Perlin噪声,梯度现在已经包括衰减乘以梯度矢量
[C#] 纯文本查看 复制代码
1
2
3
4
5
float
g = gradients1D[hash[ix & hashMask] & gradientsMask1D];
float
v = g * x;
NoiseSample sample =
new
NoiseSample();
sample.value = v * f3;
sample.derivative.x = g * f3 - 6f * v * x * f2;
return
sample;
现在我们必须确定噪声的最大值。像Perlin的声音,并达到最大值一半时沿线段两端的梯度指向对方。这意味着最大的 2x(1 - x2)3, x = ½,这是27 / 64。因此,我们必须用这个值来划分最终的结果,这意味着乘以64 / 27。
[C#] 纯文本查看 复制代码
return
sample * (64f / 27f);
2D
它是相同的对于二维,得到的梯度向量,计算点的数据,包括他们的值和导数。
[C#] 纯文本查看 复制代码
1
2
3
4
5
Vector2 g = gradients2D[hash[hash[ix & hashMask] + iy & hashMask] & gradientsMask2D];
float
v = Dot(g, x, y);
float
v6f2 = -6f * v * f2;
sample.value = v * f3;
sample.derivative.x = g.x * f3 + v6f2 * x;
sample.derivative.y = g.y * f3 + v6f2 * y;
问题是现在哪里是最大值,在边缘还是中心?两个都计算一下
记住,边缘的长度是√⅔或√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#] 纯文本查看 复制代码
private
static
float
simplexScale2D = 2916f * sqr2 / 125f;
现在,我们需要最后的因子,我们就完成了。
[C#] 纯文本查看 复制代码
return
sample * simplexScale2D;
一个额外的问题是,我们是否实际上涵盖了整个- 1 - 1范围,因为我们没有考虑我们只使用八个梯度向量。事实证明,我们的45度旋转梯度向量产生的最大值,可以得到非常接近- 1和1。所以是的,我们有效地覆盖了整个范围。
3D
这也应该在三维中能行
[C#] 纯文本查看 复制代码
1
2
3
4
5
6
7
Vector3 g = gradients3D[hash[hash[hash[ix & hashMask] + iy & hashMask] + iz & hashMask] &
gradientsMask3D];
float
v = Dot(g, x, y, z);
float
v6f2 = -6f * v * f2;
sample.value = v * f3;
sample.derivative.x = g.x * f3 + v6f2 * x;
sample.derivative.y = g.y * f3 + v6f2 * y;
sample.derivative.z = g.z * f3 + v6f2 * z;
为了获得最大的值,在我们扭曲的四面体短边中间是好的。我们显然不会发现在更长的边缘上的最大值。在一个规则的三角形的情况下,中间和中间的边缘之间的差异是非常小的。把它变成一个直角三角形,只增加了两个角之间的距离,所以面中部也在外面。对四面体的中心的距离是更大的,所以我们也可以忽略它。
最短边长度是√3 / 2,我们得到2x(½- X2)3 x =√3 / 4,最终被125√3 / 8192。倒数可以写为8192√3 / 375,所以这是我们的尺度因子。
事实上,因为我们的三维梯度阵列包含√2向量的长度必须通过我们的分母补偿。
[C#] 纯文本查看 复制代码
private
static
float
simplexScale3D = 8192f * Mathf.Sqrt(3f) / (sqr2 * 375f);
在做了乘法以后,我们可以检查出噪音。
[C#] 纯文本查看 复制代码
return
sample * simplexScale3D;
它看起来很好,但事实证明,我们目前的设置的梯度向量不填充整个范围。最大振幅似乎是大约0.85,而不是1。我们怎么能解决这个问题?
我们知道,所有的四面体有一个自己的短边与主对角线对齐。因此,如果我们包括沿主对角线的两个相反的向量,我们可以覆盖整个- 1 - 1范围。
梯度阵列我们目前使用的是由Ken Perlin设计的。它包含指向多维数据集的十二个边缘中间的向量。其中的四个被复制,以增加数组的大小到16。
要包括主对角线,我们必须向多维数据集的两个角点添加向量。为了保持对称,我们应该添加所有的八个角矢量。十二个边加八个角给我们二十个向量,这不是两个因素。然而,如果我们包括两次边缘,我们最终有32个向量。要保持他们所有的相同的长度,我们必须使他们标准化。
[C#] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private
static
Vector3[] simplexGradients3D = {
new
Vector3( 1f, 1f, 0f).normalized,
new
Vector3(-1f, 1f, 0f).normalized,
new
Vector3( 1f,-1f, 0f).normalized,
new
Vector3(-1f,-1f, 0f).normalized,
new
Vector3( 1f, 0f, 1f).normalized,
new
Vector3(-1f, 0f, 1f).normalized,
new
Vector3( 1f, 0f,-1f).normalized,
new
Vector3(-1f, 0f,-1f).normalized,
new
Vector3( 0f, 1f, 1f).normalized,
new
Vector3( 0f,-1f, 1f).normalized,
new
Vector3( 0f, 1f,-1f).normalized,
new
Vector3( 0f,-1f,-1f).normalized,
new
Vector3( 1f, 1f, 0f).normalized,
new
Vector3(-1f, 1f, 0f).normalized,
new
Vector3( 1f,-1f, 0f).normalized,
new
Vector3(-1f,-1f, 0f).normalized,
new
Vector3( 1f, 0f, 1f).normalized,
new
Vector3(-1f, 0f, 1f).normalized,
new
Vector3( 1f, 0f,-1f).normalized,
new
Vector3(-1f, 0f,-1f).normalized,
new
Vector3( 0f, 1f, 1f).normalized,
new
Vector3( 0f,-1f, 1f).normalized,
new
Vector3( 0f, 1f,-1f).normalized,
new
Vector3( 0f,-1f,-1f).normalized,
new
Vector3( 1f, 1f, 1f).normalized,
new
Vector3(-1f, 1f, 1f).normalized,
new
Vector3( 1f,-1f, 1f).normalized,
new
Vector3(-1f,-1f, 1f).normalized,
new
Vector3( 1f, 1f,-1f).normalized,
new
Vector3(-1f, 1f,-1f).normalized,
new
Vector3( 1f,-1f,-1f).normalized,
new
Vector3(-1f,-1f,-1f).normalized
};
private
const
int
simplexGradientsMask3D = 31;
现在我们可以使用这些新的梯度,而不是那些由Perlin噪声。
[C#] 纯文本查看 复制代码
1
Vector3 g = simplexGradients3D[hash[hash[hash[ix & hashMask] + iy & hashMask] + iz & hashMask] &
simplexGradientsMask3D];
我们不再需要除以√2。
[C#] 纯文本查看 复制代码
private
static
float
simplexScale3D = 8192f * Mathf.Sqrt(3f) / 375f;
有了这个,我们的噪音覆盖了整个范围。
这就是怎样做Simplex噪声
0 0
- Unity教程——Simplex噪声
- simplex
- unity shader:使用噪声
- 【拜小白opencv】26-图像噪声1——椒盐噪声
- 环境监测——扬尘噪声
- 计算机视觉——噪声
- OpenCV—图像椒盐噪声生成器
- Unity中使用柏林噪声生成地图
- 【Get深一度】高斯白噪声之——热噪声(thermal noise)
- 【Get深一度】高斯白噪声之——散粒噪声(shot noise )
- 基于matlab的图像处理——高斯噪声&均值滤波,椒盐噪声&中值滤波
- 【拜小白opencv】27-图像噪声2——高斯噪声
- 图像去噪——椒盐噪声与高斯噪声
- 图像增强—空域滤波,椒盐噪声/高斯噪声 均值滤波器/中值滤波器
- 噪声
- 噪声
- ArcGIS教程:影像去条带噪声
- Unity+Android交互教程——让手机"动"起来
- Java_类_多态
- 初识Unity 3D——基本脚本代码
- Linux Centos安装eclipse并创建桌面快捷方式
- Ubuntu16.04清理boot分区
- App性能优化
- Unity教程——Simplex噪声
- 创建一个线程
- java计算目录及文件个数,文件夹大小
- C练习(三)
- python定时任务windows服务
- Chapter 1 用Vim配置Python IDE
- MyEclipse10安装PropertiesEditor的插件
- HITS算法
- leetcode_[python/C++逐步深入] 43. Multiply Strings_(大数乘法分析)