创造自己的世界——Minecraft 1.19的地形生成(三)
在上一篇专栏中我们介绍了世界的噪声生成、矿脉等。这些操作都在区块状态 NOISE 中执行。这篇文章将继续介绍世界生成接下来的阶段。
一. 地表生成
在 NOISE 阶段后,区块会进入 SURFACE 阶段,也就是生成地表。虽然它叫“地表生成”,它实际上控制的不止地表。下面这些都是由地表生成器生成的:
深板岩层和基岩层。
恶地陶瓦条带层与岩柱。
冻洋的巨型冰山和冰盖。
裸岩山峰上的方解石条带,尖峭山峰和冰封山峰等的细雪条带。
所有地表的草方块-泥土层、沙子-砂岩层,海底的沙砾、沙子层等。
世界的大部分地形特征基本都由地表生成器决定。地表生成器会对世界中已存在的所有基础方块位置进行计算,通过数据包的地表规则(surface_rule
)决定一个位置上的方块最终会变为什么样子。下面以主世界的地表规则的一部分进行举例:
可以看到在第一层嵌套中地表规则只有这三项:
生成基岩层。
生成普通地表。
生成深板岩层。
地表生成器会根据定义的顺序挨个尝试执行,如果执行成功就不再执行接下来的规则。假如一个方块位置在 Y = -64,那么它肯定会变为基岩,而不会计算接下来的地表规则。根据上面的代码来看,基岩层是优先度最高的,防止出现基岩空洞;而深板岩层是最后计算的,即它不能覆盖已生成的地表,表现上来看就是即使在很深的露天位置会生成地表而不是深板岩。
普通地表的生成也有它的“深度”,也就是说地表的特征不能覆盖到太深层次的地下。下面为普通地表的 JSON 设置:
普通地表的深度受 above_preliminary_surface 限制,当它通过时地表才能生成,如果不通过则不能生成。它与密度函数 initial_density_without_jaggedness(不带粗糙度的初始密度)有关,以 0.390625 为界,向下偏移 2~8 格就是这个条件的边界。通常来说初始密度的界限非常接近实际生成的空气-地表边界,或比地表稍微向下一点,这样能确保地表能生成,但也影响了一些表层洞穴可能会生成地表。
有关于这个 JSON 的具体代码和解释可以在 wiki 找到。
除了 JSON 定义的地表规则外,有一些地表生成是硬编码的。风蚀恶地的岩柱、冻洋的巨型冰山都不受地表规则影响,它们都由地表生成器直接生成,而岩柱早于地表规则放置,巨型冰山晚于地表规则放置。
二. 地形雕刻
在地表生成后,区块进入 CARVERS 阶段,开始进行地形雕刻。雕刻是为了细化地形,并生成雕刻器洞穴,用于将 NOISE 阶段的噪声洞穴更好的联系起来。
目前 MC 中只有三种雕刻器:洞穴雕刻器、峡谷雕刻器和下界洞穴雕刻器。洞穴雕刻器生成了雕刻器洞穴,包括管道状的洞穴和环形内庭;峡谷雕刻器会生成巨大的峡谷。
雕刻器洞穴和噪声洞穴虽然都是洞穴,但是它的规模和数量差别巨大。区分这两种洞穴的办法就是通过 F3 屏幕上的 N(最终密度值),如果 N 大于 0 则为雕刻器洞穴,N 小于 0 则为噪声洞穴。
由于地形雕刻没有什么好说的,所以这篇文章不会讲它的算法。
在 CARVERS 阶段后的下一个阶段 LIQUID_CARVERS 在 1.18-pre3 时被清除了操作,因为这一阶段是液体雕刻,而液体雕刻阶段的水下洞穴和水下峡谷在 21w06a 就被移除了,所以这个阶段在现在的版本已经没有意义。
三. 地物生成
在地形雕刻之后,就来到了世界生成的重点环节:地物生成(FEATURES)。
地物生成阶段会生成世界上所有的地物,无论是花草树木还是地下的矿石都在这一阶段产生。结构的实际放置也会在这个阶段进行,通过读取第一、二阶段的数据得出结构位置进行放置。
地物不是在一起生成的,而是再细分为几个阶段生成:
这 11 个阶段中,除了 STRONGHOLDS 阶段没有任何作用外,其他阶段都有各自的作用:
RAW_GENERATION,用于生成其他地物依赖的位置。原版中这里只有一个末地小岛的地物。
LAKES,生成湖。在 1.18 之后水湖已经被含水层替代,目前这里只剩下熔岩湖地物。
LOCAL_MODIFICATIONS,生成一些对地形影响较大的地物。原版中紫晶洞、冰山等会在这个阶段生成。
UNDERGROUND_STRUCTURES,生成地下的结构。原版中废弃矿井、化石、埋藏的宝藏在这个阶段生成。
SURFACE_STRUCTURES,生成地表结构。大部分结构都在这个阶段生成,沙漠水井和冰刺也是在这个阶段生成。
STRONGHOLDS,无作用。要塞在地表结构生成时生成。
UNDERGROUND_ORES,生成地下矿物。所有的团簇都在这个阶段生成。
UNDERGROUND_DECORATION,生成地下装饰。远古城市在这个阶段生成,幽匿斑块也是这个阶段生成的。
FLUID_SPRINGS,生成涌泉。
VEGETAL_DECORATION,生成植物装饰。树木、花草斑块等都是在这个阶段生成。
TOP_LAYER_MODIFICATION,顶层修改。这个阶段只有冰冻顶层地物,用于生成冰雪表面。
在每个具体的阶段,结构都优先于地物生成。结构的生成顺序是固定的,按照字符串的顺序依次放置;地物放置顺序也是固定的。具体地物阶段列表可以看 https://minecraft.fandom.com/zh/wiki/User:Nickid2018/%E4%B8%96%E7%95%8C%E7%94%9F%E6%88%90%E8%A1%A8。
先说结构放置。结构的生成点在区块生成的前两个阶段就已经产生,并且包含了结构片段的起始点和参数,在结构真正生成时只会执行当前区块内含有的所有结构片段的 postProcess 方法。如果结构片段超出了本区块会怎么样呢?答案是超出的部分也会成功放置。地物生成阶段和其他阶段的不同在于,这个阶段修改区块是可以超出本区块范围的,但是不能超过以本区块为中心的 3x3 区块。如果一个结构片段或地物生成超出了这个限制,那么超出部分不能被成功放置,看起来就像是被切断了一样。
简单介绍了结构的放置,接下来来说说地物的。地物的放置每个区块每种地物都要尝试放置,而具体放置多少怎么放置与放置修饰器有关。下面说的“指定”都可以通过 JSON 配置整数、浮点、高度提供器或谓词等,用于生成对应的参数。
放置的尝试次数与放置修饰器 RepeatingPlacement 的子类有关。
CountPlacement:指定数字区间的尝试次数。
NoiseBasedCountPlacement:使用生物群系噪声 BIOME_INFO_NOISE 的噪声值进行放置。每当噪声值上升一个区间就加 1。
NoiseThresholdCountPlacement:使用生物群系噪声 BIOME_INFO_NOISE 的噪声值进行放置。如果噪声值超过某一个值就使用设置的第一个值,否则使用第二个。
生物群系噪声 BIOME_INFO_NOISE 是一个和种子无关的噪声,也就是说在不同的世界中,如果有一个位置的生物群系相同,那么使用后两个放置修饰器的同一种地物的生成尝试次数就是相同的。
生成地物时默认位置是在区块的最低子区块的 (0, 0, 0) 处。为了让区块内的地物合理放置,还需要其他放置修饰器用于修改地物的放置点。下面这些修饰器就是做这些工作的:
CountOnEveryLayerPlacement:尝试生成指定个数的点,随机分配到区块内的水平坐标。对于每个水平坐标,找到最高的地表位置作为最终的放置位置。
EnvironmentScanPlacement:对于输入的放置点,尝试在指定步数内按指定方向找到一个满足指定条件的位置作为最终放置点。
HeightmapPlacement:将放置点移动到水平坐标上的某一高度图高度处。
HeightRangePlacement:将放置点移动到指定的高度上。
InSquarePlacement:将放置点水平移动到区块的某个位置上。
RandomOffsetPlacement:以放置点为中心,将放置点移动到指定高度和宽度空间内的某个位置。
CarvingMaskPlacement:获取整个区块内所有包含指定雕刻标记的位置。
初步决定地物是否能放置在某个位置的放置修饰器都是 PlacementFilter 的子类。
BiomeFilter:检查放置点的生物群系,如果群系不对应则放弃在此点的放置。验证时使用从生物群系单元缩放到方块后的生物群系。
BlockPredicateFilter:如果放置点的方块不通过方块谓词测试,则此放置点无效。
RarityFilter:如果随机数大于
,则此放置点无效。相当于平均每
次生成才能成功一次。
SurfaceRelativeThresholdFilter:当生成点位于某个高度图高度偏移的某个区间时生成点才有效。
SurfaceWaterDepthFilter:当放置点水平坐标的最高非空气方块和最高可阻挡方块的高度差值小于某个值时,或简单来说此位置最高位置上的水深小于某个值时,此放置点有效。
这些放置修饰器在定义时是有顺序的,通常来说决定尝试次数的修饰器和 RarityFilter 在前,之后是修改放置点的修饰器,最后是过滤修饰器。这种顺序能保证地物的生成数量和位置更加正确。
以小型钻石团簇举例,它的已放置的地物定义如下:
从上方可以看出小型钻石团簇有下面的修饰器:
放置尝试(7次)-> 水平选择随机位置 -> 随机选择高度 -144~16,三角形分布 -> 检查生物群系
由于地物本身是世界生成的一部分,所以在生成时它的随机数发生器种子固定。对于某一个地物,它的随机数种子是 ,其中
是这一阶段这个地物放置的序号,
是地物放置阶段的序号。由于地物种子的微妙关系,使不同地物的放置位置产生了微妙的联系,也就是“定位法”。

在 1.18 正式版前,钻石和圆盘的放置点是有联系的。它们都处于 UNDERGROUND_ORES 阶段生成,所以 相同,不同的只有
。在 1.18 前,地物的随机数发生器使用的是 Java 的线性同余随机数发生器,这使得最终的随机数出现了联系,也就是上表。在 21w37a 时,这个规律仍然存在,但是因为这个版本中修改了某些地物的顺序,造成青金石代替了原先钻石的位置,所以在 21w37a 中使用 1.17 的方法本质上定位的是青金石。真正给定位法带来严重修改的版本是 1.18pre-7,在这个版本中随机数发生器被修改为 Xoroshino128++,最终导致了这个定位法失效。
由于地物和结构都是按照区块生成,并且可以扩散到其他区块,所以有的时候区块生成顺序的不同可能导致地物互相覆盖,理论上也会导致同一个种子生成的地物或结构因为玩家行走路径带来的区块加载顺序的不同而不同。
四. 区块光照
在地物生成完毕后,就来到了光照阶段。在这个阶段之前,任何方块的放置都不会进行光照的更新,直到这个阶段开始集中更新光照。在光照开始时,这个区块会被加上一个等级为 33 的 LIGHT 加载标签。在光照计算结束后,区块的 LIGHT 标签被自动移除。
五. 生成初始生物
在光照计算结束后,区块进入 SPAWN 阶段,用于生成世界的初始生物。这个阶段不能修改区块内的方块,只能添加实体。当这个阶段开始后,会挑选出所在生物群系动物类别的所有生物进行生成。生成时使用的随机数种子和世界种子、区块位置绑定,也就是说在世界生成时这些生物的位置都是固定的。如果一个种子的出生点附近初始就生成了一只粉红羊,那么重新创建一个一模一样种子的世界它也依然会生成。
六. 生成完毕
生成初始生物后,就到了 HEIGHTMAPS 阶段,但这个阶段并没有实质作用,因为高度图在之前就已经生成并填充完毕。或者更精准的来说,高度图随区块状态的变化而变化。在地物生成前只有两个高度图:OCEAN_FLOOR_WG 和 WORLD_SURFACE_WG,而在地物之后会变成四个高度图:OCEAN_FLOOR、WORLD_SURFACE、MOTION_BLOCKING 和 MOTION_BLOCKING_NO_LEAVES。这些高度图随对区块内方块的修改而变化,所以这个高度图阶段本质上没有什么作用。
在这个阶段后,就会到达 FULL 阶段,代表了区块生成完毕。此时原型区块会变为世界区块,可以进行正常的区块行为,比如计划刻和随机刻等。

到这里,世界生成的过程就结束了,这系列专栏的内容也就到这里了。
源代码:1.19.4,Mojang Mapping,CFR 0.152 反编译。
有错误可以在评论区指出。