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#] 纯文本查看 复制代码
?
 
returnsample * (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
publicenumNoiseMethodType {
        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
publicstaticNoiseMethod[] simplexValueMethods = {
                SimplexValue1D,
                SimplexValue2D,
                SimplexValue3D
        };
 
        publicstaticNoiseMethod[][] methods = {
                valueMethods,
                perlinMethods,
                simplexValueMethods
        };
         
        publicstaticNoiseSample SimplexValue1D (Vector3 point, floatfrequency) {
                returnnewNoiseSample();
        }
 
        publicstaticNoiseSample SimplexValue2D (Vector3 point, floatfrequency) {
                returnnewNoiseSample();
        }
 
        publicstaticNoiseSample SimplexValue3D (Vector3 point, floatfrequency) {
                returnnewNoiseSample();
        }



1D


我们开始考虑只是一个维度,它保持简单。在这种情况下,单一和超立方体网格是相等的,所以我们只抓住样本点的整数部分,以得到它的左边的格点。
[C#] 纯文本查看 复制代码
?
 
1
2
3
4
publicstaticNoiseSample SimplexValue1D (Vector3 point, floatfrequency) {
                point *= frequency;
                intix = Mathf.FloorToInt(point.x);
                returnnewNoiseSample();
        }


让我们看左边的线性片段。它应该开始是一个值,之后下降到零,当我们达到右边时,就像有规律的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
publicstaticNoiseSample SimplexValue1D (Vector3 point, floatfrequency) {
                point *= frequency;
                intix = Mathf.FloorToInt(point.x);
                floatx = point.x - ix;
                floatf = 1f - x * x;
                floatf2 = f * f;
                floatf3 = f * f2;
                NoiseSample sample = newNoiseSample();
                sample.value = f3;
                returnsample * 2f - 1f;
        }


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


 
正如你所看到的,结果是在线段的终点处的全部强度和他们之间最薄弱的一半。这种差异变得更加明显,在更高的维度。

所有剩下的将它变成Value噪声是用哈希值。
[C#] 纯文本查看 复制代码
?
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
privatestaticNoiseSample SimplexValue1DPart (Vector3 point, intix) {
                floatx = point.x - ix;
                floatf = 1f - x * x;
                floatf2 = f * f;
                floatf3 = f * f2;
                floath = hash[ix & hashMask];
                NoiseSample sample = newNoiseSample();
                sample.value = h * f3;
                returnsample;
        }
 
        publicstaticNoiseSample SimplexValue1D (Vector3 point, floatfrequency) {
                point *= frequency;
                intix = Mathf.FloorToInt(point.x);
                NoiseSample sample = SimplexValue1DPart(point, ix);
                sample += SimplexValue1DPart(point, ix + 1);
                returnsample * (2f / hashMask) - 1f;
        }


 
最终的结果看起来很像普通插值噪声,但衰减函数产生更多条带。


导数


那么,导数呢?幸运的是,他们是相当简单的。衰减函数的导数(1 - x2)3 是 -6x(1 - x2)2。因子的哈希值,你有了一维导数。
[C#] 纯文本查看 复制代码
?
 
1
2
3
NoiseSample sample = newNoiseSample();
        sample.value = h * f3;
        sample.derivative.x = -6f * h * x * f2;
        returnsample;

当然,我们仍然需要调整的频率,所以做这个事情,然后返回的最终结果。事实上,这样做的二维和3D的事情一样,所以我们不会忘记。
[C#] 纯文本查看 复制代码
?
 
sample.derivative *= frequency;

 
对于两个维度,我们可以使用完全相同的方法。导数组件之间的唯一区别是乘法的组件。由于公式的其余部分是相同的,我们不妨计算一次。
[C#] 纯文本查看 复制代码
?
 
1
2
3
4
floath = hash[hash[ix & hashMask] + iy & hashMask];
            floath6f2 = -6f * h * f2;
            sample.value = h * f3;
            sample.derivative.x = h6f2 * x;
            sample.derivative.y = h6f2 * y;


这就像三个维度一样简单。
[C#] 纯文本查看 复制代码
?
 
1
2
3
4
5
floath = hash[hash[hash[ix & hashMask] + iy & hashMask] + iz & hashMask];
            floath6f2 = -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
publicenumNoiseMethodType {
    Value,
    Perlin,
    SimplexValue,
    Simplex
}


当然,我们调整方法。
[C#] 纯文本查看 复制代码
?
 
01
02
03
04
05
06
07
08
09
10
11
publicstaticNoiseMethod[] simplexMethods = {
        Simplex1D,
        Simplex2D,
        Simplex3D
    };
 
    publicstaticNoiseMethod[][] 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
privatestaticNoiseSample Simplex1DPart (Vector3 point, intix) {
        floatx = point.x - ix;
        floatf = 1f - x * x;
        floatf2 = f * f;
        floatf3 = f * f2;
        floath = hash[ix & hashMask];
        NoiseSample sample = newNoiseSample();
        sample.value = h * f3;
        sample.derivative.x = -6f * h * x * f2;
        returnsample;
    }
     
    publicstaticNoiseSample Simplex1D (Vector3 point, floatfrequency) {
        point *= frequency;
        intix = Mathf.FloorToInt(point.x);
        NoiseSample sample = Simplex1DPart(point, ix);
        sample += Simplex1DPart(point, ix + 1);
        sample.derivative *= frequency;
        returnsample * (2f / hashMask) - 1f;
    }



1D

继续与一维,我们现在必须检索一个一维的梯度,而不是只是的散列值。与Perlin噪声,我们计算梯度值的梯度向量的点积和从角落里我们的样本点的向量。对于一维,这是一个简单的乘法。
再一次,正如Perlin噪声,梯度现在已经包括衰减乘以梯度矢量
[C#] 纯文本查看 复制代码
?
 
1
2
3
4
5
floatg = gradients1D[hash[ix & hashMask] & gradientsMask1D];
        floatv = g * x;
        NoiseSample sample = newNoiseSample();
        sample.value = v * f3;
        sample.derivative.x = g * f3 - 6f * v * x * f2;
        returnsample;


现在我们必须确定噪声的最大值。像Perlin的声音,并达到最大值一半时沿线段两端的梯度指向对方。这意味着最大的 2x(1 - x2)3, x = ½,这是27 / 64。因此,我们必须用这个值来划分最终的结果,这意味着乘以64 / 27。
[C#] 纯文本查看 复制代码
?
 
returnsample * (64f / 27f);


  


 


2D

它是相同的对于二维,得到的梯度向量,计算点的数据,包括他们的值和导数。
[C#] 纯文本查看 复制代码
?
 
1
2
3
4
5
Vector2 g = gradients2D[hash[hash[ix & hashMask] + iy & hashMask] & gradientsMask2D];
            floatv = Dot(g, x, y);
            floatv6f2 = -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#] 纯文本查看 复制代码
?
 
privatestaticfloat simplexScale2D = 2916f * sqr2 / 125f;


现在,我们需要最后的因子,我们就完成了。
[C#] 纯文本查看 复制代码
?
 
returnsample * 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];
            floatv = Dot(g, x, y, z);
            floatv6f2 = -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#] 纯文本查看 复制代码
?
 
privatestaticfloat simplexScale3D = 8192f * Mathf.Sqrt(3f) / (sqr2 * 375f);


在做了乘法以后,我们可以检查出噪音。
[C#] 纯文本查看 复制代码
?
 
returnsample * 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
privatestaticVector3[] simplexGradients3D = {
        newVector3( 1f, 1f, 0f).normalized,
        newVector3(-1f, 1f, 0f).normalized,
        newVector3( 1f,-1f, 0f).normalized,
        newVector3(-1f,-1f, 0f).normalized,
        newVector3( 1f, 0f, 1f).normalized,
        newVector3(-1f, 0f, 1f).normalized,
        newVector3( 1f, 0f,-1f).normalized,
        newVector3(-1f, 0f,-1f).normalized,
        newVector3( 0f, 1f, 1f).normalized,
        newVector3( 0f,-1f, 1f).normalized,
        newVector3( 0f, 1f,-1f).normalized,
        newVector3( 0f,-1f,-1f).normalized,
         
        newVector3( 1f, 1f, 0f).normalized,
        newVector3(-1f, 1f, 0f).normalized,
        newVector3( 1f,-1f, 0f).normalized,
        newVector3(-1f,-1f, 0f).normalized,
        newVector3( 1f, 0f, 1f).normalized,
        newVector3(-1f, 0f, 1f).normalized,
        newVector3( 1f, 0f,-1f).normalized,
        newVector3(-1f, 0f,-1f).normalized,
        newVector3( 0f, 1f, 1f).normalized,
        newVector3( 0f,-1f, 1f).normalized,
        newVector3( 0f, 1f,-1f).normalized,
        newVector3( 0f,-1f,-1f).normalized,
         
        newVector3( 1f, 1f, 1f).normalized,
        newVector3(-1f, 1f, 1f).normalized,
        newVector3( 1f,-1f, 1f).normalized,
        newVector3(-1f,-1f, 1f).normalized,
        newVector3( 1f, 1f,-1f).normalized,
        newVector3(-1f, 1f,-1f).normalized,
        newVector3( 1f,-1f,-1f).normalized,
        newVector3(-1f,-1f,-1f).normalized
    };
     
    privateconstint simplexGradientsMask3D = 31;


现在我们可以使用这些新的梯度,而不是那些由Perlin噪声。
[C#] 纯文本查看 复制代码
?
 
1
Vector3 g = simplexGradients3D[hash[hash[hash[ix & hashMask] + iy & hashMask] + iz & hashMask] &
                simplexGradientsMask3D];

我们不再需要除以√2。
[C#] 纯文本查看 复制代码
?
 
privatestaticfloat simplexScale3D = 8192f * Mathf.Sqrt(3f) / 375f;


有了这个,我们的噪音覆盖了整个范围。
   

    



  

 


这就是怎样做Simplex噪声

03-03-z-10.png (14.86 KB, 下载次数: 0)

03-03-z-10.png



0 0
原创粉丝点击