創(chuàng)造自己的世界——Minecraft 1.18的地形生成(一)

在1.18新版本中,MC的地形發(fā)生了翻天覆地的變化。它們采用了新的算法用于雕刻地形、應(yīng)用群系等。接下來(lái),我將會(huì)一步一步講解這個(gè)地形算法。
警告:下文可能有非常多且復(fù)雜的函數(shù)公式!
一.三次插值函數(shù)(Cubic Spline)
為了地形生成有比較平滑的效果,對(duì)于輸入的數(shù)據(jù),MC使用了三次Hermite插值(有意思的是類名寫的是三次樣條,但是這兩個(gè)不是一個(gè)東西)。它的定義如下:
它有兩個(gè)子類,一個(gè)是Constant,代表了常量;另一個(gè)是Multipoint,采用了Hermite插值。
先說說Constant,它返回的就是value這個(gè)值,不會(huì)進(jìn)行任何插值計(jì)算。
另一個(gè),Multipoint,它是根據(jù)locations(插值點(diǎn)自變量的值)、value(插值點(diǎn)因變量的值)和derivatives(插值點(diǎn)的一階導(dǎo)數(shù)值)采用分段兩點(diǎn)三次Hermite插值計(jì)算。這個(gè)算法的數(shù)學(xué)表述如下:
假設(shè)有個(gè)插值點(diǎn),
如果:
如果:
如果:
具體代碼如下:
二.偽隨機(jī)數(shù)源
Java中內(nèi)置了Random類提供偽隨機(jī)數(shù)。在1.18之前,MC一直使用的也是它。
Java內(nèi)置的隨機(jī)數(shù)算法是線性同余算法(LCG),它的算法代碼如下:
可以看出,這個(gè)算法會(huì)利用long中的后48位作為有效位,因此對(duì)于同余的種子在經(jīng)過算法計(jì)算后會(huì)產(chǎn)生同樣的結(jié)果,這導(dǎo)致了在老版本的世界生物群系生成中海洋圖像種子只要與
同余就會(huì)相同(但是因?yàn)榱硪粋€(gè)圖像奇偶性問題使世界不會(huì)一模一樣),在更老的版本中甚至導(dǎo)致了世界一模一樣。
老版本的世界生物群系生成:

LCG的安全性比較低,可以逆推得到種子。
在1.18,加入了一種新的偽隨機(jī)數(shù)算法,Xoroshiro128++偽隨機(jī)算法。對(duì)于生成高斯分布的隨機(jī)數(shù),采用了Marsaglia Polar算法。Xoroshiro128++偽隨機(jī)算法如下:
當(dāng)然,加入新的偽隨機(jī)數(shù)源不意味著舍棄了舊的源。當(dāng)我們?cè)谠肼暽善鲀?nèi)將legacy_random_source設(shè)置為true時(shí)就會(huì)使用原先的偽隨機(jī)數(shù)發(fā)生器,默認(rèn)是false,也就是使用新的發(fā)生器。
三.噪聲
1.18地形的最大更新就是“噪聲”(其實(shí)之前也有)。噪聲是一個(gè)圖形學(xué)概念,用于模擬自然情況下的隨機(jī)數(shù)。在MC中使用了很多種噪聲,主要使用的是Perlin噪聲、Simplex噪聲和它們的組合。
下面是一個(gè)簡(jiǎn)單的Perlin噪聲的圖像,使用了MC中ImprovedNoise的源碼轉(zhuǎn)換成GLSL語(yǔ)言編寫而成:

可以看到單個(gè)柏林噪聲的圖像并不太符合真正的地形參數(shù),所以MC采用了分疊加,將不同的柏林噪聲疊加起來(lái)。它的代碼是這樣的:
解讀一下這段代碼:
這段代碼要求輸入一個(gè)隨機(jī)數(shù)源、一個(gè)起始倍頻、一個(gè)振幅列表和是否使用新的隨機(jī)數(shù)源(這個(gè)通常是true)。以MC內(nèi)置的普通溫度(temperature)噪聲參數(shù)舉例,傳入這個(gè)構(gòu)造函數(shù)的參數(shù)是:
根據(jù)上面的代碼,首先,我們創(chuàng)建2個(gè)柏林噪聲(因?yàn)樵?的分量上不會(huì)計(jì)算),它們的隨機(jī)數(shù)源由隨機(jī)數(shù)源工廠指定。
接著,計(jì)算出第一個(gè)噪聲輸入因子和第一個(gè)結(jié)果乘法因子。
接下來(lái),我們把lowestFreqInputFactor簡(jiǎn)寫成,lowestFreqValueFactor簡(jiǎn)寫成
,將輸入的x、y、z坐標(biāo)簡(jiǎn)寫成輸入向量
,各振幅值使用
代替,各個(gè)基元柏林噪聲函數(shù)用
代替。那么,getValue的值可以用下面的公式表示:
可以看到,振幅值越靠后對(duì)于噪聲的貢獻(xiàn)越小。
在這個(gè)例子中,這個(gè)分形噪聲的計(jì)算如下:
但是分形噪聲還不夠,MC最終采用的是兩個(gè)分形噪聲混合疊加,最終形成了現(xiàn)在的噪聲生成器。
設(shè)兩個(gè)分形噪聲是(注意這兩個(gè)噪聲構(gòu)建時(shí)的代碼看起來(lái)一樣,但是因?yàn)镽NG被計(jì)算了幾次,所以兩個(gè)噪聲不一樣),最大的倍頻和最小的倍頻分別為
,上面的代碼轉(zhuǎn)換成公式就是這樣:
對(duì)于上面的溫度噪聲,就是這樣:
對(duì)于這個(gè)怪異的常量,我們最終也找不到它是怎么組成的,暫且認(rèn)為是Mojang瞎打的吧)
下面,是這個(gè)噪聲的圖像表示(y取0,這也是MC的做法):

對(duì)照真實(shí)的MC世界,我們可以看出它們基本一致:

四.區(qū)塊生成次序
講完了基礎(chǔ)的數(shù)學(xué)方面,我們來(lái)看看MC是怎么組織區(qū)塊的生成的。
區(qū)塊生成有一定階段,在ChunkStatus里面是這樣寫的:
EMPTY("empty") - 區(qū)塊沒有被加載,也不在生成當(dāng)中,只存在于內(nèi)存中,區(qū)塊內(nèi)部的所有數(shù)據(jù)未知
STRUCTURES_STARTS("structures_starts") - 區(qū)塊沒有被加載,但是在生成(之后的所有階段都是在生成之中)。這個(gè)階段計(jì)算結(jié)構(gòu)生成起始點(diǎn)。這里的結(jié)構(gòu)指村莊、要塞、神殿、寶藏等的多方塊聯(lián)合復(fù)雜結(jié)構(gòu)。
STRUCTURE_REFERENCES("structure_references") - 將結(jié)構(gòu)生成起始點(diǎn)保存到地物存儲(chǔ)中。
BIOMES("biomes") - 計(jì)算群系并保存,此時(shí)地形地貌甚至任何方塊都沒有生成。
NOISE("noise") - 計(jì)算地形噪聲,生成起伏的地形,也生成了1.18特有的洞穴、含水層與礦脈,但是地形表面和地物都沒有生成。
SURFACE("surface") - 計(jì)算地形的表面生成。地表的景物隨著群系不同而不同,例如凍洋的冰,惡地的陶瓦等。
CARVERS("carvers") - 老版本的洞穴雕刻生成,生成普通的洞穴結(jié)構(gòu)。
LIQUID_CARVERS("liquid_carvers") - 在1.18中這個(gè)階段沒有意義。
FEATURES("features") - 地物生成。地物包括了很多東西,礦物的生成、樹的生成、結(jié)構(gòu)的放置都屬于這個(gè)階段的事。注意結(jié)構(gòu)雖然在最開始就確定了位置,但是在這個(gè)階段才開始生成。高度表也在這時(shí)生成。
LIGHT("light") - 啟動(dòng)光照引擎計(jì)算區(qū)塊光照。
SPAWN("spawn") - 生成區(qū)塊附帶的生物,如草原上的原生動(dòng)物。結(jié)構(gòu)生物不在此階段生成,它們和結(jié)構(gòu)一樣在上一階段就生成了。
HEIGHTMAPS("heightmaps") - 1.18中這個(gè)階段沒有意義,在這個(gè)階段之前高度表就生成了。
FULL("full") - 區(qū)塊生成結(jié)束,現(xiàn)在這個(gè)區(qū)塊就是一個(gè)可以被玩家加載,并且在內(nèi)部游玩的區(qū)域了。
接下來(lái),我們會(huì)講講主要的部分。先來(lái)講一下群系的判斷。
五.群系生成
群系的生成有關(guān)于很多參數(shù),接下來(lái)我們將一一說明。但是首先,我們要了解一下區(qū)塊是怎么被劃分之后生成的。
在區(qū)塊生成的時(shí)候,每個(gè)區(qū)塊被分成了更小的單位用于群系生成。區(qū)塊首先被分為一塊塊16x16x16方塊的區(qū)域,這就是區(qū)塊段(Chunk Section),這是MC世界數(shù)據(jù)保存的最小單位。它又被分成了4x4x4的區(qū)域,每個(gè)區(qū)域都會(huì)計(jì)算一次群系,下面將這個(gè)區(qū)域的坐標(biāo)叫群系生成坐標(biāo)。群系生成坐標(biāo)的計(jì)算就是XYZ軸的坐標(biāo)除以4,因此我們可以看到噪聲圖像和真實(shí)生成有1:4的比例。
每個(gè)群系生成區(qū)域都有特定的數(shù)值用于計(jì)算群系和地形等,下面列舉一下它含有的各個(gè)值(坐標(biāo)指群系生成坐標(biāo),而不是方塊坐標(biāo),計(jì)算忽略Blender影響):
Offset(偏移) 分為X軸偏移和Z軸偏移,是XZ坐標(biāo)加上一個(gè)噪聲得出的,下面的公式給出了關(guān)系:
其中噪聲o的參數(shù)如下:
Continentalness(大陸性)代表了這個(gè)區(qū)域的海陸關(guān)系,它的值可以在F3中找到——Multinoise行的C,和Biome Builder行的C,用來(lái)代表陸地類型。當(dāng)值大于-1.2且小于-1.05時(shí)顯示為“Mushroom
fields”(蘑菇島);當(dāng)值大于-1.05且小于-0.455時(shí)顯示為“Deep
ocean”(深海);當(dāng)值大于-0.455且小于-0.19時(shí)顯示為“Ocean”(海洋);當(dāng)值大于-0.19且小于-0.11時(shí)顯示為“Coast”(海岸);當(dāng)值大于-0.11且小于0.03時(shí)顯示為“Near
inland”(淺內(nèi)陸);當(dāng)值大于0.03且小于0.3時(shí)顯示為“Mid inland”(中內(nèi)陸);當(dāng)值大于0.3時(shí)顯示為“Far
inland”(深內(nèi)陸)。
它的計(jì)算僅和XZ坐標(biāo)有關(guān),或者更確切地說是XZ偏移。
噪聲c參數(shù)如下:
Weirdness(奇異性) 代表了地形的奇異程度,例如竹林和叢林僅有奇異度不同,因?yàn)橹窳窒喈?dāng)于叢林的變種,所以奇異度更高。在F3中也能找到它的身影,Multinoise行的W。
計(jì)算僅和XZ坐標(biāo)有關(guān),公式與參數(shù)如下:
Erosion(侵蝕度) 代表地形被侵蝕的程度。值越低代表被侵蝕的越強(qiáng),形成峽谷;值越高代表侵蝕弱,形成平原。值可以在F3中找到,在Multinoise行的E。
計(jì)算也只關(guān)于XZ坐標(biāo)。
Ridge(山脊性) 代表地形隆起程度,它的另一個(gè)名稱是PV(Peaks and Valleys)。在F3中也能看到它的值,Terrain行的PV;另一項(xiàng)是Boime Builder的PV,值小于-0.85時(shí),該項(xiàng)顯示為“Valley”(山谷);當(dāng)值大于-0.85且小于-0.2時(shí)顯示為“Low”(低地);當(dāng)值大于-0.2且小于0.2時(shí)顯示為“Mid”(中等高度地形);當(dāng)值大于0.2且小于0.7時(shí)顯示為“High”(高地);當(dāng)值大于0.7時(shí)顯示為“Peak”(山峰)。
計(jì)算時(shí)只關(guān)于奇異性。
Terrain Offset(地形偏移)與之前的XZ偏移不一樣,這個(gè)偏移是用來(lái)計(jì)算地形的,它的具體含義在之后介紹。它的計(jì)算和之前的值計(jì)算不一樣,它使用了插值函數(shù)。由于默認(rèn)的插值函數(shù)過于復(fù)雜,這里就不給出它的公式了(具體公式在TerrainShaper里面)。它有關(guān)于大陸性、侵蝕度和山脊性。在F3中表示為Terrain行的O。
Factor(地形因子) 同上,也是地形參數(shù)。有關(guān)于大陸性、侵蝕度、奇異性和山脊性。公式也是過于復(fù)雜所以略去。F3中顯示為Terrain行的F。
Jaggedness(粗糙度)也屬于地形參數(shù),也有關(guān)于前面的4個(gè)參數(shù)。公式略去。它在F3中Terrain行的JA。
Depth(深度) 位置的深度。1.18擁有垂直群系,所以深度也決定了群系類型。值越大,代表著越深。它的計(jì)算公式如下:
Temperature(溫度)決定這個(gè)位置上的溫度。溫度決定了群系的溫度類型,比如寒冷或者溫暖。它只和XZ軸有關(guān)。
Humidity(濕度)決定這個(gè)位置上的濕度。濕度決定了群系的濕度,是干旱還是濕潤(rùn)。它也只和XZ軸有關(guān)。
知道了這些值,我們就可以選取這個(gè)位置上應(yīng)該存在的群系。
群系的選擇是這樣的:群系本身有個(gè)“最佳區(qū)間”,例如凍河(FROZEN_RIVER)的最佳區(qū)間是溫度-1.0~-0.45、任意濕度區(qū)間、大陸性-0.11~0.03、侵蝕度-1.0~-0.375,奇異性-0.05~0.05,深度0或1。如果噪聲點(diǎn)正好位于某個(gè)最佳區(qū)間內(nèi),那么直接選中表示的群系,但是我們的噪聲結(jié)果不一定能正好放到這些區(qū)間上,所以系統(tǒng)會(huì)選取噪聲圖上離這個(gè)噪聲點(diǎn)最近的群系最佳區(qū)間,并設(shè)置群系為這個(gè)區(qū)間的群系。為了減少一些群系的生成,讓他們更稀有,又加入了一個(gè)offset偏移用于增加和其他點(diǎn)的距離(普通群系這項(xiàng)是0),這樣就會(huì)更加稀有。
群系的內(nèi)置設(shè)定都在類OverworldBiomeBuilder內(nèi)部,但是內(nèi)部的代碼實(shí)在是過于雜亂并且區(qū)間很多,這里就不給出具體的各個(gè)群系的最佳區(qū)間了。這里說明幾個(gè)規(guī)律:
1. 蘑菇島,它只需要大陸性為-1.2~-1.05(這也可以解釋為什么蘑菇島都在離海岸極遠(yuǎn)的地方),除了深度之外無(wú)視其他參數(shù)(準(zhǔn)確來(lái)說是其他參數(shù)的區(qū)間都被包括在內(nèi),無(wú)論什么值都在區(qū)間之內(nèi)都能達(dá)到最佳區(qū)間)。
2. 在地表的群系深度的最佳值是0和1,在地底的群系深度最佳區(qū)間是0.2~0.9,這意味著兩件事:一是地底群系不能無(wú)限向下延伸,二是地表也有可能出現(xiàn)地底群系。如果深度噪聲因?yàn)榈匦纹票粡?qiáng)制覆蓋(比如設(shè)置成很大的值),那么可能出現(xiàn)地底群系的生成高度上升或下降。
3. 溫度(T)、濕度(H)、侵蝕度(E)有對(duì)應(yīng)的簡(jiǎn)化值,這些簡(jiǎn)化值在F3的Biome Builder行能看到。這些簡(jiǎn)化值實(shí)質(zhì)上是數(shù)組的序號(hào),數(shù)組的內(nèi)容如下:
這里有一個(gè)特殊的區(qū)間,也就是溫度-1.0~-0.45。這個(gè)區(qū)間代表了地表結(jié)冰,簡(jiǎn)化值是0。在寒冷生態(tài)群系中,基本T都是0;而當(dāng)T是1~4時(shí)通常不會(huì)產(chǎn)生積雪等地表景物。
4. 奇異性決定是否為某個(gè)群系的變種。河流的奇異性在-0.05到0.05之間(這也說明了奇異性的噪聲圖像能大概看出河流位置),變種的奇異值大于0,而非變種的奇異性小于0。這也可以看出件事:變種群系和非變種群系基本不在河流的同一岸。又因?yàn)槠娈愋詻Q定了山脊性,所以可以看出為什么河流奇異性是接近0的:因?yàn)樵谶@時(shí)山脊性最小,接近-1。
5. 巨大化生物群系只修改了大陸性、溫度、濕度、侵蝕度噪聲,準(zhǔn)確來(lái)說是每個(gè)噪聲都下調(diào)了兩個(gè)數(shù)量級(jí)(firstOctave被減了2),但是奇異性不變。這導(dǎo)致了奇異性決定的一些事物不會(huì)變化,最明顯的是河流生成位置是相同的?。ㄔ?.18前的版本之中,巨大化生物群系和普通生成的河流圖像也是一樣的,可能是為了同步兩個(gè)版本)
至此,群系的初步生成就結(jié)束了。
下面是幾種參數(shù)的噪聲圖像和真實(shí)生成的比較:

更加清晰的版本:
https://sm.ms/image/FigCvdPQEDk1r4t - 侵蝕度噪聲圖像
https://sm.ms/image/NcHBptkxRi57ldW - 濕度噪聲圖像
https://sm.ms/image/GNYjkw1vySQELZs - 大陸性噪聲圖像
https://sm.ms/image/AYLg2j7OKpnZ1hI - 奇異性噪聲圖像
https://sm.ms/image/bjN1kXI6YgonaeJ - 溫度噪聲圖像
每個(gè)群系生成的單位在計(jì)算完群系之后,就要從4x4x4的網(wǎng)格放大到16x16x16的區(qū)塊段上,這樣才可以讓群系的過渡更平滑。這個(gè)工作由BiomeManager完成,由于代碼比較復(fù)雜,在這就不做演示了。
六.結(jié)構(gòu)起始點(diǎn)生成
結(jié)構(gòu)在生成之前要決定放置位置,也就是結(jié)構(gòu)起始點(diǎn)生成。在區(qū)塊生成的步驟中它是第一項(xiàng),而真正的結(jié)構(gòu)生成被包含在后面的地物生成,這時(shí)會(huì)刪除結(jié)構(gòu)起始點(diǎn)代表結(jié)構(gòu)已經(jīng)生成完畢。
結(jié)構(gòu)的定位使用了幾種不同的算法,但是大多數(shù)都使用了一個(gè)算法定位。首先介紹的也就是這個(gè)算法:
為了保證結(jié)構(gòu)之間不能離得太近也不能離得太遠(yuǎn),MC提供了兩個(gè)參數(shù)用于控制結(jié)構(gòu)的距離:
spacing(空位) - 兩個(gè)同類型結(jié)構(gòu)距離的平均值。
separation(間隔)- 兩個(gè)同類型結(jié)構(gòu)距離的最小值,要求必須小于spacing。
通過這兩個(gè)參數(shù),MC能將結(jié)構(gòu)控制在一個(gè)合理的密度,不至于兩個(gè)結(jié)構(gòu)離得太近。這兩個(gè)值的具體應(yīng)用見下方代碼:
從這段代碼可以看出,MC其實(shí)是將世界分成了一個(gè)一個(gè)邊長(zhǎng)為spacing的格子計(jì)算結(jié)構(gòu)的。每個(gè)格子內(nèi)部只能有一個(gè)結(jié)構(gòu),并且每個(gè)格子內(nèi)的結(jié)構(gòu)都不會(huì)超出以格子起始點(diǎn)為左上角邊長(zhǎng)為spacing-separation的方格。這也就代表著,如果X軸/Z軸的區(qū)塊坐標(biāo)有一項(xiàng)遵照了下面的公式:
那么這個(gè)結(jié)構(gòu)無(wú)論如何也不會(huì)在這里生成。理論上,一個(gè)結(jié)構(gòu)在
格內(nèi)肯定有另一個(gè)同種類型的結(jié)構(gòu),兩個(gè)相同結(jié)構(gòu)的距離不會(huì)超過
格。
在這段代碼中,還有一個(gè)參數(shù)之前沒有提到:salt。salt指鹽,它在密碼學(xué)中是一種輔助散列化的值,在MC中它用于輔助結(jié)構(gòu)生成的隨機(jī)化。在這里,鹽作用于隨機(jī)數(shù)引擎的種子上,它是這樣計(jì)算的:
現(xiàn)在這段代碼里面只差一個(gè)方法沒有解釋了。linearSeparation方法返回布爾值,代表最短間隔是否遵循線性規(guī)律。這是個(gè)硬編碼在代碼里的值,不能通過我們配置修改。在使用了這種方式生成的結(jié)構(gòu)只有三種結(jié)構(gòu)不使用線性規(guī)律,它們是末地城、海底神殿和林地府邸。
下面我們使用這段代碼生成一下海底神殿的位置圖。它的平均間隔是32,最小間隔是5,鹽是10387313。也就是說理論上,一個(gè)海底神殿在759.4格范圍內(nèi)必有另一個(gè)海底神殿,兩個(gè)相鄰海底神殿的最大間隔不會(huì)超過1038.1格,X軸和Z軸區(qū)塊坐標(biāo)符合的坐標(biāo)不會(huì)生成海底神殿。

對(duì)比這兩張圖我們可以發(fā)現(xiàn)左面的坐標(biāo)右面也都有,可是右面卻多出了幾個(gè)點(diǎn)。這些點(diǎn)是被過濾掉了,因?yàn)楹5咨竦钪荒茉诤Q笊锶合瞪?。但是一個(gè)區(qū)塊可是有很多群系決定點(diǎn)的,哪個(gè)才是它判斷的點(diǎn)呢?下面這段代碼給出了答案:
可以看到檢查的是區(qū)塊中心點(diǎn)(8,8)的最高點(diǎn)處群系。
到這里,結(jié)構(gòu)生成點(diǎn)的判斷就結(jié)束了。
除了這種方式外,還有幾種結(jié)構(gòu)生成點(diǎn)生成算法。
對(duì)于埋藏的寶藏結(jié)構(gòu),它擁有一個(gè)“概率值”用于生成。在默認(rèn)情況下這個(gè)值是0.01,也就是說每個(gè)區(qū)塊都有1%的幾率可能生成埋藏的寶藏。在可能的前提下檢查群系,如果群系是沙灘或者積雪沙灘那么就能生成。
結(jié)構(gòu)起始點(diǎn)生成就說到這里,結(jié)構(gòu)的具體生成和地物的具體生成在這系列的專欄不會(huì)詳細(xì)說明。

這篇專欄就到這里,下一篇將講述高度表生成、洞穴生成等。
有錯(cuò)誤可以在評(píng)論區(qū)指出。
(MC的代碼真的是一團(tuán)糟)