Perlin 和 Simplex Noise 的排列和梯度表在实践中如何工作?
How do the permutation and gradient tables of Perlin and Simplex Noise work in practice?
所以我一直在研究 Perlin 和 Simplex 噪声的工作原理,虽然我了解了常规 Perlin 噪声的核心原理,但我对排列和梯度的方式有点困惑table 的工作。
根据我的理解,它们提供了比种子随机数生成器更好的性能,因为它们是 table 预先计算的值,可以很好地索引以便快速访问。
但我不完全明白它们是如何实际工作的。我已经看到一个排列 table 实现为 0-255 的随机值数组,如下所示:
permutation[] = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
但我不确定这样做的实际目的是什么。我想知道的是:
- 排列 table 如何与网格点相关?
- 梯度table是如何生成的?
- 排列 table 的值如何与梯度 table 一起使用?排列值是否对应于梯度的索引 table?
我一直在断断续续地研究 libnoise 和 perlin 噪声代码一段时间,这样我就可以理解它们是如何工作的。我讨厌使用我不理解的代码:)
如果您不使用 Unity,浏览 http://catlikecoding.com/unity/tutorials/noise/ 可能会对您有所帮助,但您可能能够相应地转换代码。对我帮助很大。
还有许多其他网站提供提示和技巧。 Google libnoise、procedural 等应该向您展示一些您可以浏览的示例。
基本上,虽然在噪声中与整数数组一起使用的梯度是 0,0,0 附近的点,但有一些额外的点可以将其填充到设定的数字。使用基于 x,y,z 坐标(0 和 1 表示点的每一侧)选择的整数的组合,例如,您有:
// Separate the integer element
int ix0 = int(point.x);
int iy0 = int(point.y);
int iz0 = int(point.z);
// Grab the fractional parts for use later
float tx0 = point.x - ix0;
float ty0 = point.y - iy0;
float tz0 = point.z - iz0;
float tx1 = tx0 - 1f;
float ty1 = ty0 - 1f;
float tz1 = tz0 - 1f;
// Make sure that it is a value compatible with the integer array
ix0 &= hashMask;
iy0 &= hashMask;
iz0 &= hashMask;
// Get the other side of the point
int ix1 = ix0 + 1;
int iy1 = iy0 + 1;
int iz1 = iz0 + 1;
// Grab the integers found at the location in the array
int h0 = hash[ix0];
int h1 = hash[ix1];
int h00 = hash[h0 + iy0];
int h10 = hash[h1 + iy0];
int h01 = hash[h0 + iy1];
int h11 = hash[h1 + iy1];
// Gradient array
private static Vector3[] gradients3D = {
new Vector3( 1f, 1f, 0f),
new Vector3(-1f, 1f, 0f),
new Vector3( 1f,-1f, 0f),
new Vector3(-1f,-1f, 0f),
new Vector3( 1f, 0f, 1f),
new Vector3(-1f, 0f, 1f),
new Vector3( 1f, 0f,-1f),
new Vector3(-1f, 0f,-1f),
new Vector3( 0f, 1f, 1f),
new Vector3( 0f,-1f, 1f),
new Vector3( 0f, 1f,-1f),
new Vector3( 0f,-1f,-1f),
new Vector3( 1f, 1f, 0f),
new Vector3(-1f, 1f, 0f),
new Vector3( 0f,-1f, 1f),
new Vector3( 0f,-1f,-1f)
};
private const int gradientsMask3D = 15;
// Grab the gradient value at the requested point
Vector3 g000 = gradients3D[hash[h00 + iz0] & gradientsMask3D];
Vector3 g100 = gradients3D[hash[h10 + iz0] & gradientsMask3D];
Vector3 g010 = gradients3D[hash[h01 + iz0] & gradientsMask3D];
Vector3 g110 = gradients3D[hash[h11 + iz0] & gradientsMask3D];
Vector3 g001 = gradients3D[hash[h00 + iz1] & gradientsMask3D];
Vector3 g101 = gradients3D[hash[h10 + iz1] & gradientsMask3D];
Vector3 g011 = gradients3D[hash[h01 + iz1] & gradientsMask3D];
Vector3 g111 = gradients3D[hash[h11 + iz1] & gradientsMask3D];
// Calculate the dot product using the vector and respective fractions
float v000 = Dot(g000, tx0, ty0, tz0);
float v100 = Dot(g100, tx1, ty0, tz0);
float v010 = Dot(g010, tx0, ty1, tz0);
float v110 = Dot(g110, tx1, ty1, tz0);
float v001 = Dot(g001, tx0, ty0, tz1);
float v101 = Dot(g101, tx1, ty0, tz1);
float v011 = Dot(g011, tx0, ty1, tz1);
float v111 = Dot(g111, tx1, ty1, tz1);
// Interpolate between 2 dot results using the fractional numbers
l0 = Lerp(v000, v100, tx);
l1 = Lerp(v010, v110, tx);
l2 = Lerp(l0,l1,ty);
l3 = Lerp(v001, v101, tx);
l4 = Lerp(v011, v111, tx);
l5 = Lerp(l3,l4,ty);
l6 = Lerp(l2,l5,tz);
这会产生一个数字,代表 space 中使用相同整数和梯度数组的单个唯一点。简单地更改种子并重新排列整数数组和渐变数组将生成不同的数字,使您可以为项目带来独特性,但使用相同的代码来生成它。
之所以整数数组是一组重复的数字,总共有 512 个元素,是因为这样查找就不会意外地超过 0-255 的限制,上面代码中添加的 +1 值可能会导致这种限制。
如果您想象一条直线 (1D x0 - x1)、一个正方形 (2D x0,y0 - x1,y1) 和一个立方体 (3D x0,y0,z0 - x1,y1,z1),您将有望看到代码的作用以及大部分代码将非常相似。
我尝试制作自己的代码版本,但尽管进行了多次尝试,我现在可以理解为什么每个人的噪音代码都如此相似。 perlin 和类似的单纯形噪声实际上只有一种方法可以工作。
所以我现在的目标是将此功能应用到着色器等效代码中,以帮助我至少了解 perlin 噪声和着色器编程的来龙去脉。这是一个学习曲线,但同时也很有趣。
好吧,希望这已经回答了你所有的问题。如果您想了解 Ken Perlin 改进的 Perlin 代码的来龙去脉,请查看以下内容:
http://http.developer.nvidia.com/GPUGems/gpugems_ch05.html - 立方体的视觉效果
所以我一直在研究 Perlin 和 Simplex 噪声的工作原理,虽然我了解了常规 Perlin 噪声的核心原理,但我对排列和梯度的方式有点困惑table 的工作。
根据我的理解,它们提供了比种子随机数生成器更好的性能,因为它们是 table 预先计算的值,可以很好地索引以便快速访问。
但我不完全明白它们是如何实际工作的。我已经看到一个排列 table 实现为 0-255 的随机值数组,如下所示:
permutation[] = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
但我不确定这样做的实际目的是什么。我想知道的是:
- 排列 table 如何与网格点相关?
- 梯度table是如何生成的?
- 排列 table 的值如何与梯度 table 一起使用?排列值是否对应于梯度的索引 table?
我一直在断断续续地研究 libnoise 和 perlin 噪声代码一段时间,这样我就可以理解它们是如何工作的。我讨厌使用我不理解的代码:)
如果您不使用 Unity,浏览 http://catlikecoding.com/unity/tutorials/noise/ 可能会对您有所帮助,但您可能能够相应地转换代码。对我帮助很大。
还有许多其他网站提供提示和技巧。 Google libnoise、procedural 等应该向您展示一些您可以浏览的示例。
基本上,虽然在噪声中与整数数组一起使用的梯度是 0,0,0 附近的点,但有一些额外的点可以将其填充到设定的数字。使用基于 x,y,z 坐标(0 和 1 表示点的每一侧)选择的整数的组合,例如,您有:
// Separate the integer element
int ix0 = int(point.x);
int iy0 = int(point.y);
int iz0 = int(point.z);
// Grab the fractional parts for use later
float tx0 = point.x - ix0;
float ty0 = point.y - iy0;
float tz0 = point.z - iz0;
float tx1 = tx0 - 1f;
float ty1 = ty0 - 1f;
float tz1 = tz0 - 1f;
// Make sure that it is a value compatible with the integer array
ix0 &= hashMask;
iy0 &= hashMask;
iz0 &= hashMask;
// Get the other side of the point
int ix1 = ix0 + 1;
int iy1 = iy0 + 1;
int iz1 = iz0 + 1;
// Grab the integers found at the location in the array
int h0 = hash[ix0];
int h1 = hash[ix1];
int h00 = hash[h0 + iy0];
int h10 = hash[h1 + iy0];
int h01 = hash[h0 + iy1];
int h11 = hash[h1 + iy1];
// Gradient array
private static Vector3[] gradients3D = {
new Vector3( 1f, 1f, 0f),
new Vector3(-1f, 1f, 0f),
new Vector3( 1f,-1f, 0f),
new Vector3(-1f,-1f, 0f),
new Vector3( 1f, 0f, 1f),
new Vector3(-1f, 0f, 1f),
new Vector3( 1f, 0f,-1f),
new Vector3(-1f, 0f,-1f),
new Vector3( 0f, 1f, 1f),
new Vector3( 0f,-1f, 1f),
new Vector3( 0f, 1f,-1f),
new Vector3( 0f,-1f,-1f),
new Vector3( 1f, 1f, 0f),
new Vector3(-1f, 1f, 0f),
new Vector3( 0f,-1f, 1f),
new Vector3( 0f,-1f,-1f)
};
private const int gradientsMask3D = 15;
// Grab the gradient value at the requested point
Vector3 g000 = gradients3D[hash[h00 + iz0] & gradientsMask3D];
Vector3 g100 = gradients3D[hash[h10 + iz0] & gradientsMask3D];
Vector3 g010 = gradients3D[hash[h01 + iz0] & gradientsMask3D];
Vector3 g110 = gradients3D[hash[h11 + iz0] & gradientsMask3D];
Vector3 g001 = gradients3D[hash[h00 + iz1] & gradientsMask3D];
Vector3 g101 = gradients3D[hash[h10 + iz1] & gradientsMask3D];
Vector3 g011 = gradients3D[hash[h01 + iz1] & gradientsMask3D];
Vector3 g111 = gradients3D[hash[h11 + iz1] & gradientsMask3D];
// Calculate the dot product using the vector and respective fractions
float v000 = Dot(g000, tx0, ty0, tz0);
float v100 = Dot(g100, tx1, ty0, tz0);
float v010 = Dot(g010, tx0, ty1, tz0);
float v110 = Dot(g110, tx1, ty1, tz0);
float v001 = Dot(g001, tx0, ty0, tz1);
float v101 = Dot(g101, tx1, ty0, tz1);
float v011 = Dot(g011, tx0, ty1, tz1);
float v111 = Dot(g111, tx1, ty1, tz1);
// Interpolate between 2 dot results using the fractional numbers
l0 = Lerp(v000, v100, tx);
l1 = Lerp(v010, v110, tx);
l2 = Lerp(l0,l1,ty);
l3 = Lerp(v001, v101, tx);
l4 = Lerp(v011, v111, tx);
l5 = Lerp(l3,l4,ty);
l6 = Lerp(l2,l5,tz);
这会产生一个数字,代表 space 中使用相同整数和梯度数组的单个唯一点。简单地更改种子并重新排列整数数组和渐变数组将生成不同的数字,使您可以为项目带来独特性,但使用相同的代码来生成它。
之所以整数数组是一组重复的数字,总共有 512 个元素,是因为这样查找就不会意外地超过 0-255 的限制,上面代码中添加的 +1 值可能会导致这种限制。
如果您想象一条直线 (1D x0 - x1)、一个正方形 (2D x0,y0 - x1,y1) 和一个立方体 (3D x0,y0,z0 - x1,y1,z1),您将有望看到代码的作用以及大部分代码将非常相似。
我尝试制作自己的代码版本,但尽管进行了多次尝试,我现在可以理解为什么每个人的噪音代码都如此相似。 perlin 和类似的单纯形噪声实际上只有一种方法可以工作。
所以我现在的目标是将此功能应用到着色器等效代码中,以帮助我至少了解 perlin 噪声和着色器编程的来龙去脉。这是一个学习曲线,但同时也很有趣。
好吧,希望这已经回答了你所有的问题。如果您想了解 Ken Perlin 改进的 Perlin 代码的来龙去脉,请查看以下内容:
http://http.developer.nvidia.com/GPUGems/gpugems_ch05.html - 立方体的视觉效果