主世界的生成——浅谈Minecraft地形生成(四)

现在,我们已经了解了密度函数是如何影响地形生成的,那么现在让我们把目光重新看向主世界的简化版的密度函数:

{
    "type": "minecraft:squeeze",
    "argument": {
        "type": "minecraft:mul",
        "argument1": 0.64,
        "argument2": {
            "type": "minecraft:interpolated",
            "argument": {
                "type": "minecraft:blend_density",
                "argument": {
                    "type": "minecraft:range_choice",
                    "input": "minecraft:overworld/sloped_cheese",
                    "min_inclusive": -1000000,
                    "max_exclusive": 1.5256,
                    "when_in_range": {
                        "type": "minecraft:min",
                        "argument1": "minecraft:overworld/sloped_cheese",
                        "argument2": 1
                    },
                    "when_out_of_range": 1
                }
            }
        }
    }
}

其中我们主要将洞穴相关的噪声函数去除了。如果你在misode的预览器中观察它,你会看到大概这样的图像:

紫色的部分就是>0的部分,大于0的密度函数将会生成默认方块,在主世界就是石头了。在图中,你可以看到哪些地方是山谷或者海洋,哪些地方是山峰。

大致浏览一次这个密度函数后,我们会发现它还调用了一些其他的函数,比如minecraft:overworld/sloped_cheese。继续深挖,我们会发现更多的调用函数。那么,让我们直接前往函数调用的最底部,看看这个世界是怎么生成的。

minecraft:overworld/offset

{
  "type": "minecraft:flat_cache",
  "argument": {
    "type": "minecraft:cache_2d",
    "argument": {
      "type": "minecraft:add",
      "argument1": {
        "type": "minecraft:mul",
        "argument1": {
          "type": "minecraft:blend_offset"
        },
        "argument2": {
          "type": "minecraft:add",
          "argument1": 1.0,
          "argument2": {
            "type": "minecraft:mul",
            "argument1": -1.0,
            "argument2": {
              "type": "minecraft:cache_once",
              "argument": {
                "type": "minecraft:blend_alpha"
              }
            }
          }
        }
      },
      "argument2": {
        "type": "minecraft:mul",
        "argument1": {
          "type": "minecraft:add",
          "argument1": -0.5037500262260437,
          "argument2": {
            "type": "minecraft:spline",
            "spline": {...}
          }
        },
        "argument2": {
          "type": "minecraft:cache_once",
          "argument": {
            "type": "minecraft:blend_alpha"
          }
        }
      }
    }
  }
}

观察这个密度函数。我们省略了它的样条插值部分,因为真的特别特别多,加起来总共一千多行的JSON,在这里是绝对看不完的。有兴趣的读者可以自行打开源文件研究一下子。除了插值以外,我们可以看到它还有一些其他的密度函数,但是又一看,都是blend_xxx函数。而这些函数都是用来处理新旧世界区块交接的,对于全新的世界生成来说,这些就是常数而已。cache_once用于增强性能,不会影响函数的值。因此,经过简单的加减法即可发现,除了样条插值以外的所有部分都被约为了0,也就是说这个函数的值只取决于这个样条插值函数。

最后得到的结果就是上面这个条形码一样的图案。其中的每一个小细条都是成百上千格的距离,世界的起源就蕴含在这些小小的条纹中。

如果你把视图切换为俯视图,你还能看出河流和山脊。在这里,大陆性(continents)和侵蚀性(erosion)两个噪声共同参与了样条采样的过程,通过控制三次样条的控制点从而控制样条曲线,进而实现了地形的沟壑纵横或山脉起伏。同时,生物群系的生成同样使用了大陆性和侵蚀性两个参数,因此在对应的山脉或者峡谷地形,就会生成对应的生物群系。也就是说,大陆性和侵蚀性两个噪声,同时决定了地形生成和生物群系的生成两个过程。

minecraft:overworld/depth

{
  "type": "minecraft:add",
  "argument1": {
    "type": "minecraft:y_clamped_gradient",
    "from_value": 1.5,
    "from_y": -64,
    "to_value": -1.5,
    "to_y": 320
  },
  "argument2": "minecraft:overworld/offset"
}

这个函数中,我们已经看到了起伏的山脉,而这个函数却惊人的简单——只有一个加法运算。第一个参数表示了一个从y轴的-64到320,值从1.5变化到-1.5的函数。让这个函数的值加上我们之前看到的条纹噪声的值,就可以得到上图这样的函数了。

为什么能得到这样的函数呢?因为第一个函数的噪声值和y轴成线性关系,即value1 = (y+64)/384*3-1.5,而第二个密度函数的值仅仅和xz轴有关。那么假设第二个密度函数在此处的值为value2,所以可以求出,如果要密度函数大于0,那么需要value1+value2大于0,即(y+64)/384*3-1.5+value2 > 0,即y > (1.5-value2)/3*384-64,其实也是一个线性关系。如此,就把offset的条纹码转换为了高低起伏的地形。

现在,我们在y轴为64的位置,也就是通常的海平面位置,观察俯视图,就可以更明显地看出河流、海洋、大陆和山脉的位置(绿色的部分就是低于0,也就是海洋和河流的位置,而蓝紫色则是大于0,表示这里应该是大陆或者山脉)

minecraft:overworld/jaggedness

{
  "type": "minecraft:flat_cache",
  "argument": {
    "type": "minecraft:cache_2d",
    "argument": {
      "type": "minecraft:add",
      "argument1": 0.0,
      "argument2": {
        "type": "minecraft:mul",
        "argument1": {
          "type": "minecraft:blend_alpha"
        },
        "argument2": {
          "type": "minecraft:add",
          "argument1": -0.0,
          "argument2": {
            "type": "minecraft:spline",
            "spline": {...}
          }
        }
      }
    }
  }
}

这个密度函数看起来很繁杂,其实一点也不简单。首先,cache_2d是用于缓存以加快运算速度的,所以我们这里可以不用管,而blend_alpha则是用于区分新版本和旧版本区块,用于处理加载旧版本世界的时候,新旧区块交界处的,所以我们可以把它看作一个常数。因此经过简单计算以后,这个密度函数就只和被我用{...}代替的这个样条插值有关了。具体的插值过程有兴趣的读者可以自己下去研究,如果有合适的计算工具的话,其实计算这个插值过程并不复杂。上图分别是这个这个密度函数的俯视图和横向剖面图。这个函数的结果永远是大于0的。

minecraft:overworld/sloped_cheese

{
  "type": "minecraft:add",
  "argument1": {
    "type": "minecraft:mul",
    "argument1": 4,
    "argument2": {
      "type": "minecraft:quarter_negative",
      "argument": {
        "type": "minecraft:mul",
        "argument1": {
          "type": "minecraft:add",
          "argument1": "minecraft:overworld/depth",
          "argument2": {
            "type": "minecraft:mul",
            "argument1": "minecraft:overworld/jaggedness",
            "argument2": {
              "type": "minecraft:half_negative",
              "argument": {
                "type": "minecraft:noise",
                "noise": "minecraft:jagged",
                "xz_scale": 1500,
                "y_scale": 0
              }
            }
          }
        },
        "argument2": "minecraft:overworld/factor"
      }
    }
  },
  "argument2": "minecraft:overworld/base_3d_noise"
}

这个函数需要我们慢慢拆开一层层一看。首先,我们把目光转向最内层:这里一个叫做jagged的噪声在xz方向进行放缩后,进行了half_negative计算,这个计算让噪声大于0的部分保持不变,同时让噪声小于0的部分减半。接着,它与overworld/jaggedness相乘。由于overworld/jaggedness大部分区域的值实际上为0,因此这个步骤相当的作用是将jagged噪声限制在了一定的范围内。下图从左到右分别展示了同一种子的同一位置,在进行half_negative计算后的jagged密度函数,overworld/jaggedness密度函数和进行上述计算后的结果(图的位置可能稍有偏移,y轴为64):

jagged的意思是层次不齐的,而jaggedness的意思是粗糙度。通过简单的加和操作,这个部分用于为我们已经初具地形模样的overworld/depth函数添加更多的变化。下图是原来的overworld/depth密度函数,以及加上我们刚刚计算结果后的样子:

接下来是overworld/factor密度函数。这个函数我们之前没有介绍过。它的内部也是一个三次样条,而具体的形状在下图的左侧(剖面图)。这个函数的用途我推测是用于给后续叠加三维噪声创造缺口,为什么这么说我们之后再解释。下方中部的那张图就是加上overworld/factor后的密度函数。而左侧则是加上三维噪声后的密度函数:

可以看到,叠加了overworld/factor后,密度函数的这个地方的颜色更加接近绿色,这也就意味着这个地方的密度绝对值较低,因此更容易受到三维噪声的影响。右图中可以看到,在overworld/factor影响的位置,地形出现了更加明显的崎岖不平,甚至出现了小山峰。在某些极端情况下,这会导致空岛这种极端的地形。

主世界密度函数

现在我们终于看完了所有引用的函数,是时候看看主世界的生成过程了。这里是主世界的密度函数,和我们文章开头的时候看到的一样,是简化版的:

{
    "type": "minecraft:squeeze",
    "argument": {
        "type": "minecraft:mul",
        "argument1": 0.64,
        "argument2": {
            "type": "minecraft:interpolated",
            "argument": {
                "type": "minecraft:blend_density",
                "argument": {
                    "type": "minecraft:range_choice",
                    "input": "minecraft:overworld/sloped_cheese",
                    "min_inclusive": -1000000,
                    "max_exclusive": 1.5256,
                    "when_in_range": {
                        "type": "minecraft:min",
                        "argument1": "minecraft:overworld/sloped_cheese",
                        "argument2": 1
                    },
                    "when_out_of_range": 1
                }
            }
        }
    }
}

我们还是从里面往外面看。首先是一个range_choice计算,这个计算让密度函数大于1的部分都变成了1。随后进行了blend_density,即新旧版本区块的过渡,这一步我们可以不用管。之后便是至关重要的一步,interpolated,插值。插值可以让地形变得更加的平滑,否则你的地形中将会全是以4*4小格子为单位的地形。下面分别展示了插值前后地形的变化。

此后,便只有简单的数值运算了。挤压(squeeze)操作会将地形进一步进行一些细小的微调,挤压的函数图像如下图:

最后,我们就得到了简单的一个主世界的地形,有河流,有山丘,有海洋。但是,还是缺少了一些东西,比如噪声洞穴。有兴趣的读者可以自行用我们文章中的这种思路,分析主世界密度函数的完整版,此处我们就不再赘述了。

类似文章

发表回复