Perlin noise(二)
Perlin noise(二)
通过前面两篇文章,我们现在对噪声已经有了初步的了解,对Perlin噪声生成原理也进行了阐述,现在是时候开始学习噪声生成了,即将进入程序纹理这个神秘而又让人觊觎的国度了。
(一)、1D Perlin Noise
前面我们是以2D Perlin 噪声来讲解Perlin噪声的原理的,对于1D Perlin噪声,我们首先要明白,1D Perlin噪声就是点,通过插值我们可以得到线。对1D Perlin噪声来说,我们不需要晶格,或者说我们只需要一个点就可以了,但Perlin噪声生成的流程我们都得过一遍。回顾一下2D Perlin噪声生成,第一步我们得到晶格的顶点梯度向量,第二步得到距离向量并将距离向量与梯度向量作点积,第三步我们对得到的点做插值从而得到二维纹理。对1D Perlin噪声也是一样。
第一步:生成伪随机梯度值(Pseudo-Random Number Generator(PRNG ))。对1D Perlin噪声来说,在晶格上他只有一个点,所以只需要一个随机梯度值,根据我们之前说的,伪随机梯度值其实并不是完全随机的值,他需要保持一定的稳定性,对于给定的输入要求得到确实的输出。因此对我们来说Math.random()并不是最好的选择,在前面的文章中,我们学习了LCG(Linear Congruential Generator),正好在这里派上用场。在这里,我们用一个随机数初始化Z,但在下一次计算的时候就会用上一次生成的Z来取代这个随机Z,同时,我们将返回值限制在[0,1]这个区间内,为方便使用,我们将PRNG单独写成一个类。
public class PRNG
{
static long M = 4294967296;
static long A = 1664525;
static long C = 1;
static long Z;
public PRNG()
{
Random rdm = new Random();
Z = (long)Math.Floor((rdm.NextDouble() * M));
}
public double Next()
{
Z = (A * Z + C) % M;
return (double)Z / (double)M;
}
}
我们知道LCG输出其实是跟上一个输出有关系的,在这里我们使用了Math.random(),在参数选择上,我们选择了最大的 uint 值作为M的值,M值越大,生成随机数重复的可能性就越小。好了,现在梯度向量我们已经生成了。
第二步:生成距离向量,在1D perlin噪声中,使用的晶格只有一个点,因此没有办法计算这个距离向量,这里我们将距离向量看成是1,点积后不改变梯度向量即可。
第三步:插值。根据前面所学,插值方法多种多样,这里我们选择Cosine插值算法进行插值处理,兼顾计算量与平滑度。
private double Cos_Interpolate(double a, double b, double t)
{
double ft = t * Math.PI;
t = (1 - Math.Cos(ft)) * 0.5;
return a * (1 - t) + t * b;
}
至此,所有准备工作都已准备了,剩下的就是把这些整合起来得到最终的1D perlin 噪声了。
public int Perlin1D(int x)
{
Wavelength = (int)(1.0f / Frequency);
double y;
if (x % Wavelength == 0)
{
pointA = pointB;
pointB = myPRNG.Next();
y = pointA * Amplitude;
}
else
{
y = Cos_Interpolate(pointA, pointB, (double)(x % Wavelength) / Wavelength) * Amplitude;
}
return (int)y;
}
简单解释一下代码,首先通过频率得到波长,得到波长的目的是在合适的点生成Perlin噪声点,其他点则使用插值法进行计算,现在1D perlin噪声我们就生成了,其余的工作就是将生成的曲线在界面上表现出来,不再赘述(代码在本文末,界面我们使用GDI+来画,代码在VS2015+WIN10上测试通过,以下同)。生成的1D Perlin噪声如下图所示:
这张图是完全用离散的点来画的,当然,我们也可以在点与点之间用直线连接起来形成连续的曲线。上面这张图看起来很平滑,也就是说缺乏细节,我们可以使用分形布朗运动方法生成带有丰富细节的曲线,下面这张图就是octaves = 4时的分形曲线。
1D Perlin噪声可以用来扭曲曲线,增加曲线或图形的随机感,稍后我们将会学习到。1D Perlin噪声生成相对简单,因为没有距离向量的参与让事情简单不少,1D Perlin噪声生成的关键是PRNG,使用LCG保证了PRNG的相关性,所以生成的点不是完全随机而是有一定规律的点。
(二)、2D Perlin Noise
1D Perlin噪声因为没有距离的参与让事情简单不少,在学完1D Perlin噪声之后,现在是时候学习2D Perlin噪声了,我们遵照Perlin噪声生成原理,一步一步的实现之。
第一步:生成伪随机梯度值PRNG ,在1D中,我们使用了LCG来生成PRNG,在2D中,我们不再使用这种方法,我们采用在单位圆上分布均匀的单位向量作为PRNG值,对2D Perlin噪声来说,8或16个梯度值就基本能满足要求了。这里我们使用8个梯度值。我们让这8个梯度向量在圆上均匀分布。
static float[,] gradients;
gradients = new float[8,2];
private void GenerateGradients()
{
for (int i = 0; i < 8; ++i)
{
gradients[i,0] = (float)Math.Cos(0.785398163f * (float)i); // ( 2 * PI / 8) * i
gradients[i,1] = (float)Math.Sin(0.785398163f * (float)i);
}
}
gradients[i,0] = (float)Math.Cos(0.785398163f * (float)i) 这行代码就是计算梯度向量的x分量值,0.785398163f = ( 2 * PI / 8) ,这是把圆平均分成了8份。然后我们依次取各向量的值作为梯度向量。好了,现在梯度向量有了,如何选择他们呢?也就是说对于每一个顶点,我们该选择哪一个值作为他的梯度向量呢?根据前文叙述我们知道这个梯度值不能随便选择,得保持一定的稳定性,对于给定的值,梯度向量应该选择同一个值,这样才能保证生成的Perlin噪声的连续性。对此,我们先初始化一个排列permutation,然后我们初始的时候我们打乱这个排列表以得到不同的结果。这个排列表permutation也是Perlin本人给出的,这里我们只是拿过来用。
private static readonly int[] 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
};
private static int[] p;
p = new int[256];
Random rdm = new Random();
int j;
for (int x = 0; x < 256; x++)
{
j = (int)rdm.Next(256);
p[x] = permutation[j];
}
有了排列表,我们就可以根据输入值所在晶格选择相应的梯度值了。选择梯度值的代码如下:
grad11 = p[(Xi + p[Yi & 255]) & 255] & 7;
grad12 = p[(Xi + 1 + p[Yi & 255]) & 255] & 7;
grad21 = p[(Xi + p[(Yi + 1) & 255]) & 255] & 7;
grad22 = p[(Xi + 1 + p[(Yi + 1) & 255]) & 255] & 7;
grad11,grad12,grad21,grad22分别代表输入点所在晶格四个顶点的梯度值索引,得到排列值后我们让他与7相与,保证梯度值索引小于等7(因为我们只有8个梯度值)。有了梯度值索引后,我们通过gradients就可以得到梯度值了。
第二步:生成距离向量。根据前文的生成原理,我们要得到距离向量,我们首先要得到输入点所在的晶格四个点的索引值,这个通过取输入点的Foor值就可以得到。输入点在所在晶格(四边形)的位置其实就是输入点的小数部分,得到位置用他去减晶格四个索引值就得一距离向量了。
Xi = GetFloor((float)x * Frequency); //x,y为输入值
Yi = GetFloor((float)y * Frequency);
fracX = (float)x * Frequency - (float)Xi; //x * Frequency是为了将输入值与频率关联起来。
fracY = (float)y * Frequency - (float)Yi;
得到了距离向量和梯度值,我们将这两个值作点积。
noise11 = DotProduct(gradients[grad11,0], gradients[grad11, 1], fracX, fracY);
noise12 = DotProduct(gradients[grad12,0], gradients[grad12, 1], fracX - 1.0f, fracY);
noise21 = DotProduct(gradients[grad21,0], gradients[grad21, 1], fracX, fracY - 1.0f);
noise22 = DotProduct(gradients[grad22,0], gradients[grad22, 1], fracX - 1.0f, fracY - 1.0f);
因为是2D,总共有四个点,所以点积后得到四个值,得到四个值后我们对这四个值进行双向性插值,这就是第三步了。
第三步:插值。我们先得到插值参数,这个利用改进的Perlin算法缓和曲线就可以实现,不再赘述。
fracX = Fade(fracX); //复用了变量fracX ,fracY
fracY = Fade(fracY);
interpolatedx1 = (float)Cos_Interpolate(noise11, noise12, fracX);
interpolatedx2 = (float)Cos_Interpolate(noise21, noise22, fracX);
return (float)Cos_Interpolate(interpolatedx1, interpolatedx2, fracY) * Amplitude;
好了,至此,2D Perlin噪声生成完毕,剩下的工作就是将其显示出来(或者保存为文件),这里不再赘述。生成图片的时候我们对颜色进行了一些处理,所以看起来跟之前看到的2D Perlin噪声图像有一点不一样。
同样,我们可以使用分形布朗运动方法生成带有丰富细节的图像,下面这张图就是octaves = 4时的分形图像。
我们采用了两种方式来展示生成的噪声图像,一种采用VS2015+C#,另一种采用UNIYT2017.1.1f1+Shader,这两种方式一种使用CPU来计算,另一种则使用GPU来计算,因为底层架构不同,实现方式也不一样。
(三)、小结
这篇文章我们主要讲解了1D和2D Perlin噪声的生成算法,1D Perlin生成中距离向量没有参与或者说是以单位向量的形式参与运算,所以大大简化了复杂性,2D Perlin噪声生成中完全遵照了Perlin论文中生成噪声的算法来生成,排列表permutation主要是保证选择的梯度向量有很强的随机性,但同时他还保证对于同样的输入有同样的输出,这个对生成Perlin噪声至关重要,否则生成的噪声可能就是白噪声了。
(四)、代码下载
一、VS2015+C#版的1D和2D 噪声生成代码:代码
二、Unity+Shader版本: 代码
参考文献:
1、Understanding Perlin Noise https://flafla2.github.io/2014/08/09/perlinnoise.html
智能推荐
转载:Simplex Noise(二)
本文转载自:https://blog.cs 一、2D Simplex Noise 这次我们不打算从1D做起而是直接从2D Simplex Noise做起,不是因为Simplex Noise不能实现1D噪声,而是应用中2D、3D、4D用得更多一些。从前面的学习中,我们将Simplex Noise生成算法分成四个步骤,本文将继续采用这种分步实现的模式。 (一)、坐标变换(Coo...
Simplex Noise(二)
一、2D Simplex Noise 这次我们不打算从1D做起而是直接从2D Simplex Noise做起,不是因为Simplex Noise不能实现1D噪声,而是应用中2D、3D、4D用得更多一些。从前面的学习中,我们将Simplex Noise生成算法分成四个步骤,本文将继续采用这种分步实现的模式。 (一)、坐标变换(Coordinate skewing)。 &ems...
Worley Noise(二)
根据前文所述,Worley噪声代码实现比较简单,我们着重讲解一下2D噪声的算法,3D、4D实现基本一致,不作详细描述。 一、Worley Noise 2D实现 还是与以前一样,我们分步来实现Worley噪声,根据前文步骤,Worley噪声实现共分六步: 第一步、确定输入点所在的晶胞 这与Perlin噪声确定输入点所在晶格是一样...
Value Noise(二)
一、Value Noise 1D 根据之前学习的Value 噪声原理,我们分步来实现之。但在这之前,我们还有一些准备工作要做,如之前所说的,Value噪声是将随机值赋于晶格点,然后根据输入点所处晶格,对随机值进行线性插值而得到噪声值,所以,我们首先初始化一个晶格顶点值数组,并将各顶点的随机值保存在顶点上,在实施中,我们初始化了一个长度512的float数组保存我们的晶格顶点...
Linux信号及工作原理
什么是信号 信号可以理解为软件中断,是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是差不多的。信号是异步的,一个进程不必通过任何操作来等待信号的到达。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。 谁来产生信号 信号事件的发生有两个...
猜你喜欢
手机端图片放大,双指放大,元素的双指缩放
在做webapp时候,遇到一个需要做双指放大的功能,需求是:一张带有坐标的图片上有固定的点,需要点击这些坐标上的点进入相应的商品,并且需要对这一块进行双指可以缩放,双击缩放; 一开始是自己写监听touch事件进行处理,但是再缩放的时候,偶尔出现卡顿闪烁,用户体验不很好,后来采用插件 pinch-zoom GitHub地址: https://github.com/manuelstofer/...
redis 初步了解
1.连接redis 通过java操作 1.首先 导入redis驱动 2.连接redis通过jedis 2.创建redis连接池 连接redis通过 jedis 相当于HTTPclient 1.创建单例模式的方法 在调用的时候被创建 2.创建私有静态 jedisPool 3. 创建私有类 创建静态代码块 放入连接池的基本配置 4.有 最大连接数 最大空闲书 最小空闲连接数 5. 创建连接redis对...
第6章数据类型-基本数据类型-Boolean类型-main
防采集标记:亢少军老师的课程和资料 Dart交流群:1046954554 Flutter开源项目请关注: https://github.com/kangshaojun @作者: 亢少军 '...
使用 Infura 和 web3.js 呼叫合约
如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战入门教程 以太坊去中心化电商应用开发实战 如果你希望了解如何部署合约,可以查看另一篇文章:在truffle中使用infura部署以太坊智能合约。 Infura 提供公开的 Ethereum 主网和测试网络节点。到 Infura 官网申请,只要输入一点基本资料和 Email,就可以收到 API-ke...
