启航星:行星与美术
左键拖动旋转 · 右键拖动光源
地图需求与 Photoshop 草稿
地图首先要服务于《启航星》的故事。人类来到这颗星球后,与原住民精灵发生矛盾,双方又各自有主战和反战的组织。因此,我需要给不同势力留出地理缓冲地带,让河流为城镇提供资源,也需要险峻高山,让故事中的山间庇护所能够成立。
故事没有涉及海战,分成多个主大陆会明显影响故事走向,所以我保留一片主要大陆。大陆整体呈弯月形,缺口来自古代陨石撞击,宽广的内陆则用来容纳剧情需要的沙漠。绘图时又发现,只有一片大陆会显得不真实,因此补充了外围群岛。

从平面草稿到球面
最初用 Photoshop 画大陆、岛屿和板块草稿。问题是,平面上看起来合适的轮廓,放到球面后会扭曲,大小感也会改变。为了按球面上的实际关系绘图,我最后改用 GPlates 制作正式的大陆和板块。

用 GPlates 绘制大陆与板块
大片山脉需要在板块结构上有合理的解释,所以我先画板块,再画山地。西部的新安第斯山脉被放在新陆板块与西岬板块相互挤压的区域,外围群岛也参考了板块交界、俯冲和局部小板块的关系。
在 GPlates 中,大陆用闭合多边形绘制,板块共用的边界用拓扑连接。星球直径设为 12,050 公里,再用测量工具检查大陆尺度,得到主大陆面积约为 3,601 万平方公里。



地理区域的命名
我希望名字首先像真实世界的地理名称,同时区分人类和原住民精灵对同一个地区的不同称呼。人类依靠启航计划来到这里,因此叫它“启航星”;精灵恐惧火焰、赞美水源,因此用水的颜色称它为“蓝星”。
大陆在人类口中是“新大陆”,带有新开始的含义;精灵则称它为“根大陆”,因为大陆承载着灵树的根系。同一条山脉,人类借用熟悉的地理记忆称为“新安第斯山脉”,精灵称为“水源高地”。
用 QGIS 制作粗地形
如果直接手绘完整的等高线,不但工作量很大,也不方便思考山地的形状。因此我改画山脊线:先在 QGIS 中确定主要山脉的走向,再给山脊节点设置不同高度,让软件补出山脊与海岸之间的坡面。
海洋也需要地形。我把海底与陆地放进同一张全球高程图:海洋使用负高程,海岸固定为 0 米,陆地使用正高程。从大陆架、大陆坡到深海盆地,整颗星球共用一张连续表面,等高线也包括海底部分。

平滑算法的试错
我先用 SAGA GIS 的自然邻域插值生成粗地形。它根据附近已有高度推算中间的地面,让山脊和 0 米海岸之间先有一个完整的坡面。

先根据山脊和 0 米海岸,生成陆地粗地形。
平滑比想象中难得多。它既要保留主要山脉和海岸,还要处理球面投影与地图接缝。我对同一组山脊与海岸控制线比较了两种办法:QGIS 的 TIN 插值把已知高程连接成三角形,再在每个三角形内计算高度,但山坡留下了明显的折面;GRASS GIS 的正则样条用连续曲面连接已知高程,表面更平滑,却出现了计算分区的接缝。

控制线保留得较好,但山坡出现了明显的三角形折面。

表面更平滑,但方块接缝与内部计算分区对应,因此没有继续采用。
最后,我保留自然邻域插值得到的粗地形,再用 Screened Poisson 平滑坡度。这个方法让相邻地面的坡度更协调,同时用原有高程约束表面:主要山脉继续控制高度,海岸保持 0 米,让山顶、山脚和平原之间的变化更连续。

再连接山顶、山脚和平原之间的坡度,保留主要山系。
为了减轻反复试错的成本,我用 Python 把这些处理连接起来,使用 GDAL 转换局部投影与全球栅格,再用 SciPy 做数值处理。这样更换算法时,可以继续使用同一组山脊与海岸控制线。
全球高程与等高线
最后得到的全球高程图为 18,000 × 9,000。完成等高线后,我又按照等高线的高度给地图上色,查看整体效果。


气候模拟与地形调整
粗地形完成后,我需要知道森林、沙漠和河流应该出现在哪里,因此开始模拟全球温度与降水。我也会根据结果回头调整地形,检查大陆内陆、山脉两侧和高海拔地区是否符合故事需要。
气候软件的选择
一开始我尝试使用 Isca,之后又比较了 SpeedyWeather 的 GPU 计算。SpeedyWeather 的速度较快,但测试中出现了降水异常,因此我最后选择 ExoPlaSim 进行气候模拟。
为了反复比较地形和环境参数,我写了逐年运行的脚本,记录全球平均温度,并用温度变化判断气候是否稳定。随后从已有结果提取温度与降水,转换成地理栅格,再放回 QGIS 和 SAGA GIS 检查。
V2:先用地球环境检查地形
我先把自绘地形放进地球参考环境,看看大陆、山脉和海洋会形成怎样的温度与降水分布。这样可以先检查地形本身,再逐步加入启航星与地球不同的环境参数。
V2 使用 T42,也就是 128 × 64 的模型网格。大气计算沿用模型的地球半径、重力、气压和辐照度,地形则来自我绘制的启航星。下面依次是这一组测试的温度和降水。

ExoPlaSim · T42(128 × 64)· 地球参考环境
模型半径 6371.22 km;重力 9.80665 m/s²;气压 101100 Pa。
恒星辐照度 1367 W/m²;模型年长 360 日。

ExoPlaSim · T42(128 × 64)· 地球参考环境
模型半径 6371.22 km;重力 9.80665 m/s²;气压 101100 Pa。
恒星辐照度 1367 W/m²;模型年长 360 日。
V3:缩小行星,降低重力与气压
我在 V3 中把模型半径改为 6025 公里,并降低重力与气压,开始比较启航星的行星尺度对气候的影响。分辨率仍然保持 T42,恒星辐照度也保持地球参考值。
这次仍采用接近地球的自转与季节条件:自转周期设为 1 地球日,轴倾角为 23.441°,公转周期设为 365.25 地球日。这样可以先观察较小的星球与较低气压下的温度、降水,再继续调整启航星的日长和季节。

ExoPlaSim · T42(128 × 64)· 启航星尺寸与类地球条件
模型半径 6025 km;重力 9.022118 m/s²;气压 92005.52 Pa。
设定自转周期 1 地球日;轴倾角 23.441°;公转周期 365.25 地球日。
恒星辐照度保留 1367 W/m²。

ExoPlaSim · T42(128 × 64)· 启航星尺寸与类地球条件
模型半径 6025 km;重力 9.022118 m/s²;气压 92005.52 Pa。
设定自转周期 1 地球日;轴倾角 23.441°;公转周期 365.25 地球日。
恒星辐照度保留 1367 W/m²。
V4:提高地球参考测试的分辨率
V4 用来检查更细的模型网格。我仍使用地球参考环境,把分辨率从 T42 提高到 T63,也就是 192 × 96,比较山脉附近、海岸和大陆内陆的气候分布。
这一组保持地球参考半径、重力、气压与辐照度,便于和 V2 对照。记录中从配置到完成约用了 49 小时 36 分钟。

ExoPlaSim · T63(192 × 96)· 地球参考环境
模型半径 6371.22 km;重力 9.80665 m/s²;气压 101100 Pa。
恒星辐照度 1367 W/m²;模型年长 360 日。

ExoPlaSim · T63(192 × 96)· 地球参考环境
模型半径 6371.22 km;重力 9.80665 m/s²;气压 101100 Pa。
恒星辐照度 1367 W/m²;模型年长 360 日。
V6:用较低分辨率预览启航星环境
高分辨率的耗时增长很快,租用服务器的成本也很高。因此,在继续调整环境参数时,我先把分辨率降到 T21,也就是 64 × 32,让每次试验更快得到可以检查的温度与降水。
V6 使用启航星的半径、重力和气压,把太阳日设为 20 小时、公转周期设为 380 地球日,轴倾角改为 16°。恒星辐照度先保留地球参考值的 100%,检查这套日长和季节条件下的气候。记录中这一轮约用了 44 分 20 秒。

ExoPlaSim · T21(64 × 32)· 启航星 16° 方案
半径 6025 km;重力 9.022118 m/s²;气压 93219 Pa。
太阳日 20 小时;设定公转周期 380 地球日;轴倾角 16°。
恒星辐照度 1367 W/m²(地球参考值的 100%)。

ExoPlaSim · T21(64 × 32)· 启航星 16° 方案
半径 6025 km;重力 9.022118 m/s²;气压 93219 Pa。
太阳日 20 小时;设定公转周期 380 地球日;轴倾角 16°。
恒星辐照度 1367 W/m²(地球参考值的 100%)。
V7:把恒星辐照度降低到 99%
我在 V7 中保留 V6 的行星半径、重力、气压、日长、公转周期和 16° 轴倾角,只把恒星辐照度从地球参考值的 100% 降到 99%,比较这一变化对温度与降水的影响。
这一组同样使用 T21,与较高分辨率的 V5 并行计算,先预览温度是否符合设定,减少等待和服务器开销。记录中这一轮约用了 1 小时 2 分钟。

ExoPlaSim · T21(64 × 32)· 启航星 16° 方案
半径 6025 km;重力 9.022118 m/s²;气压 93219 Pa。
太阳日 20 小时;设定公转周期 380 地球日;轴倾角 16°。
恒星辐照度 1353.33 W/m²(地球参考值的 99%)。

ExoPlaSim · T21(64 × 32)· 启航星 16° 方案
半径 6025 km;重力 9.022118 m/s²;气压 93219 Pa。
太阳日 20 小时;设定公转周期 380 地球日;轴倾角 16°。
恒星辐照度 1353.33 W/m²(地球参考值的 99%)。
从湿润内陆到逆行自转的设想
做过几轮正向自转的测试后,我发现大陆内陆比预想中湿润,故事需要的沙漠并没有按我的预期出现。此前我没有理解清楚风带的方向,也就低估了水汽经过山脉后会怎样进入内陆,因此又回头检查自转与风向的关系。
为了尝试得到干旱的内陆,我考虑让星球逆行自转。我先试着把自转周期设为负数,但脚本要求这个数值为正;查阅自转与轴倾角的资料后,我又尝试了约 165° 的轴倾角方案。这里记录的是我调整设定时的思路,实际得到的气候仍以各次运行保存的参数和结果为准。
当前交互地图使用的气候数据
当前交互星球的温度、降水和气候类型统一使用 164°、T85 的同一组结果。T85 对应 256 × 128 的模型网格,采用气候稳定后保留的 15 年结果,方便在同一套数据上比较季节、地形与气候类型。
这一轮从配置记录到完成记录的时间跨度约为 6 天 4 小时 42 分钟。文中列出的用时都是这样的记录间隔,可能包含暂停、重启和输出处理。
把气候结果落到精细地形上
为了把全球气候分布细化到山地,我用 SAGA GIS 结合高程处理温度,让高山与低地体现出高度带来的温差。降水部分使用 GeoDS,结合风向、迎风坡、背风坡和高程差,试验水汽经过山脉时的分配,再放回 QGIS 对照山地两侧的变化。
地表细化与侵蚀模拟
地形和气候有了以后,我把它们交给 goSPL,让水流参与河谷、侵蚀和沉积的计算。在这之前还需要处理两个问题:山地表面太平滑,气候径流的格网又太粗。
先调整过于平滑的山地
前面的地形为了保住主要山脉和大陆轮廓,做过较强的插值与平滑,很多山区虽然高度已经确定,表面却仍然太光滑。因此,我写了一个 QGIS Python 工具,在原有山系里补充连续的高低起伏。
变化按照几十公里的范围连续展开,高海拔地区变化较大,低地较小,海岸和海底保持原样。调整后再生成等高线,与原来的山地并排检查。

山地经过插值与平滑,高度已经确定,表面仍然过于规整。

在原有山系内部补充连续起伏,海岸和海底保持原样。

径流细化算法
气候模拟得到的径流像元很粗,直接放到精细地形上,会看到明显的方格、直角和突然跳变的边缘。因此我做了一套插值方法,在原始采样点之间补出连续过渡,让径流的变化更平滑。
处理时固定原始采样点,并保持整体总量基本不变,避免为了平滑而改掉原始气候数据。两张灰度图展示的是细化前后的差别,一次总量核对的差异约为 0.2%。

低分辨率径流图保留了明显的像元边界。

固定原始采样点,只补充点与点之间的连续变化。
用 goSPL 计算侵蚀与沉积
高程与 ExoPlaSim 的地表径流被放到同一套球面网格上,再由 goSPL 计算汇流、河流侵蚀、物质搬运与沉积。水流沿低处汇集,逐渐切入地表,搬运下来的物质则在坡脚、河谷和低地重新堆积。
程序按模拟时间保存地形状态,之后再转回全球地理栅格,放回 QGIS 与原始高程和等高线对照。这样可以检查河谷的位置、山地切割的程度与沉积区域,再继续调整地图。



星球渲染与网页展示
地图最终需要放回完整的星球上看。我在 Blender 中接入自己制作的地形与地表颜色,同时保留原场景复杂的程序化云层、海洋和大气节点。这样,山地、海岸和气候带可以在完整的光照、云影和大气效果下放在一起检查,而不只是看一张平面地图。
最终 Blender 渲染
网页中的交互星球使用 Three.js。为了把 Blender 的地表带到浏览器,我把材质的颜色导出成没有预先烘焙光照与昼夜阴影的全球 UV 贴图,再配合法线、粗糙度、云层和大气数据,由网页实时计算受光面、云影与星球边缘的大气。旋转星球或调整光源时,光照便能随之变化。
全球贴图的数据量很大,因此我把它们转换为浏览器可用的 KTX2 压缩纹理,另外保留 WebP 作为兼容版本。地表先使用 4096 像素宽的贴图,靠近观察时再加载 8192 像素版本;板块、等高线与气候数据也在选择对应图层时加载,减少打开页面时的等待。
海岸、板块和等高线则保留为矢量线。我把经纬度转换成单位球面上的三维坐标,在线段之间补充球面上的点,让它们沿着球面延伸。跨过地图左右两侧的 ±180° 经线时,也按球面上相邻的位置连接,避免把平面地图的边缘画成星球上的竖直接缝。
为了控制浏览器的负担,星球进入可视区域后才初始化;静止时按需要重绘,拖动、缩放或切换图层时再更新。季节播放也会在星球离开可视区域或页面隐藏时暂停更新,让外观、地形与气候能够放在同一处反复比较。