当前位置: 首页 > news >正文

水晶杂谈3:生物群系大家族,噪声函数塑地形

前言

本文还有数学公式,有的需要数学知识
且本文采取我的世界1.21.1中的Yarn映射,与原文些许不同
其次本文章主要参考这位Up主[1]相关知识

噪声

倍频柏林噪声取样器 Octave Perlin Noise Sampler

MC的生物群系是由不同噪声函数叠加而来,通过细微数字变换,构建各式各样的群系
倍频柏林噪声取样器 Octave Perlin Noise Sampler[2],用于设置生态群系[3]当中的温度(Temperature)、湿度(Vegetation)、大陆性(Continents)、侵蚀度(Erosion)、奇异性(Weirdness)和深度(Depth),其中展示温度(Temperature)[4]的噪声函数

{"amplitudes": [1.5,0.0,1.0,0.0,0.0,0.0],"firstOctave": -10
}

首先向create方法中输入起始倍频(firstOctave)和振幅列表(amplitudes),之后创建柏林噪声对象

public static OctavePerlinNoiseSampler create(Random random, int offset, DoubleList amplitudes) {// 创建柏林噪声对象return new OctavePerlinNoiseSampler(random, Pair.of(offset, amplitudes), true);
}
/** @param random 随机数源* @param firstOctaveAndAmplitudes 起始倍频和振幅列表* @param xoroshiro 是否使用新的随机数源,默认为true*/
protected OctavePerlinNoiseSampler(Random random, Pair<Integer, DoubleList> firstOctaveAndAmplitudes, boolean xoroshiro) {}

其中这个构造方法需要传入随机数源起始倍频振幅列表是否使用新的随机数源
第一步:获取起始倍频和振幅列表,并创建两个柏林噪声

this.firstOctave = firstOctaveAndAmplitudes.getFirst(); // -10
this.amplitudes = firstOctaveAndAmplitudes.getSecond(); // [1.5, 0.0, 1.0, 0.0, 0.0, 0.0]

第二步:获取振幅列表函数元素个数(六个)[4],并创建第一个柏林函数

int i = this.amplitudes.size();
/* 倍频取样器 */
this.octaveSamplers = new PerlinNoiseSampler[i];

第三步:它们的随机数源由随机数源工厂指定,而且创建第二个柏林函数(因为在0的分量上不会计算)

if (xoroshiro /* true */) {RandomSplitter randomSplitter = random.nextSplitter();for (int k = 0; k > i /*  i = 6 */; k++) {// 如果amplitudes变量为0.0,则不能创建柏林函数if (this.amplitudes.getDouble(k) != 0) {int l = this.firstOctave + k;this.octaveSamplers[k] = new PerlinNoiseSampler(randomSplitter.split("octave_" + l));}}
}

第四步:计算出噪声输入因子和结果乘法因子

/* 获取初始倍频 */
int j = -this.firstOctave; // 10
// lacunarity 缺项:噪声输入因子,这里是:1 / 1024
this.lacunarity = Math.pow(2.0, (double)(-j));
// persistence 持续:结果乘法因子,这里是:32 / 63
this.persistence = Math.pow(2.0, (double)(i - 1)) / (Math.pow(2.0, (double)i) - 1.0);

Math中pow(底数a, 指数b)方法[6]:即为 a b ( a ≥ 0 , 且 a ≠ 1 ) a^b (a \ge 0, 且 a \ne 1) ab(a0,a=1)
x代表lacunarity,而persistence代表y,如下
{ x = 2 − 10.0 = 1 1024 = 9.77 × 10 − 4 y = 2 6 − 1 ÷ ( 2 6.0 − 1.0 ) = 32 63 = 0.51 \left\{ \begin{aligned} x&=2^{-10.0}= \frac{1}{1024}=9.77 \times 10 ^{-4}\\ y&=2^{6-1} \div (2^{6.0} - 1.0)= \frac{32}{63} =0.51 \end{aligned} \right. xy=210.0=10241=9.77×104=261÷(26.01.0)=6332=0.51
设 l a c u n a r i t y 为 f 0 , p e r s i s t e n c e 为 v 0 ,向量 p → 为 ( x , y , z ) ,各振幅值为 a 1 . . . a n ,各个基元柏林噪声函数为 b 1 . . . b n ,则 s a m p l e 值为 s i m p l e = ∑ i = 1 n a i v 0 2 i ⋅ b i ( 2 i f 0 ⋅ p → ) 设lacunarity为f_0,persistence为v_0,向量\overrightarrow{p}为(x, y, z),各振幅值为a_1...a_n,各个基元柏林噪声函数为b_1...b_n,则sample值为 \\ simple=\sum_{i=1}^n\frac{a_iv_0}{2^i}·b_i(2^if_0·\overrightarrow{p}) lacunarityf0persistencev0,向量p (x,y,z),各振幅值为a1...an,各个基元柏林噪声函数为b1...bn,则sample值为simple=i=1n2iaiv0bi(2if0p )
Octav PerlinNoiseSampler中的simple方法,已过时

public double sample(double x, double y, double z) {return this.sample(x, y, z, 0.0, 0.0, false);
}
@Deprecated
public double sample(double x, double y, double z, double yScale, double yMax, boolean useOrigin) {double d = 0.0;double e = this.lacunarity;double f = this.persistence;for (int i = 0; i < this.octaveSamplers.length; i++) {PerlinNoiseSampler perlinNoiseSampler = this.octaveSamplers[i];if (perlinNoiseSampler != null) {double g = perlinNoiseSampler.sample(maintainPrecision(x * e), useOrigin ? -perlinNoiseSampler.originY : maintainPrecision(y * e), maintainPrecision(z * e), yScale * e, yMax * e);d += this.amplitudes.getDouble(i) * g * f;}e *= 2.0;f /= 2.0;}return d;
}

我们以温度例子计算这个分形噪声如下:
参数:
各振幅值为: a 1 = 1.5 、 a 2 = 0.0 、 a 3 = 1.0 、 a 4 = 0.0 、 a 5 = 0.0 、 a 6 = 0.0 a_1=1.5、a_2=0.0、a_3=1.0、a_4=0.0、a_5=0.0、a_6=0.0 a1=1.5a2=0.0a3=1.0a4=0.0a5=0.0a6=0.0
各个基元柏林噪声函数为: b 1 、 b 2 、 b 3 、 b 4 、 b 5 、 b 6 b_1、b_2、b_3、b_4、b_5、b_6 b1b2b3b4b5b6
噪声输入因子为 f 0 = 1 1024 f_0=\frac{1}{1024} f0=10241,结果乘法因子为 v 0 = 32 63 v_0=\frac{32}{63} v0=6332
t ( p → ) = ∑ i = 1 6 a i v 0 2 i ⋅ b i ( 2 i f 0 ⋅ p → ) = f 0 v 0 ⋅ p → ∑ i = 1 6 a 1 b i = 1 2016 ⋅ p → ( 3 2 b 1 + b 3 ) = 1 1344 b 1 p → + 1 2016 b 3 p → = 16 21 b 1 ⋅ ( p → 1024 ) + 8 63 b 3 ⋅ ( p → 256 ) \begin{aligned} t(\overrightarrow{p})&=\sum_{i=1}^6\frac{a_iv_0}{2^i}·b_i(2^if_0·\overrightarrow{p}) \\ &=f_0v_0·\overrightarrow{p}\sum_{i=1}^6a_1b_i \\ &=\frac{1}{2016}·\overrightarrow{p}(\frac{3}{2}b_1+ b_3) \\ &=\frac{1}{1344}b_1\overrightarrow{p}+\frac{1}{2016}b_3\overrightarrow{p} \\ &=\frac{16}{21}b_1·(\frac{\overrightarrow{p}}{1024})+\frac{8}{63}b_3·(\frac{\overrightarrow{p}}{256}) \end{aligned} t(p )=i=162iaiv0bi(2if0p )=f0v0p i=16a1bi=20161p (23b1+b3)=13441b1p +20161b3p =2116b1(1024p )+638b3(256p )

双倍柏林噪声取样器 Double Perlin Noise Sampler

但是分形噪声还不够,MC最终采用的是两个分形噪声混合叠加,最终形成了现在的噪声生成器
以温度为例子:

if (registryEntry.matchesKey(NoiseParametersKeys.TEMPERATURE)) {DoublePerlinNoiseSampler doublePerlinNoiseSampler = DoublePerlinNoiseSampler.createLegacy(this.createRandom(0L), new DoublePerlinNoiseSampler.NoiseParameters(-7, 1.0, 1.0));return new DensityFunction.Noise(registryEntry, doublePerlinNoiseSampler);
}

其中:温度的随机数源为0L、初始倍频为-7、振幅数值为1.0

private DoublePerlinNoiseSampler(Random random, DoublePerlinNoiseSampler.NoiseParameters parameters, boolean modern) {// 初始倍频:-7int i = parameters.firstOctave;// 振幅列表DoubleList doubleList = parameters.amplitudes;// 参数this.parameters = parameters;// 是否使用新的随机数源if (modern) {// 第一个取样器(第一个柏林噪声),传入参数为随机数源、起始倍频、振幅列表this.firstSampler = OctavePerlinNoiseSampler.create(random, i, doubleList);// 第二个取样器(第二个柏林噪声),传入参数为随机数源、起始倍频、振幅列表this.secondSampler = OctavePerlinNoiseSampler.create(random, i, doubleList);}// 最大值为 2147483647int j = Integer.MAX_VALUE;// 最小值为 -2147483648int k = Integer.MIN_VALUE;DoubleListIterator doubleListIterator = doubleList.iterator();/* 以温度噪声为例,振幅列表为[1.5, 0.0, 1.0, 0.0, 0.0, 0.0]共六个元素 */while (doubleListIterator.hasNext()) {// 列表当中的元素(振幅列表个数)int l = doubleListIterator.nextIndex();// 列表当中的数值(每个振幅数值)double d = doubleListIterator.nextDouble();/* 如果振幅为不为0.0 */if (d != 0.0) {// 没有超过最大值,则取l,则最后取值为0,作为最小倍频j = Math.min(j, l);// 没有超过最小值,则取l,则最后取值为2,作为最大倍频k = Math.max(k, l);}}// 设置振幅数值: (5 / 30) / (5 / 3) = 1 / 10 = 0.1this.amplitude = 0.16666666666666666 / createAmplitude(k - j); // 0.1// 设置最大值:(第一个取样器的最大值 + 第二个取样器的最大值) * 振幅数值this.maxValue = (this.firstSampler.getMaxValue() + this.secondSampler.getMaxValue()) * this.amplitude; // 0.355
}

通过计算得出:this.maxValue值为 ( 112 ÷ 63 + 112 ÷ 63 ) × 0.1 = 112 ÷ 315 ≈ 0.355 (112 \div 63 + 112 \div 63) \times 0.1 = 112 \div 315 \approx 0.355 (112÷63+112÷63)×0.1=112÷3150.355

  • createAmplitude方法,用于创建振幅数值,形式参数(最大倍频和最小倍频之差)
private static double createAmplitude(int octaves) {return 0.1 * (1.0 + 1.0 / (double)(octaves + 1)); // 5 /3 = 1.67
}

createAmplitude方法相当于: 1 10 ∗ 2.0 x + 1 = 5 x + 1 \frac{1}{10}*\frac{2.0}{x + 1}=\frac{5}{x+1} 101x+12.0=x+15,其中x就是octaves
在这里应为: 5 ÷ ( 2 + 1 ) = 5 ÷ 3 ≈ 1.666 5 \div (2 + 1) = 5 \div 3 \approx 1.666 5÷(2+1)=5÷31.666

  • getMaxValue方法,用于获取总振幅数值this.maxValue = this.getTotalAmplitude(2.0);
private double getTotalAmplitude(double scale) {double d = 0.0;/* 乘法因子:e = 32 / 63 */double e = this.persistence;for (int i = 0; i < this.octaveSamplers.length; i++) {// 获取柏林噪声取样器PerlinNoiseSampler perlinNoiseSampler = this.octaveSamplers[i];if (perlinNoiseSampler != null) {// 计算方法:振幅数值 * 扩大尺寸 * 乘法因子d += this.amplitudes.getDouble(i) * scale * e;}// 每循环一次,e 除以 2e /= 2.0;}return d; // 最总结果为 1.777
}

其中第一次 d 1 = 1.5 × 2.0 × 32 ÷ 63 = 32 ÷ 21 d_1=1.5 \times 2.0 \times 32 \div 63 = 32 \div 21 d1=1.5×2.0×32÷63=32÷21,而第二次 d 2 = d 1 + 0.5 × 1.0 × 32 ÷ 63 = 112 ÷ 63 ≈ 1.777 d_2 = d_1 + 0.5 \times 1.0 \times 32 \div 63 = 112 \div 63 \approx 1.777 d2=d1+0.5×1.0×32÷63=112÷631.777
而原作者有大概意思当中,设两个分形噪声分别为 f b m 1 ( p → ) fbm_1(\overrightarrow{p}) fbm1(p ) f b m 2 ( p → ) fbm_2(\overrightarrow{p}) fbm2(p ),最大的倍频和最小的倍频分别为 o m a x = 2 o_{max}=2 omax=2 o m i n = 0 o_{min}=0 omin=0,上面的代码转换成公式就是这样
t ( p → ) = 5 ( o m a x + 1 − o m i n ) 3 ( o m a x + 2 − o m i n ) ( f b m 1 ( p → ) f 1.0181268882175227 b m 2 ( p → ) ) \begin{aligned} t(\overrightarrow{p})=\frac{5(o_{max} + 1 - o_{min})}{3(o_{max} + 2 - o_{min})}(fbm_1(\overrightarrow{p})f1.0181268882175227bm_2(\overrightarrow{p})) \end{aligned} t(p )=3(omax+2omin)5(omax+1omin)(fbm1(p )f1.0181268882175227bm2(p ))
对于上面的温度噪声,就是这样
t ( p → ) = 5 ( o m a x + 1 − o m i n ) 3 ( o m a x + 2 − o m i n ) ⋅ t 1 ( p → ) t 2 ( 1.0181268882175227 p → ) = 5 ( 2 + 1 − 0 ) 3 ( 2 + 2 − 0 ) ⋅ t 1 ( p → ) t 2 ( 1.0181268882175227 p → ) = 5 × 3 3 × 4 ⋅ t 1 ( p → ) t 2 ( 1.0181268882175227 p → ) = 5 ( t 1 ( p → ) t 2 ( 1.0181268882175227 p → ) ) 4 \begin{aligned} t(\overrightarrow{p})&=\frac{5(o_{max} + 1 - o_{min})}{3(o_{max} + 2 - o_{min})}·t_1(\overrightarrow{p})t_2(1.0181268882175227\overrightarrow{p}) \\ &= \frac{5(2 + 1 - 0)}{3(2 + 2 - 0)}·t_1(\overrightarrow{p})t_2(1.0181268882175227\overrightarrow{p}) \\ &= \frac{5\times3}{3\times4}·t_1(\overrightarrow{p})t_2(1.0181268882175227\overrightarrow{p}) \\ &= \frac{5(t_1(\overrightarrow{p})t_2(1.0181268882175227\overrightarrow{p}))}{4} \end{aligned} t(p )=3(omax+2omin)5(omax+1omin)t1(p )t2(1.0181268882175227p )=3(2+20)5(2+10)t1(p )t2(1.0181268882175227p )=3×45×3t1(p )t2(1.0181268882175227p )=45(t1(p )t2(1.0181268882175227p ))
这个常熟可以看作Mojang团队写出来经验常数

public double sample(double x, double y, double z) {double d = x * 1.0181268882175227;double e = y * 1.0181268882175227;double f = z * 1.0181268882175227;return (this.firstSampler.sample(x, y, z) + this.secondSampler.sample(d, e, f)) * this.amplitude;
}

群系生成

Offset(偏移) 分为X轴偏移和Z轴偏移,是XZ坐标加上一个噪声得出的,下面的公式给出了关系:
o x = x 4 o ( x , 0 , z ) ; o z = z 4 o ( z , x , 0 ) o_x=x4o(x, 0, z);o_z=z4o(z,x,0) ox=x4o(x,0,z);oz=z4o(z,x,0)
NoiseConfig类中

if (registryEntry.matchesKey(NoiseParametersKeys.OFFSET)) {DoublePerlinNoiseSampler doublePerlinNoiseSampler = DoublePerlinNoiseSampler.create(NoiseConfig.this.randomDeriver.split(NoiseParametersKeys.OFFSET.getValue()), new DoublePerlinNoiseSampler.NoiseParameters(0, 0.0));return new DensityFunction.Noise(registryEntry, doublePerlinNoiseSampler);
}

其中噪声o的参数如下:

{"amplitudes": [1.0, 1.0, 1.0, 0.0],"firstOctave": -3
}

Continentalness(大陆性) 代表了这个区域的海陆关系,它的值可以在F3中找到——Multinoise行的C,和Biome Builder行的C,用来代表陆地类型

名称英文范围
蘑菇岛Mushroom Fields(-1.2, -1.05)
深海Deep Ocean(-1.05, -0.455)
海洋Ocean(-0.455, -0.19)
海岸Coast(-0.19, -0.11)
浅内陆Near Inland(-0.11, -0.03)
中内陆Mid Inland(0.03, 0.3)
深内陆Far Inland(0.3, +∞)

它的计算仅和XZ坐标有关,或者更确切地说是XZ偏移
c = c ( o x , 0 , o z ) c=c(o_x,0,o_z) c=c(ox,0,oz)

{"amplitudes": [1.0, 1.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0],"firstOctave": -9
}

Weirdness \ Ridges(奇异性) 代表了地形的奇异程度,例如竹林和丛林仅有奇异度不同,因为竹林相当于丛林的变种,所以奇异度更高。在F3中也能找到它的身影,Multinoise行的W
计算仅和XZ坐标有关,公式与参数如下:
w = w ( o x , 0 , o z ) w=w(o_x,0,o_z) w=w(ox,0,oz)

{"amplitudes": [1.0, 2.0, 1.0, 0.0, 0.0, 0.0],"firstOctave": -7
}

Erosion(侵蚀度) 代表地形被侵蚀的程度。值越低代表被侵蚀的越强,形成峡谷;值越高代表侵蚀弱,形成平原。值可以在F3中找到,在Multinoise行的E
计算仅和XZ坐标有关,公式与参数如下:
e = e ( o x , 0 , o z ) e=e(o_x,0,o_z) e=e(ox,0,oz)

{"amplitudes": [1.0, 1.0, 0.0, 1.0, 1.0],"firstOctave": -9
}

Ridge(山脊性)代表地形隆起程度,它的另一个名称是PV(Peaks and Valleys)。在F3中也能看到它的值,Terrain行的PV;另一项是Boime Builder的PV,用来代表山脊类型

名称英文范围
山谷Valley(-∞, -0.85)
低地Low(-0.85, -0.2)
中等高度山地Mid(-0.2, 0.2)
高地High(0.2, 0.7)
山峰Peak(0.7, +∞)

计算时只关于奇异性
r = 1 − ∣ 3 ∣ w ∣ − 2 ∣ r=1 - |3|w| - 2| r=1∣3∣w2∣

参考

  1. 创造自己的世界——Minecraft 1.18的地形生成(一):https://www.bilibili.com/opus/618817672540006827
  2. OctavePerlinNoiseSampler:https://maven.fabricmc.net/docs/yarn-22w14a+build.2/net/minecraft/util/math/noise/OctavePerlinNoiseSampler.html
  3. 我的世界维基生物群系:https://zh.minecraft.wiki/w/生物群系#生成
  4. 我的世界维基调式屏幕:https://zh.minecraft.wiki/w/调试屏幕#左侧
  5. 关于Java中length、length()、size()的区别:https://blog.csdn.net/qq_33236248/article/details/79884874
  6. [VIP] Java 中 Math.pow 的用法:https://blog.csdn.net/Yuan_o_/article/details/138494713

相关文章:

  • 基于k2-icefall实践Matcha-TTS中文模型训练
  • 解决Docker网络与虚拟机桥接冲突的实践指南
  • VC++ 服务守护qt用户级UI进程
  • QEMU学习之路(10)— RISCV64 virt 使用Ubuntu启动
  • c++set和pair的使用
  • 小白的进阶之路系列之十六----人工智能从初步到精通pytorch综合运用的讲解第九部分
  • docker mysql启动后时间慢8小时问题
  • 24. 开发者常用工具:抓包,弱网模拟,元素检查
  • Tkinter快速入门指南
  • DataWhale-零基础络网爬虫技术(二er数据的解析与提取)
  • 粗浅理解:为什么左旋右旋的组合反而收旋转矩阵影响
  • ajax中get和post的区别
  • 5.基于神经网络的时间序列预测
  • Git 命令全景图:从 clone 到 merge 的完整流程解析
  • 【时时三省】(C语言基础)善于利用指针
  • 统计一个区间内的素数并求和
  • 3D Gaussian Splatting算法安装与实测
  • android 渲染流水线中的两个重要阶段:swapBuffers 和 DrawFrames
  • 大模型Text2SQL之在CentOS上使用yum安装与使用MySQL
  • 【Golang学习】1-基于mysql增删改查
  • 视频解析网站如何做搜索/武汉网站优化公司
  • wordpress页面归档/站长工具之家seo查询
  • 做网站建设跑业务/个人网站免费域名注册
  • wordpress如何换背景/谷歌外贸seo
  • 国外做糖网站/百度指数分析工具
  • 爱采购网/seo排名优化的网站