創(chuàng)造自己的世界——Minecraft 1.19的地形生成(二)
在上一篇專欄中我們討論了群系和結(jié)構(gòu)起始點(diǎn)的計(jì)算。這篇專欄將仔細(xì)講解地形與洞穴生成。
一.與 1.18 的不同
由于上一篇專欄是在 1.18 寫(xiě)的而現(xiàn)在已經(jīng)到 1.19 了,所以需要說(shuō)明這個(gè)版本改了什么??傮w而言,生成規(guī)律沒(méi)有任何變化,最重要的變化其實(shí)是開(kāi)放了配置這些生成的 API。
這個(gè)新的 API 叫做密度函數(shù),它不僅包含了普通的噪聲,還包含了其他計(jì)算,讓地形生成更多樣化。
在生成參數(shù)上,也發(fā)生了一點(diǎn)變化。不過(guò)這種變化不影響生成,只是換了個(gè)方式表達(dá)而已。下面是奇異性使用密度函數(shù)表達(dá)的例子(使用 https://misode.github.io/worldgen/density-function 生成):
具體的格式可以參考Wiki頁(yè)面。
在密度函數(shù)中,有一類混合密度函數(shù)。它們的共同點(diǎn)是都以 blend 開(kāi)頭。對(duì)于你創(chuàng)建的新世界來(lái)說(shuō),這些密度函數(shù)是固定值:blend_offset 是 0、blend_alpha 是 1。而對(duì)于從舊版本升級(jí)而來(lái)的世界,這些混合密度函數(shù)就不是固定值了,它們會(huì)在過(guò)渡區(qū)塊上起作用。在下文中,我們不會(huì)討論新舊區(qū)塊的過(guò)渡問(wèn)題,因此這些函數(shù)將被視為固定值。
接下來(lái)就讓我們正式開(kāi)始討論地形的生成。
二.地形偏移、深度與世界的高度
在上一篇專欄中,我們提過(guò)地形偏移和深度,這兩個(gè)量有對(duì)應(yīng)關(guān)系,簡(jiǎn)化之后像這樣:
從這個(gè)公式,我們可以看出深度隨 Y 值變低而升高。而對(duì)深度在水平方向上起作用的是地形偏移,地形偏移代表了地形上下起伏。
在公式上,地形偏移是一個(gè)三次樣條函數(shù),它使用了某一點(diǎn)的大陸性值、奇異性值、侵蝕度和山脊性值進(jìn)行插值,得到最終的結(jié)果:
這個(gè)樣條函數(shù)極度復(fù)雜,以 JSON 格式書(shū)寫(xiě)需要 1000 行左右,遠(yuǎn)遠(yuǎn)超出了本專欄可以解釋的范圍,所以在這里只討論這幾個(gè)參數(shù)對(duì)地形偏移的主要影響。
大陸性主要影響總體偏移,使偏移呈現(xiàn)階梯狀。
侵蝕度主要影響小幅度的偏移。
奇異性和山脊性主要影響河谷的形成。
由于地形偏移與 Y 值無(wú)關(guān),而深度是在地形偏移的基礎(chǔ)上加入了一個(gè)線性的 Y 參數(shù),所以在水平方向上,起伏只與地形偏移有關(guān),在任何高度下標(biāo)準(zhǔn)化后的圖都應(yīng)該相同。下圖是指定種子下,Y=128 時(shí)的深度值圖,數(shù)據(jù)經(jīng)過(guò)標(biāo)準(zhǔn)化處理。

清晰版本:https://sm.ms/image/dekqiXQ6n98Bp4G
深度不僅影響上篇專欄中講過(guò)的群系放置,還影響接下來(lái)的不加粗糙度的原始密度值和最終密度值,主要影響它們的上部曲線,也就是地形最終的高度。一般來(lái)說(shuō),基礎(chǔ)地形的最高點(diǎn)的深度值通常都在 0 左右,上下浮動(dòng)不超過(guò) 0.3。但是深度不是影響地形高度的絕對(duì)因素,所以有的時(shí)候會(huì)產(chǎn)生較大的偏差,不過(guò)總體而言地形越高,地形偏移越大,同一高度上的深度也自然變大。
由于深度同時(shí)影響了群系和上部曲線,所以群系和地形高度是有一些聯(lián)系的。最明顯的就是:遠(yuǎn)古城市往往出現(xiàn)在高地形的地下,而海洋下基本不會(huì)出現(xiàn)深暗之域。深暗之域的放置要求是:深度大于 0.9 且侵蝕度小于 -0.225。而一個(gè)水平位置上至少存在一個(gè)深度大于 0.9 的垂直位置就要求基礎(chǔ)地形高度至少在 52 格以上,再加上基巖層等的限制,基本上在基礎(chǔ)地形高度 60 格以上地下才會(huì)出現(xiàn)深暗之域,所以海洋下能找到深暗之域是非常少的。而要生成遠(yuǎn)古城市,那么需要的高度就更高,所以高地形的地下才更容易出現(xiàn)遠(yuǎn)古城市,這是有著一定聯(lián)系的。
三. 最終密度與基礎(chǔ)方塊放置
講了這么多,那么洞穴之類的到底是怎么生成的呢?在講解最終密度(N)之前,我們先介紹一下組成它的其他密度函數(shù)。
minecraft:overworld/caves/noodle
聽(tīng)名字就知道這個(gè)是生成面條洞穴的,主要生成細(xì)長(zhǎng)封閉的洞穴,圖像如下所示:

紅色標(biāo)出的是小于0的部分。
清晰版本https://sm.ms/image/JVN1YtbLyI2wBT3
minecraft:overworld/sloped_cheese
主要用于控制生成芝士洞穴的密度函數(shù),和深度、粗糙度和地形因子都有關(guān)。

綠線是值為0左右的點(diǎn),紅線是值為1.5625左右的點(diǎn)。
清晰版本https://sm.ms/image/XAHmQuwiceVqhgo
minecraft:overworld/caves/entrances
用于控制洞穴與地面間進(jìn)行連接的密度函數(shù)。

紅色表出的是小于0的部分。
清晰版本https://sm.ms/image/EQ5thMfneA3bvWx
minecraft:overworld/caves/spaghetti_2d
控制意面洞穴是否進(jìn)行生成。

清晰版本https://sm.ms/image/RzluLU4g3TmkWvV
minecraft:overworld/caves/spaghetti_roughness_function
控制意面洞穴的形狀。

清晰版本https://sm.ms/image/8uyXHgCweUsjoxn
minecraft:overworld/caves/pillars
負(fù)責(zé)計(jì)算噪聲柱。

清晰版本https://sm.ms/image/tuTQ4gzC5x8Ofhl
最終密度值就是以上密度函數(shù)進(jìn)行計(jì)算。為了簡(jiǎn)化公式,上述六個(gè)密度函數(shù)我們簡(jiǎn)記為 noodle(n)、cheese(c)、entrances(e)、spe2d(s_0)、speghetti(s_1)和 pillars(p)。
先來(lái)看看芝士洞穴、意面洞穴和噪聲柱的生成,這三種計(jì)算基本是在一起的。
洞穴本質(zhì)上分為兩部分:與地上的連接口和在地下的空穴。為了分開(kāi)這兩個(gè)部分,最終密度中將 cheese 密度值小于 1.5625 的部分作為淺層地表和外界,大于 1.5625 的部分作為深層地下。淺層地表的密度值由 cheese 和 entrances 密度值決定,具體公式為:
在深層地下,洞穴的計(jì)算式為:
其中是 minecraft:cave_layer 噪聲,
是 minecraft:cave_cheese 噪聲,這兩個(gè)噪聲也控制著芝士洞穴的位置。這個(gè)公式每一行都代表了一種洞穴類型:第一行是芝士洞穴,第二行是意面洞穴,第三行仍然是洞穴入口的函數(shù)。
但是這還沒(méi)有結(jié)束,我們還沒(méi)有生成噪聲柱。如果 pillars 函數(shù)值不超過(guò) 0.3,則會(huì)被認(rèn)為不能成為一個(gè)噪聲柱,修正為 -1000000。經(jīng)過(guò)這一步修正,現(xiàn)在的密度函數(shù)變成了:
與前面的表面函數(shù)組合,形成新的函數(shù):
為了防止山過(guò)高和地下出現(xiàn)過(guò)深的芝士洞穴和意面洞穴,還需要進(jìn)一步修改這個(gè)函數(shù)。具體來(lái)說(shuō),是對(duì)上下兩部分進(jìn)行鉗制,具體做法是這樣的:
從上述公式可以看出,超過(guò) 256 層密度值變?yōu)?-0.024994792 不變,低于 -40 層密度逐漸變?yōu)?0.037482421875。
到這里芝士洞穴、意面洞穴和噪聲柱的生成就結(jié)束了。面條洞穴的生成要比這三個(gè)簡(jiǎn)單,僅需要 noodle 函數(shù)就可以。得到最終密度值為:
下面是最終密度值的圖像:

清晰版本https://sm.ms/image/yVU8w5cn4JTFkaf
使用紅色標(biāo)記這個(gè)圖像中小于 0 的部分,得到下面的圖像:

清晰版本https://sm.ms/image/vpqdVyGo6SBnLCs
可以看到在圖像上方全部都小于 0,下方基本大于 0 但圖像中間含有小于 0 的空洞。
最終密度值主要影響的是世界基礎(chǔ)方塊的放置,而放置與不放置的分界線就是 0。由于 256 層之上固定為 -0.024994792,所以 256 層以上不會(huì)有基礎(chǔ)方塊放置;地下 -40 層開(kāi)始非面條洞穴密度逐漸增加,即在很低的高度下面條洞穴更主要。
可以看到上面的圖像在邊緣不怎么平滑,是因?yàn)闆](méi)有進(jìn)行插值。下面這張圖像將最終密度值大于 0 的部分標(biāo)記為白色,其他部分標(biāo)記為黑色,并進(jìn)行插值:

清晰版本https://sm.ms/image/uHLJ2c3BzdqanCE
與實(shí)際生成進(jìn)行對(duì)比,可以發(fā)現(xiàn)地形上確實(shí)差不多。但是有些在實(shí)際世界中生成的洞穴并沒(méi)有在圖中顯示出來(lái),這是因?yàn)槎囱ㄉ刹恢褂性肼暥囱?,還有雕刻器洞穴。
在實(shí)際應(yīng)用中,還有一個(gè)不帶粗糙度的初始密度函數(shù)(AS),它是最終密度上部曲線的近似,分界線是 0.390625。它影響接下來(lái)的含水層放置和之后的地表規(guī)則。

清晰版本https://sm.ms/image/IxaqGndy6bZMeA5
四.含水層
在噪聲洞穴中通常還包含有含水層,含水層是和基礎(chǔ)方塊一起生成的,且只生成于最終密度值小于 0 的地方。
含水層中的流體由 Aquifer.FluidPicker 提供,它唯一的實(shí)現(xiàn)是這樣的:
從這段代碼中可以看出,低于 -54 高度的含水層都必然是熔巖,高于 -54 且低于 63 則默認(rèn)為水。
負(fù)責(zé)含水層計(jì)算的類是 Aquifer,它有兩個(gè)實(shí)現(xiàn):NoiseBasedAquifer 和 createDisabled。如果在世界設(shè)置里面關(guān)閉含水層,則默認(rèn)調(diào)用 createDisabled,它的代碼如下:
可以看出,即使關(guān)閉含水層也會(huì)進(jìn)行放置流體,并且放置的特點(diǎn)是:-64~-55 層為熔巖,-54~63 層為水,再向上就沒(méi)有含水層了。并且放置的所有流體都不會(huì)進(jìn)行初始的流體更新。
如果開(kāi)啟含水層,則使用 NoiseBasedAquifer。它的代碼比較復(fù)雜,這里將簡(jiǎn)單的說(shuō)明一下它的做法。
如果此處最終密度大于 0,則不生成含水層。
如果高度小于 -54,則放置熔巖作為含水層。
barrier 控制壓力值的計(jì)算,而壓力值過(guò)大會(huì)阻止生成含水層。
fluid_level_floodedness 與 fluid_level_spread 控制地下含水層液面高度。
lava 控制是否使用熔巖充滿含水層而不是水。如果此值絕對(duì)值大于 0.3,則使用熔巖。
如果生成時(shí)水在上方熔巖在下方,則液體一定進(jìn)行更新。
下面是加入了含水層的地形生成。其中紅色為熔巖,藍(lán)色為水。

清晰版本https://sm.ms/image/n9VtiubZ6x7hONX
五.礦脈
礦脈的生成也在初始地形生成時(shí)就已經(jīng)生成,或者說(shuō)它不屬于地物。它優(yōu)先于最終密度函數(shù)影響的放置。礦脈使用三個(gè)密度函數(shù)進(jìn)行計(jì)算:
礦脈開(kāi)關(guān)密度函數(shù)(vein_toggle)。當(dāng)此值小于等于 0 時(shí)生成鐵礦脈,大于 0 時(shí)生成銅礦脈。它的特點(diǎn)是:只在 Y 值 -60~51 時(shí)可能不為 0,其他高度都是 0。它不能完全決定礦脈的存在,如果不在礦脈的生成高度(銅 0~50,鐵 -60~-8),則礦脈也不生成。

清晰版本https://sm.ms/image/cmhsYDH6VJuK1tC
上面的生成高度條件滿足后,還需要計(jì)算是否合適進(jìn)行礦脈方塊放置。先計(jì)算當(dāng)前點(diǎn)到要放置的礦脈的最大高度與最小高度的差值,取較小的一個(gè),并鉗制在 0~20 之間并映射到 -0.2~0 區(qū)間內(nèi)。計(jì)算開(kāi)關(guān)密度函數(shù)絕對(duì)值與這個(gè)映射值的和,如果小于 0.4,則也不生成礦脈方塊。之后會(huì)再隨機(jī)過(guò)濾掉30%方塊,防止礦脈方塊過(guò)多。

滿足上述條件(不包含過(guò)濾30%方塊的部分)的部分已標(biāo)紅。
清晰版本https://sm.ms/image/VFn2PRQLTNOeGcf
第二個(gè)決定礦脈放置的是礦脈奇異密度函數(shù)(vein_ridged),類似于開(kāi)關(guān)密度函數(shù),它在 Y 不是 -60~51 的地方值為 -0.08。它進(jìn)一步削減礦脈方塊的數(shù)量。當(dāng)此值大于 0 時(shí),此處也被篩選掉不作為礦脈生成位置。剩下的位置將生成礦脈。

小于0的部分已標(biāo)紅。
清晰版本https://sm.ms/image/yVN5lWCbjR8P4ZK
最后一個(gè)決定礦脈放置的是礦脈間隙密度函數(shù)(vein_gap)。它影響的是在礦脈中是否放置礦石或者礦物塊。如果此值小于等于 -0.3,則會(huì)生成礦脈中的填充方塊。緊接著再將開(kāi)關(guān)密度的絕對(duì)值鉗制在 0.4~0.6 并映射到 0.1~0.3,生成一個(gè)隨機(jī)數(shù),如果隨機(jī)數(shù)小于映射值,則可以放置礦石(98%)或者礦物塊(2%)。

小于-0.3的部分已標(biāo)紅。
清晰版本https://sm.ms/image/LchO2aizkqrFlG5
接下來(lái)我們把礦脈也加入生成之中,現(xiàn)在我們的地形切面和真實(shí)生成已經(jīng)很接近了:

清晰版本https://sm.ms/image/THVCMm3vsFryBo6
到這里,區(qū)塊生成的 NOISE 階段結(jié)束。

這篇專欄到這里就結(jié)束了,下篇專欄會(huì)介紹地表規(guī)則和地物放置。
源碼來(lái)源:1.19.3,Mojang Mapping,反編譯器 CFR 0.152,自動(dòng)生成源碼倉(cāng)庫(kù) Nickid2018/GitMCDecomp(https://github.com/Nickid2018/GitMCDecomp)。
編寫(xiě)時(shí)長(zhǎng):3 周。
測(cè)試代碼總行數(shù):7669 行。
參考:
https://misode.github.io(misode 數(shù)據(jù)包生成器)
https://minecraft.fandom.com/zh(中文 Minecraft Wiki)
對(duì)文章有疑問(wèn)或文章有錯(cuò)誤可以在下方評(píng)論區(qū)指出。