该文章参考1.21.1 Java版Yarn映射,详细分析柏林噪声
本文存在许多数学公式,可以更好理解文章
将排列表取名为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+1,y1=y0+1,z1=z0+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取值为1,sectionY取值为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 72、80、129、171),接着加入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 abc、a1bc、ab1c、a1b1c、abc1、a1bc1、ab1c1、a1b1c1这八个顶点梯度向量
设 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=g1ab1c=g2a1bc=g3a1b1c=g4abc1=g5ab1c1=g6a1bc1=g7a1b1c1=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)=6x5−15x4+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函数和一次函数交于点 ( 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,如图:
其中改进版函数,可以应用在凹凸映射以及位移贴图等计算要求,这两种计算都基于法线,其中计算法线先进行计算切线,计算切线需要进行求一阶导数,所以要求一阶导数之上也要满足连续性[3]
如何得到改进版函数?[15],主要参考资料[15]
首先函数必需满足以下条件:
其中 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(x−0.5)4+b(x−0.5)2+ca(x4−2x3+23x2−21x+161)+b(x2−x+41)+cax4−2ax3+(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+c为0,所以b=−41a−4c,带入式子得
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 ax4−2ax3+(45a−4c)x2−(41a−4c)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} ∫[ax4−2ax3+(45a−4c)x2−(41a−4c)x]⋅dx=51ax5−21ax4+(125a−34c)x3−(81a−2c)x2
然后令自变量 x 为 1 ,则 然后令自变量x为1,则 然后令自变量x为1,则
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} 51a−21a+(125a−34c)−(81a−2c)24a−60a+50a−160c−15a+240c−a+80xa=1=120=120=80c−120
将 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=80c−120带入原式51ax5−21ax4+(125a−34c)x3−(81a−2c)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(80c−120)x5−21(80c−120)x4+(125(80c−120)−34c)x3−(81(80c−120)−2c)x2(16c−24)x5−(40c−60)x4+(32c−50)x3−(8c−15)x2
但是这个函数要让计算得负担尽可能少又不会让效果变差,所以我们减少最后一项
8 c − 15 = 0 c = 15 8 \begin{aligned} 8c-15&=0 \\ c&=\frac{15}{8} \end{aligned} 8c−15c=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×815−24)x5−(40×815−60)x4+(32×815−50)x3−(8×815−15)x26x5−15x4+10x3
综上所述,得出进阶版fade函数
6 x 5 − 15 x 4 + 10 x 3 6x^5-15x^4+10x^3 6x5−15x4+10x3
注意:结果的数值均采取上一步的哈希值,其随机点 P ( 1.32 , 1.48 , 0 ) P(1.32, 1.48, 0) P(1.32,1.48,0),随机种子是157
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)=abc、B(x1,y0,z0)=a1bc、C(x0,y1,z0)=ab1c、D(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)=abc1、B1(x1,y0,z1)=a1bc1、C1(x0,y1,z1)=ab1c1、D1(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));
}
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));
}
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×(a1bc−abc)=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×(a1b1c−ab1c)=0.6400
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×(∣BC∣−∣AD∣)=0.522496≈0.5225
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×(a1bc1−abc1)=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×(a1b1c1−ab1c1)=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×(∣B1C1∣−∣A1D1∣)=0.7479
正方体:
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×(∣A1B1C1D1∣−∣ABCD∣)=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);
}
}
}
}
受篇幅所限,请听下回分解~~