水晶杂谈4:手撕柏林噪声源码,跳转随机领域展望无限

文章目录

  • 前言
  • 柏林噪声取样器 Perlin Noise Sampler
    • 取值操作
    • 顶点哈希
    • 梯度向量
    • 平滑函数
      • 了解Fade函数
    • 立方插值
    • 图样效果
    • 游戏实现
  • 参考

前言

该文章参考1.21.1 Java版Yarn映射,详细分析柏林噪声
本文存在许多数学公式,可以更好理解文章

柏林噪声取样器 Perlin Noise Sampler

取值操作

将排列表取名为permutation,permutation装有256个元素,其范围是从0到255的随机数,然后每个值的哈希算法得到

public PerlinNoiseSampler(Random random) {
	/* 首先创建一个排列表 Permutation Table */
	this.permutation = new byte[256];

	for (int i = 0; i < 256; i++) {
    	// 向数组中添加数值:0、1、2 ... 255
    	this.permutation[i] = (byte)i;
	}

    /* 根据种子随机确定唯一的排列表:运用哈希算法 */
    /* 数组长度为256,分别随机、无重复地存放了0-255这些数值 */
    for (int i = 0; i < 256; i++) {
    	// 随机取值,每次取值范围-1
    	int j = random.nextInt(256 - i);
    	// 置换操作
    	byte b = this.permutation[i];
    	this.permutation[i] = this.permutation[i + j];
    	this.permutation[i + j] = b;
    }
}

为何使用Byte数组装在256个元素,理由Byte字节,由8位的二进制组成,其取值范围为[-128, 127],总计为256个元素

例如:new Radom(157):随机种子为157时,该排列表中256个元素中值为

byte[] permutation = {
18, -116, -7, 23, -40, -21, -49, 39, -83, -84, 54, 61, -34, -111, 71, -17,
-74, -59, 6, 76, -99, -27, 70, 57, 34, 92, 60, 37, -63, 42, -109, 53,
12, -70, 25, 112, -19, 26, -125, -11, -25, -9, 101, -122, 49, 47, 82, 13,
21, -50, 122, 127, -86, -77, 73, 3, -47, -108, 44, -91, 62, 11, -62, 29,
35, 107, -22, 103, 27, -61, -14, -73, 119, -5, -69, 0, 109, -96, 43, -24,
-58, 120, 36, -75, 9, 63, -103, 87, 104, -81, 1, -68, -126, 30, 77, 106,
15, 24, 28, 121, 51, 22, -113, 90, -55, -13, -92, 74, 33, -87, 125, 111,
100, 67, -120, -44, -20, 81, -97, -102, -6, -89, 2, 8, -76, -60, 66, -51,
91, -119, -46, 69, -57, -8, -15, -98, 126, -78, 41, 14, 105, 72, 80, 78,
113, 86, -79, -43, -10, 17, -71, -41, -3, -35, -114, -117, -54, 79, -56, 98,
110, -67, 114, 116, -65, 75, -110, -1, -4, 59, 4, -115, 118, -38, 102, -106,
-101, -30, -48, 45, -95, 68, -64, -128, 7, -29, -39, 16, 88, -124, -18, 65,
84, -88, 55, 64, 19, 108, 85, 10, -72, 20, 99, 117, -52, 123, -53, -45,
-104, -32, 52, 94, -42, -121, 97, -90, -93, -66, -82, 115, 95, -112, 32, -107,
-94, 46, 48, 83, -26, 38, -36, 96, -80, 89, -37, 31, 40, -123, -12, 93,
124, 5, -105, -31, 50, -2, 58, 56, -33, -28, -127, -85, -100, -23, -16, -118
}

sample 取样器:任意找一点 P P P,设空间坐标点 P ( x , y , z ) P(x, y ,z) P(x,y,z),并输出0.0~1.0的double值[1](这样得到的噪声值的范围都在 [ − 1 , 1 ] [-1, 1] [1,1]

public double sample(double x, double y, double z) {
	return this.sample(x, y, z, 0.0, 0.0);
}

P ( x , y , z ) P(x,y,z) P(x,y,z)向下整数部分并设为x2, y2, z2[2],作为左下角空间坐标 A ( x 0 , y 0 , z 0 ) A(x_0, y_0, z_0) A(x0,y0,z0)。小数部分设置为x3, y3, z3,作为正方体中的x, y, z的偏移量(相对于 A A A来说),用于计算权重

@Deprecated
public double sample(double x, double y, double z, double yScale, double yMax) {
	/* 向下取整数,整数x2, y2, z2 */
	int x2 = MathHelper.floor(x);
	int y2 = MathHelper.floor(y);
	int z2 = MathHelper.floor(z);
	/* 取小数部分,差值大于0且小于1,小数x3, y3, z3 */
	double x3 = x - (double)x2;
	double y3 = y - (double)y2;
	double z3 = z - (double)z2;
	return this.sample(x2, y2, z2, x3, y3, z3, y3);
}

根据 A ( x 0 , y 0 , z 0 ) A(x_0, y_0, z_0) A(x0,y0,z0)依次生成八个顶点,构成一个正方体 A B C D A 1 B 1 C 1 D 1 ABCDA_1B_1C_1D_1 ABCDA1B1C1D1,如图:
其中 x 1 = x 0 + 1 , y 1 = y 0 + 1 , z 1 = z 0 + 1 x_1=x_0+1,y_1=y_0+1,z_1=z_0+1 x1=x0+1y1=y0+1z1=z0+1用于构成正方体
水晶杂谈4:手撕柏林噪声源码,跳转随机领域展望无限_第1张图片
计算过程:
假如有一点 P = ( 1.32 , 1.48 , 0 ) P=(1.32,1.48,0) P=(1.32,1.48,0),则对 P P P点进行向下取整得到 A ( 1 , 1 , 0 ) A(1, 1, 0) A(1,1,0),然后对A点每个数值分别自加一得到 ( 1 , 1 , 0 ) 、 ( 2 , 1 , 0 ) 、 ( 1 , 2 , 0 ) 、 ( 2 , 2 , 0 ) 、 ( 1 , 1 , 1 ) 、 ( 2 , 1 , 1 ) 、 ( 1 , 2 , 1 ) 、 ( 2 , 2 , 1 ) (1, 1, 0)、(2, 1, 0)、(1, 2, 0)、(2, 2, 0)、(1, 1, 1)、(2, 1, 1)、(1, 2, 1)、(2, 2, 1) (1,1,0)(2,1,0)(1,2,0)(2,2,0)(1,1,1)(2,1,1)(1,2,1)(2,2,1)这八个顶点

顶点哈希

根据正方体的底面正方形ABCD上这四个顶点( ( x 0 , y 0 ) 、 ( x 0 , y 1 ) 、 ( x 1 , y 0 ) 、 ( x 1 , y 1 ) (x_0, y_0)、(x_0, y_1)、(x_1, y_0)、(x_1, y_1) (x0,y0)(x0,y1)(x1,y0)(x1,y1)),分别计算这四个顶点的哈希值。

private double sample(int sectionX, int sectionY, int sectionZ, double localX, double localY, double localZ, double fadeLocalY) {
	int i = this.map(sectionX);
	int j = this.map(sectionX + 1);
	/* 在正方体中一个面内计算哈希值 */
	int ab = this.map(i + sectionY); // 计算左左下角哈希值
	int ab1 = this.map(i + sectionY + 1); // 计算左右上角哈希值
	int a1b = this.map(j + sectionY); // 计算右左下角哈希值
	int a1b1 = this.map(j + sectionY + 1); // 计算右右上角哈希值
}

而且还要限制坐标在[0,255]这个范围内,避免访问排列表时,出现数组越界错误,柏林噪声每隔256个整数就会再次重复
调用map方法,获取permutation排列表中的值,如下

/**
 * 

限制坐标在[0,255]这个范围内,避免访问排列表时,出现数组越界错误,柏林噪声每隔256个整数就会再次重复

*

通过哈希函数找到排列表的索引

* @param input 数值下标 * @return 返回整数类型 */
private int map(int input) { /* 按位与&: 当对应位都是1是为1 */ return this.permutation[input & 0xFF] & 0xFF; // 0xFF = 255 }

按位与 & 当对应位都是1是为1,如果对应位不是都是1则为0

计算过程
设 s e c t i o n X 取值为 1 , s e c t i o n Y 取值为 1 ,排列表数组为 p e r m u t a t i o n [ ] , 0 x F F 视为 255 设sectionX取值为1,sectionY取值为1,排列表数组为permutation[],0xFF视为255 sectionX取值为1sectionY取值为1,排列表数组为permutation[]0xFF视为255

例如求得这段代码的输出值:int i = this.map(sectionX);
1   &   255 = 00000001   &   11111111 = 00000001 = 1 p e r m u t a t i o n [ 1 ] = − 116 p e r m u t a t i o n [ 1 ]   &   255 = − 116   & 255   = 1000110   &   11111111 = 1000110 = 140 \begin{aligned} 1\:\&\:255&=00000001\:\&\:11111111 \\ &=00000001 \\ &=1 \\ permutation[1]&=-116 \\ permutation[1]\:\&\:255&=-116\:\&255\: \\ &=1000110\:\&\:11111111 \\ &=1000110 \\ &=140 \end{aligned} 1&255permutation[1]permutation[1]&255=00000001&11111111=00000001=1=116=116&255=1000110&11111111=1000110=140
所以int i = this.map(sectionX);输出为140
同理,可得( 令 p e r m u t a t i o n [ ] = p [ ] ,随机种子为 157 令permutation[]=p[],随机种子为157 permutation[]=p[],随机种子为157)
i = p [ 1   &   255 ]   &   255 = p [ 1 ]   &   255 = 140 j = p [ 2   &   255 ]   &   255 = p [ 2 ]   &   255 = 249 a b = p [ 141   &   255 ]   &   255 = p [ 141 ]   &   255 = 72 a b 1 = p [ 142   &   255 ]   &   255 = p [ 142 ]   &   255 = 80 a 1 b = p [ 250   &   255 ]   &   255 = p [ 250 ]   &   255 = 129 a 1 b 1 = p [ 251   &   255 ]   &   255 = p [ 251 ]   &   255 = 171 \begin{aligned} i&=p[1\:\&\:255]\:\&\:255=p[1]\:\&\:255=140 \\ j&=p[2\:\&\:255]\:\&\:255=p[2]\:\&\:255=249 \\ ab&=p[141\:\&\:255]\:\&\:255=p[141]\:\&\:255=72 \\ ab1&=p[142\:\&\:255]\:\&\:255=p[142]\:\&\:255=80 \\ a1b&=p[250\:\&\:255]\:\&\:255=p[250]\:\&\:255=129 \\ a1b1&=p[251\:\&\:255]\:\&\:255=p[251]\:\&\:255=171 \\ \end{aligned} ijabab1a1ba1b1=p[1&255]&255=p[1]&255=140=p[2&255]&255=p[2]&255=249=p[141&255]&255=p[141]&255=72=p[142&255]&255=p[142]&255=80=p[250&255]&255=p[250]&255=129=p[251&255]&255=p[251]&255=171
这样就能获取正方形ABCD四个顶点的哈希值( 72 、 80 、 129 、 171 72、80、129、171 7280129171),接着加入z轴进一步计算哈希值,如下

/* 计算左左下角哈希值 */
double abc = grad(this.map(ab + sectionZ), localX, localY, localZ);
/* 计算左右下角哈希值 */
double a1bc = grad(this.map(a1b + sectionZ), localX - 1.0, localY, localZ);
/* 计算右左下角哈希值 */
double ab1c = grad(this.map(ab1 + sectionZ), localX, localY - 1.0, localZ);
/* 计算右右下角哈希值 */
double a1b1c = grad(this.map(a1b1 + sectionZ), localX - 1.0, localY - 1.0, localZ);
/* 计算左左上角哈希值 */
double abc1 = grad(this.map(ab + sectionZ + 1), localX, localY, localZ - 1.0);
/* 计算左右上角哈希值 */
double a1bc1 = grad(this.map(a1b + sectionZ + 1), localX - 1.0, localY, localZ - 1.0);
/* 计算右左上角哈希值 */
double ab1c1 = grad(this.map(ab1 + sectionZ + 1), localX, localY - 1.0, localZ - 1.0);
/* 计算右右上角哈希值 */
double a1b1c1 = grad(this.map(a1b1 + sectionZ + 1), localX - 1.0, localY - 1.0, localZ - 1.0);

随机种子为157,随机点为 ( 1.32 , 1.48 , 0 ) (1.32,1.48,0) (1.32,1.48,0),得出八个顶点的哈希值
a b c = p [ 72   &   255 ]   &   255 = p [ 72 ]   &   255 = 119 a b 1 c = p [ 129   &   255 ]   &   255 = p [ 142 ]   &   255 = 137 a 1 b c = p [ 80   &   255 ]   &   255 = p [ 250 ]   &   255 = 198 a 1 b 1 c = p [ 171   &   255 ]   &   255 = p [ 251 ]   &   255 = 141 a b c 1 = p [ 73   &   255 ]   &   255 = p [ 73 ]   &   255 = 251 a b 1 c 1 = p [ 130   &   255 ]   &   255 = p [ 130 ]   &   255 = 210 a 1 b c 1 = p [ 81   &   255 ]   &   255 = p [ 81 ]   &   255 = 120 a 1 b 1 c 1 = p [ 172   &   255 ]   &   255 = p [ 172 ]   &   255 = 118 \begin{aligned} abc&=p[72\:\&\:255]\:\&\:255=p[72]\:\&\:255=119 \\ ab1c&=p[129\:\&\:255]\:\&\:255=p[142]\:\&\:255=137 \\ a1bc&=p[80\:\&\:255]\:\&\:255=p[250]\:\&\:255=198 \\ a1b1c&=p[171\:\&\:255]\:\&\:255=p[251]\:\&\:255=141 \\ abc1&=p[73\:\&\:255]\:\&\:255=p[73]\:\&\:255=251 \\ ab1c1&=p[130\:\&\:255]\:\&\:255=p[130]\:\&\:255=210 \\ a1bc1&=p[81\:\&\:255]\:\&\:255=p[81]\:\&\:255=120 \\ a1b1c1&=p[172\:\&\:255]\:\&\:255=p[172]\:\&\:255=118 \\ \end{aligned} abcab1ca1bca1b1cabc1ab1c1a1bc1a1b1c1=p[72&255]&255=p[72]&255=119=p[129&255]&255=p[142]&255=137=p[80&255]&255=p[250]&255=198=p[171&255]&255=p[251]&255=141=p[73&255]&255=p[73]&255=251=p[130&255]&255=p[130]&255=210=p[81&255]&255=p[81]&255=120=p[172&255]&255=p[172]&255=118

梯度向量

然后将八个点各自生成一个伪随机的梯度向量,随机挑选结果其实取决于前一步所计算得出的哈希值(grad()函数的第一个参数)。后面3个参数则代表由输入点 P ( x , y , z ) P(x,y,z) P(x,y,z)指向左左下角点 A ( x 0 , y 0 , z 0 ) A(x_0,y_0,z_0) A(x0,y0,z0)的偏移量( x 3 , y 3 , z 3 x_3,y_3,z_3 x3,y3,z3)(最终拿来与梯度向量进行点积)

private static double grad(int hash, double x, double y, double z) {
	return SimplexNoiseSampler.dot(SimplexNoiseSampler.GRADIENTS[hash & 15], x, y, z);
}

而这些梯度向量由16个向量(由正方体的中心点到各条边中点的向量)组成的二维数组.。
采用四组均匀分布的梯度向量,然后通过排列表的索引进行取值,这样虽然丢弃了梯度表的随机性,但是可以通过排列表的随机性来实现
先预设有12个不同的向量,并在这些向量里随机选一个作为这个顶点的随机梯度,且梯度向量的值最终需要在[-1, 1]之间
为了方便用位运算,所以在原来基础之上新增加4个,把预设的向量个数增加到16个,与第一组梯度向量重复
而重复向量并不影响取值操作,仍可以在这16个向量里随便选一个作为顶点处的梯度向量

/** 以下16个向量里随机挑选一个作为梯度向量 */
protected static final int[][] GRADIENTS = new int[][]{
        {1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0},
        {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1},
        {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1},
        // 新增4个梯度向量为一组
        {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1}
};

根据偏移量 n → ( x 3 , y 3 , z 3 ) \overrightarrow{n}(x_3,y_3,z_3) n (x3,y3,z3)与梯度向量的 g → ( g 1 , g 2 , g 3 ) \overrightarrow{g}(g_1,g_2,g_3) g (g1,g2,g3),分别成积得到最终的哈希值
n → ⋅ g → = ( x 3 , y 3 , z 3 ) ⋅ ( g 1 , g 2 , g 3 ) = x 3 g 1 + y 3 g 2 + z 3 g 3 \overrightarrow{n}·\overrightarrow{g}=(x_3,y_3,z_3)·(g_1,g_2,g_3)=x_3g_1+y_3g_2+z_3g_3 n g =(x3,y3,z3)(g1,g2,g3)=x3g1+y3g2+z3g3

protected static double dot(int[] gradient, double x, double y, double z) {
    return (double)gradient[0] * x + (double)gradient[1] * y + (double)gradient[2] * z;
}

根据上一步计算八个顶点的哈希值,去计算出 a b c 、 a 1 b c 、 a b 1 c 、 a 1 b 1 c 、 a b c 1 、 a 1 b c 1 、 a b 1 c 1 、 a 1 b 1 c 1 abc、a1bc、ab1c、a1b1c、abc1、a1bc1、ab1c1、a1b1c1 abca1bcab1ca1b1cabc1a1bc1ab1c1a1b1c1这八个顶点梯度向量
g r a d i e n t [ ] = g [ ] gradient[]=g[] gradient[]=g[]
a b c = g 1 → = g [ 119   &   15 ] = g [ 7 ] = ( − 1 , 0 , − 1 ) a b 1 c = g 2 → = g [ 137   &   15 ] = g [ 9 ] = ( 0 , − 1 , 1 ) a 1 b c = g 3 → = g [ 198   &   15 ] = g [ 6 ] = ( 1 , 0 , − 1 ) a 1 b 1 c = g 4 → = g [ 141   &   15 ] = g [ 13 ] = ( 0 , − 1 , 1 ) a b c 1 = g 5 → = g [ 251   &   15 ] = g [ 11 ] = ( 0 , − 1 , − 1 ) a b 1 c 1 = g 6 → = g [ 210   &   15 ] = g [ 2 ] = ( 1 , − 1 , 0 ) a 1 b c 1 = g 7 → = g [ 120   &   15 ] = g [ 8 ] = ( 0 , 1 , 1 ) a 1 b 1 c 1 = g 8 → = g [ 118   &   15 ] = g [ 6 ] = ( 1 , 0 , − 1 ) \begin{aligned} abc=\overrightarrow{g_1}&=g[119\:\&\:15]=g[7]=(-1,0,-1) \\ ab1c=\overrightarrow{g_2}&=g[137\:\&\:15]=g[9]=(0,-1,1) \\ a1bc=\overrightarrow{g_3}&=g[198\:\&\:15]=g[6]=(1,0,-1) \\ a1b1c=\overrightarrow{g_4}&=g[141\:\&\:15]=g[13]=(0,-1,1) \\ abc1=\overrightarrow{g_5}&=g[251\:\&\:15]=g[11]=(0,-1,-1) \\ ab1c1=\overrightarrow{g_6}&=g[210\:\&\:15]=g[2]=(1,-1,0) \\ a1bc1=\overrightarrow{g_7}&=g[120\:\&\:15]=g[8]=(0,1,1) \\ a1b1c1=\overrightarrow{g_8}&=g[118\:\&\:15]=g[6]=(1,0,-1) \\ \end{aligned} abc=g1 ab1c=g2 a1bc=g3 a1b1c=g4 abc1=g5 ab1c1=g6 a1bc1=g7 a1b1c1=g8 =g[119&15]=g[7]=(1,0,1)=g[137&15]=g[9]=(0,1,1)=g[198&15]=g[6]=(1,0,1)=g[141&15]=g[13]=(0,1,1)=g[251&15]=g[11]=(0,1,1)=g[210&15]=g[2]=(1,1,0)=g[120&15]=g[8]=(0,1,1)=g[118&15]=g[6]=(1,0,1)
获取各点梯度向量之后,通过偏移量 ( 0.32 , 0.48 , 0 ) (0.32, 0.48, 0) (0.32,0.48,0)乘积,进而求得最终的哈希值
a b c = ( − 1 , 0 , − 1 ) × ( 0.32 , 0.48 , 0 ) = − 0.32 a b 1 c = ( 0 , − 1 , 1 ) × ( − 0.68 , 0.48 , 0 ) = − 0.48 a 1 b c = ( 1 , 0 , − 1 ) × ( 0.32 , − 0.52 , 0 ) = 0.32 a 1 b 1 c = ( 0 , − 1 , 1 ) × ( − 0.68 , − 0.52 , 0 ) = 0.52 a b c 1 = ( 0 , − 1 , − 1 ) × ( 0.32 , 0.48 , − 1 ) = 0.52 a b 1 c 1 = ( 1 , − 1 , 0 ) × ( − 0.68 , 0.48 , − 1 ) = − 1.16 a 1 b c 1 = ( 0 , 1 , 1 ) × ( 0.32 , − 0.52 , − 1 ) = − 1.52 a 1 b 1 c 1 = ( 1 , 0 , − 1 ) × ( − 0.68 , − 0.52 , − 1 ) = 0.32 \begin{aligned} abc&=(-1, 0, -1)\times(0.32, 0.48, 0)=-0.32 \\ ab1c&=(0, -1, 1)\times(-0.68,0.48,0)=-0.48 \\ a1bc&=(1, 0, -1)\times(0.32,-0.52,0)=0.32 \\ a1b1c&=(0, -1, 1)\times(-0.68,-0.52,0)=0.52 \\ abc1&=(0, -1, -1)\times(0.32,0.48,-1)=0.52 \\ ab1c1&=(1, -1, 0)\times(-0.68,0.48,-1)=-1.16 \\ a1bc1&=(0, 1, 1)\times(0.32,-0.52,-1)=-1.52 \\ a1b1c1&=(1, 0, -1)\times(-0.68,-0.52,-1)=0.32 \end{aligned} abcab1ca1bca1b1cabc1ab1c1a1bc1a1b1c1=(1,0,1)×(0.32,0.48,0)=0.32=(0,1,1)×(0.68,0.48,0)=0.48=(1,0,1)×(0.32,0.52,0)=0.32=(0,1,1)×(0.68,0.52,0)=0.52=(0,1,1)×(0.32,0.48,1)=0.52=(1,1,0)×(0.68,0.48,1)=1.16=(0,1,1)×(0.32,0.52,1)=1.52=(1,0,1)×(0.68,0.52,1)=0.32

平滑函数

Fade函数: f ( x ) = 6 x 5 − 15 x 4 + 10 x 3 f(x)=6x^5-15x^4+10x^3 f(x)=6x515x4+10x3

/**
 * Fade Function: 6t^5 - 15t^4 + 10t^3
 * @param value 自变量x
 * @return 返回平滑函数
 */
public static double perlinFade(double value) {
    return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
}

将偏移值 ( x 3 , x y , x 3 ) (x_3, x_y,x_3) (x3,xy,x3)带入fade函数,进行取值得出
例如:偏移值 ( 0.32 , 0.48 , 0 ) (0.32, 0.48,0) (0.32,0.48,0),带入这个函数求得 ( 0.16 , 0.46 , 0 ) (0.16, 0.46, 0) (0.16,0.46,0),之后在进行插值操作
此后的输入平滑函数为 Δ x = 0.16 、 Δ y = 0.46 、 Δ z = 0 \Delta x=0.16、\Delta y=0.46、\Delta z=0 Δx=0.16Δy=0.46Δz=0
传入MathHelper中的lerp3方法中去

return MathHelper.lerp3(fx, fy, fz, abc, a1bc, ab1c, a1b1c, abc1, a1bc1, ab1c1, a1b1c1);

以下均为了解内容,可以快进到立方插值


了解Fade函数

fade函数和一次函数交于点 ( 0.5 , 0.5 ) (0.5,0.5) (0.5,0.5),而且在正方形中关于 ( 0.5 , 0.5 ) (0.5,0.5) (0.5,0.5)中心对称
而它得一阶导数 f ‘ ( 0 ) = 0 , f ‘ ( 1 ) = 1 f`(0)=0,f`(1)=1 f(0)=0,f(1)=1,二阶导函数也满足 f ‘ ‘ ( 0 ) = 0 , f ‘ ‘ ( 1 ) = 1 f``(0)=0,f``(1)=1 f‘‘(0)=0,f‘‘(1)=1,如图:
水晶杂谈4:手撕柏林噪声源码,跳转随机领域展望无限_第2张图片
其中改进版函数,可以应用在凹凸映射以及位移贴图等计算要求,这两种计算都基于法线,其中计算法线先进行计算切线,计算切线需要进行求一阶导数,所以要求一阶导数之上也要满足连续性[3]
如何得到改进版函数?[15]主要参考资料[15]
首先函数必需满足以下条件:

  • 在自变量 x = ± 5 x=\pm5 x=±5的值都为0
  • 反导数的自变量 x = ± 5 x=\pm5 x=±5的值都为1
  • 反导函数两端变化慢,中间变化快

其中 f ( x ) = − 2 x 3 + 3 x 2 f(x)=-2x^3+3x^2 f(x)=2x3+3x2的反导数并不满足以上条件,所以需要改进版函数
补充: − 2 x 3 + 3 x 2 -2x^3+3x^2 2x3+3x2导数中含有线性分量,在计算相邻差时会体现人工效果,不够自然连续性
f ( x ) = a x 4 + b x 2 + c f(x)=ax^4+bx^2+c f(x)=ax4+bx2+c
首先使函数在 x = ± 0.5 处为 0 ,然后把函数向右平移 0.5 个单位长度如下: 首先使函数在x=\pm0.5处为0,然后把函数向右平移0.5个单位长度如下: 首先使函数在x=±0.5处为0,然后把函数向右平移0.5个单位长度如下:
a ( ± 1 2 ) 4 + b ± 1 2 x 2 + c = 0 a 16 + b 4 + c = a + 4 b + 16 c = a ( x − 0.5 ) 4 + b ( x − 0.5 ) 2 + c = a ( x 4 − 2 x 3 + 3 2 x 2 − 1 2 x + 1 16 ) + b ( x 2 − x + 1 4 ) + c = a x 4 − 2 a x 3 + ( 3 2 a + b ) x 2 − ( 1 2 a + b ) x + 1 16 a + 1 4 b + c = \begin{aligned} a(\pm\frac{1}{2})^4+b\pm\frac{1}{2}x^2+c&=0 \\ \frac{a}{16}+\frac{b}{4}+c&= \\ a+4b+16c&= \\ a(x-0.5)^4+b(x-0.5)^2+c&= \\ a(x^4-2x^3+\frac{3}{2}x^2-\frac{1}{2}x+\frac{1}{16})+b(x^2-x+\frac{1}{4})+c&= \\ ax^4-2ax^3+(\frac{3}2{a+b})x^2-(\frac{1}{2}a+b)x+\frac{1}{16}a+\frac{1}{4}b+c&= \end{aligned} a(±21)4+b±21x2+c16a+4b+ca+4b+16ca(x0.5)4+b(x0.5)2+ca(x42x3+23x221x+161)+b(x2x+41)+cax42ax3+(23a+b)x2(21a+b)x+161a+41b+c=0=====
令 1 16 a + 1 4 b + c 为 0 ,所以 b = − 1 4 a − 4 c ,带入式子得 令\large\frac{1}{16}a+\frac{1}{4}b+c\normalsize为0,所以\large b=-\frac{1}{4}a-4c\normalsize,带入式子得 161a+41b+c0,所以b=41a4c,带入式子得
a x 4 − 2 a x 3 + ( 5 4 a − 4 c ) x 2 − ( 1 4 a − 4 c ) x = 0 ax^4-2ax^3+(\frac{5}{4}a-4c)x^2-(\frac{1}{4}a-4c)x=0 ax42ax3+(45a4c)x2(41a4c)x=0
接着求这个函数得反函数
∫ [ a x 4 − 2 a x 3 + ( 5 4 a − 4 c ) x 2 − ( 1 4 a − 4 c ) x ] ⋅ d x = 1 5 a x 5 − 1 2 a x 4 + ( 5 12 a − 4 3 c ) x 3 − ( 1 8 a − 2 c ) x 2 \begin{aligned} \int \Big[ ax^4-2ax^3+(\frac{5}{4}a-4c)x^2-(\frac{1}{4}a-4c)x \Big]·dx \\ = \frac{1}{5}ax^5-\frac{1}{2}ax^4+(\frac{5}{12}a-\frac{4}{3}c)x^3-(\frac{1}{8}a-2c)x^2 \end{aligned} [ax42ax3+(45a4c)x2(41a4c)x]dx=51ax521ax4+(125a34c)x3(81a2c)x2
然后令自变量 x 为 1 ,则 然后令自变量x为1,则 然后令自变量x1,则
1 5 a − 1 2 a + ( 5 12 a − 4 3 c ) − ( 1 8 a − 2 c ) = 1 24 a − 60 a + 50 a − 160 c − 15 a + 240 c = 120 − a + 80 x = 120 a = 80 c − 120 \begin{aligned} \frac{1}{5}a-\frac{1}{2}a+(\frac{5}{12}a-\frac{4}{3}c)-(\frac{1}{8}a-2c)&=1 \\ 24a-60a+50a-160c-15a+240c&=120 \\ -a+80x&=120 \\ a&=80c-120 \end{aligned} 51a21a+(125a34c)(81a2c)24a60a+50a160c15a+240ca+80xa=1=120=120=80c120
将 a = 80 c − 120 带入原式 1 5 a x 5 − 1 2 a x 4 + ( 5 12 a − 4 3 c ) x 3 − ( 1 8 a − 2 c ) x 2 ,可得 将a=80c-120带入原式\begin{aligned}\frac{1}{5}ax^5-\frac{1}{2}ax^4+(\frac{5}{12}a-\frac{4}{3}c)x^3-(\frac{1}{8}a-2c)x^2\end{aligned},可得 a=80c120带入原式51ax521ax4+(125a34c)x3(81a2c)x2,可得
1 5 ( 80 c − 120 ) x 5 − 1 2 ( 80 c − 120 ) x 4 + ( 5 12 ( 80 c − 120 ) − 4 3 c ) x 3 − ( 1 8 ( 80 c − 120 ) − 2 c ) x 2 ( 16 c − 24 ) x 5 − ( 40 c − 60 ) x 4 + ( 32 c − 50 ) x 3 − ( 8 c − 15 ) x 2 \begin{aligned} \frac{1}{5}(80c-120)x^5-\frac{1}{2}(80c-120)x^4+(\frac{5}{12}(80c-120)-\frac{4}{3}c)x^3-(\frac{1}{8}(80c-120)-2c)x^2 \\ (16c-24)x^5-(40c-60)x^4+(32c-50)x^3-(8c-15)x^2 \end{aligned} 51(80c120)x521(80c120)x4+(125(80c120)34c)x3(81(80c120)2c)x2(16c24)x5(40c60)x4+(32c50)x3(8c15)x2
但是这个函数要让计算得负担尽可能少又不会让效果变差,所以我们减少最后一项
8 c − 15 = 0 c = 15 8 \begin{aligned} 8c-15&=0 \\ c&=\frac{15}{8} \end{aligned} 8c15c=0=815
最后将c得值带入原式得出
( 16 × 15 8 − 24 ) x 5 − ( 40 × 15 8 − 60 ) x 4 + ( 32 × 15 8 − 50 ) x 3 − ( 8 × 15 8 − 15 ) x 2 6 x 5 − 15 x 4 + 10 x 3 \begin{aligned} (16\times\frac{15}{8}-24)x^5-(40\times\frac{15}{8}-60)x^4+(32\times\frac{15}{8}-50)x^3-(8\times\frac{15}{8}-15)x^2 \\ 6x^5-15x^4+10x^3 \end{aligned} (16×81524)x5(40×81560)x4+(32×81550)x3(8×81515)x26x515x4+10x3
综上所述,得出进阶版fade函数
6 x 5 − 15 x 4 + 10 x 3 6x^5-15x^4+10x^3 6x515x4+10x3

立方插值

注意:结果的数值均采取上一步的哈希值,其随机点 P ( 1.32 , 1.48 , 0 ) P(1.32, 1.48, 0) P(1.32,1.48,0),随机种子是157

  1. 获取正方体 A B C D A 1 B 1 C 1 D 1 ABCDA_1B_1C_1D_1 ABCDA1B1C1D1的八个顶点哈希值和x, y, z的fade函数值

A ( x 0 , y 0 , z 0 ) = a b c 、 B ( x 1 , y 0 , z 0 ) = a 1 b c 、 C ( x 0 , y 1 , z 0 ) = a b 1 c 、 D ( x 1 , y 1 , z 0 ) = a 1 b 1 c A(x_0,y_0,z_0)=abc、B(x_1, y_0,z_0)=a1bc、C(x_0,y_1,z_0)=ab1c、D(x_1,y_1,z_0)=a1b1c A(x0,y0,z0)=abcB(x1,y0,z0)=a1bcC(x0,y1,z0)=ab1cD(x1,y1,z0)=a1b1c
同理 A 1 ( x 0 , y 0 , z 1 ) = a b c 1 、 B 1 ( x 1 , y 0 , z 1 ) = a 1 b c 1 、 C 1 ( x 0 , y 1 , z 1 ) = a b 1 c 1 、 D 1 ( x 1 , y 1 , z 1 ) = a 1 b 1 c 1 A_1(x_0,y_0,z_1)=abc1、B_1(x_1, y_0,z_1)=a1bc1、C_1(x_0,y_1,z_1)=ab1c1、D_1(x_1,y_1,z_1)=a1b1c1 A1(x0,y0,z1)=abc1B1(x1,y0,z1)=a1bc1C1(x0,y1,z1)=ab1c1D1(x1,y1,z1)=a1b1c1

public static double lerp3(
		double deltaX,
        double deltaY,
        double deltaZ,
        double x0y0z0,
        double x1y0z0,
        double x0y1z0,
        double x1y1z0,
        double x0y0z1,
        double x1y0z1,
        double x0y1z1,
        double x1y1z1
) {
	return lerp(deltaZ, lerp2(deltaX, deltaY, x0y0z0, x1y0z0, x0y1z0, x1y1z0), lerp2(deltaX, deltaY, x0y0z1, x1y0z1, x0y1z1, x1y1z1));
}
  1. 采用lerp2方法对正方形ABCD进行插值整合,如下
public static double lerp2(double deltaX, double deltaY, double x0y0, double x1y0, double x0y1, double x1y1) {
	return lerp(deltaY, lerp(deltaX, x0y0, x1y0), lerp(deltaX, x0y1, x1y1));
}
  1. 采用lerp方法分别对AD方向、BC方向进行插值整合

方法:

public static double lerp(double delta, double start, double end) {
	return start + delta * (end - start);
}

AD方向:
∣ A D ∣ = a b c + Δ x × ( a 1 b c − a b c ) = 0.4224 |AD|=abc+\Delta x\times(a_1bc-abc)=0.4224 AD=abc+Δx×(a1bcabc)=0.4224
BC方向
∣ B C ∣ = a b 1 c + Δ x × ( a 1 b 1 c − a b 1 c ) = 0.6400 |BC|=ab_1c+\Delta x\times(a_1b_1c-ab_1c)=0.6400 BC=ab1c+Δx×(a1b1cab1c)=0.6400

  1. 采用lerp方法对AD方向和BC方向以及Y值进行插值整合

正方形ABCD:
∣ A B C D ∣ = ∣ A D ∣ + Δ y × ( ∣ B C ∣ − ∣ A D ∣ ) = 0.522496 ≈ 0.5225 |ABCD|=|AD|+\Delta y\times(|BC|-|AD|)=0.522496\approx0.5225 ABCD=AD+Δy×(BCAD)=0.5224960.5225

  1. 同理重复第2、3、4步一次,对 正方形 A 1 B 1 C 1 D 1 正方形A_1B_1C_1D_1 正方形A1B1C1D1进行插值整合

A 1 D 1 A_1D_1 A1D1方向:
∣ A 1 D 1 ∣ = a b c 1 + Δ x × ( a 1 b c 1 − a b c 1 ) = 0.1936 |A_1D_1|=abc_1+\Delta x\times(a_1bc_1-abc_1)=0.1936 A1D1=abc1+Δx×(a1bc1abc1)=0.1936
B 1 C 1 B_1C_1 B1C1方向:
∣ B C ∣ = a b 1 c 1 + Δ x × ( a 1 b 1 c 1 − a b 1 c 1 ) = 1.3968 |BC|=ab_1c_1+\Delta x\times(a_1b_1c_1-ab_1c_1)=1.3968 BC=ab1c1+Δx×(a1b1c1ab1c1)=1.3968
正方形 A 1 B 1 C 1 D 1 A_1B_1C_1D_1 A1B1C1D1
∣ A 1 B 1 C 1 D 1 ∣ = ∣ A 1 D 1 ∣ + Δ y × ( ∣ B 1 C 1 ∣ − ∣ A 1 D 1 ∣ ) = 0.7479 |A_1B_1C_1D_1|=|A_1D_1|+\Delta y\times(|B_1C_1|-|A_1D_1|)=0.7479 A1B1C1D1=A1D1+Δy×(B1C1A1D1)=0.7479

  1. 最后对正方体 A B C D A 1 B 1 C 1 D 1 ABCDA_1B_1C_1D_1 ABCDA1B1C1D1整体进行插值整合,如下

正方体:
r e s u l t = ∣ A B C D ∣ + Δ z × ( ∣ A 1 B 1 C 1 D 1 ∣ − ∣ A B C D ∣ ) = 0.5225 result=|ABCD|+\Delta z\times(|A_1B_1C_1D_1|-|ABCD|)=0.5225 result=ABCD+Δz×(A1B1C1D1ABCD)=0.5225
最后的结果必需 [ − 1 , 1 ] [-1,1] [1,1]区间范围内经过实地测试计算正确结果是:-0.0227

图样效果

其中 P ( 0.32 , 0.48 , 0 ) P(0.32,0.48,0) P(0.32,0.48,0)是500*5000像素窗口中一个随机点,每个一个随机点对应不同哈希值,且返回值都在 [ − 1 , 1 ] [-1,1] [1,1]区间内

public class PerlinNoiseSamplerImpl extends JPanel {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Perlin Noise");
        PerlinNoiseSamplerImpl panel = new PerlinNoiseSamplerImpl();
        frame.add(panel);
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (int x = 0; x < getWidth(); x++) {
            for (int y = 0; y < getWidth(); y++) {
                double noise = new PerlinNoiseSampler(new Random(157)).sample(x / 50.0, y / 50.0, 0);
                int color = (int) ((noise + 1) / 2 * 255);
                g.setColor(new Color(color, color, color));
                g.fillRect(x, y, 1, 1);
            }
        }
    }
}

随机种子157图片
水晶杂谈4:手撕柏林噪声源码,跳转随机领域展望无限_第3张图片
完整代码请看下一篇:柏林函数

游戏实现

受篇幅所限,请听下回分解~~

参考

  1. 一篇文章搞懂柏林噪声算法:https://www.cnblogs.com/leoin2012/p/7218033.html
  2. Math.round(),Math.ceil(),Math.floor()的区别:https://blog.csdn.net/zuihongyan518/article/details/96978200
  3. Minecraft地形生成与柏林噪声 (一) 详解噪声:https://www.bilibili.com/video/BV11V4y1M7N6
  4. 柏林噪声(Perlin Noise) 介绍及应用:https://blog.csdn.net/Game_jqd/article/details/131112116
  5. 博客园的柏林噪声算法(Perlin Noise):https://www.cnblogs.com/hggzhang/p/17269270.html
  6. 知乎的Perlin noise(柏林噪声):https://zhuanlan.zhihu.com/p/721568930
  7. 构建真实感海洋:2D Perlin Noise技术应用:https://blog.csdn.net/weixin_42163404/article/details/148308142
  8. 博客园的[数学][转载][柏林噪声]:https://www.cnblogs.com/Memo/archive/2008/09/08/1286963.html
  9. 创造自己的世界——Minecraft 1.18的地形生成(一):https://www.bilibili.com/opus/618817672540006827
  10. Perlin噪声与Simplex噪声笔记:https://zhuanlan.zhihu.com/p/240763739
  11. 伪随机噪声(四)—— 柏林噪声:https://zhuanlan.zhihu.com/p/655246970
  12. 【Aegisub】随机烟雾、随机地形与柏林噪声简介:https://www.bilibili.com/opus/704183094004940851
  13. Perlin Noise:https://adrianb.io/2014/08/09/perlinnoise.html
  14. [图形技术科普] 如何以通俗易懂的方式理解柏林噪声的原理?:ttps://www.bilibili.com/video/BV18kZQYEEj8
  15. 柏林噪声-fade函数究竟是什么:https://www.bilibili.com/video/BV1jx41197Qs

你可能感兴趣的:(水晶杂谈,算法,噪声,柏林噪声,我的世界,MC,Fabric,Java)