四分鐘四十億年!國外小哥在GPU上模擬世界
四十億年里的地球,是什么樣子?
最近,一位外國小哥寫了一個程序,在幾分鐘內,就模擬了一顆類地行星的完整歷史。
這個實現是完全用GLSL片段著色器編寫的,模擬的更新速度為每秒60幀。
1 原行星
這個故事始于四億五億年前,有一塊熔巖……
早期的地球是一顆原行星,溫度熾熱,且因小行星撞擊而布滿隕石坑。
由于這個地球模擬完全是按程序生成的,沒有預先渲染的紋理,因此第一個任務,就是生成該地形的地圖。
要計算給定經度和緯度處的地形高度,首先要轉換為3D笛卡爾坐標:
vec3 p = 1.5 * vec3(
sin(lon*PI/180.) * cos(lat*PI/180.),
sin(lat*PI/180.),
cos(lon*PI/180.) * cos(lat*PI/180.));
現在,小行星的大小各不相同,因此產生的隕石坑也不盡相同。
為了適應這種情況,著色器迭代了五級細節,將大小逐漸減小的隕石坑層層疊加。
fBM() 用于生成地形、云、樹木分布、它們的顏色變化以及頂篷細節
為了使隕石坑具有逼真的凹凸不平的外觀,小哥在隕石坑中混入了一些分數布朗運動噪音,并按比例調整,使最大的隕石坑對地形的影響最大。
float height = 0.;
for (float i = 0.; i < 5.; i++) {float c = craters(0.4 * pow(2.2, i) * p);float noise = 0.4 * exp(-3.c) * FBM(10.p);float w = clamp(3. * pow(0.4, i), 0., 1.);
height += w * (c + noise);
}
height = pow(height, 3.);
隕石坑本身是在3D網格上生成的,而地表地形則是從網格中劃分出來的一個球體。
為避免明顯的規律性,隕石坑中心使用哈希函數從網格點中隨機生成。
要計算給定位置上隕石坑的影響,就可以對屬于附近網格點的隕石坑進行加權平均,權重隨距離中心的距離呈指數遞減。
而坑的邊緣,由一條簡單的正弦曲線生成。
float craters(vec3 x) {
vec3 p = floor(x);
vec3 f = fract(x);
float va = 0.;
float wt = 0.;
for (int i = -2; i <= 2; i++)
for (int j = -2; j <= 2; j++)
for (int k = -2; k <= 2; k++) {
vec3 g = vec3(i,j,k);
vec3 o = 0.8 * hash33(p + g);
float d = distance(f - g, o);
float w = exp(-4. * d);
va += w * sin(2.*PI * sqrt(d));
wt += w;
}
return abs(va / wt);
}
最終,程序生成的高度圖如下——
雖然相對簡單,但在低洼地區注滿水后,這個程序地形類似于科學家認為的早期地球的實際樣子:
NASA提供的對早期地球的藝術印象
其中所含的水被熱量蒸發,逸出并開始在地球周圍形成的早期大氣中循環。隨著時間的推移和巖石的冷卻,水蒸氣開始凝結成海洋。液態水在地表流動,在地形上刻畫出一道道溝壑,留下了大量沉積物。
2 構造板塊
山脈、海溝和我們熟悉的大陸地貌的形成,需要一個構造運動模型。
我們讓模擬隨機生成板塊的種子位置,并設定初始速度。
隨著時間的推移,這些板塊的大小會隨著一個簡單的聚集模型而增長,該模型會隨機選擇相鄰的點,如果這些點還沒有被分配到另一個板塊中,就會被添加到一個板塊中。
板塊內的所有像素都會存儲板塊的移動速度。這種聚合模型類似于擴散限制聚合(但實際并沒有擴散):
板塊的連續移動是很困難的,因為這需要板塊邊界來解釋以像素為單位的移動。
為避免出現這種情況,板以離散的時間步長移動,橫向或縱向均以一個像素為單位。
每個板塊的移動時間都是隨機的,這樣就可以使平均速度保持在設定的速度和方向上,而且相鄰板塊不太可能同時移動。
當一個板塊的一些邊界像素移動到以前被另一個板塊的像素占據的位置時,就會發生板塊碰撞。
這會導致俯沖,只要稍微增加碰撞位置的地形海拔,即可對這種情況進行建模。
雖然這種情況只發生在板塊邊界的像素點上,但通過簡單的熱侵蝕模型,這種影響會逐漸擴散到鄰近的像素點上,從而將像素點的海拔高度推向其鄰近像素點的平均海拔高度方向。
總之,這就形成了對有山脈的大陸很好地模擬(在下一節中,我們會引入水力侵蝕,對模擬進一步改進)——
3 水力侵蝕
自然地形的崎嶇外觀,很大程度上是由河流流域形成的,它們會以我們熟悉的分支模式,來侵蝕著地貌景觀。
想要模擬出這種景觀,有很多水流模擬的方法。
然而有一個難題:對于整個地球來說,地形圖的分辨率相當低。
因此,模型必須能夠模擬出寬度不超過一個像素的河流。
好在,Barnes提出的一個簡單模型,就能實現這一目標。
簡單來說,每個像素都會檢查與它相鄰的八個像素,以確定哪個方向的海拔降低幅度最大(由于對角線上的相鄰像素距離較遠,因此需要進行調整)。
這個坡度最大的方向,就是水流出這個像素點的方向。
水流最初通過降雨在各單元之間分配,然后會在每個時間步長內,在相鄰像素之間傳輸。
侵蝕是由水流冪律驅動的:
elevation -= 0.05 * pow(water, 0.8) * pow(slope, 2.);
在這里,我們有當前單元的海拔高度和水量,以及水流方向的坡度。
海拔的降低是有上限的,這樣就不會低于水流方向的位置。
水流和侵蝕之間的相互作用,會導致地形中河谷的自然形成:
通過給相連的水道著色(顏色由河口位置決定),就可以制作出令人印象深刻的可視化效果,直接能讓人聯想到真實的流域圖——
模擬河流流域
來自《蚱蜢地理》的美國河流流域
4 全球氣候
模擬整個星球的氣候系統是一項艱巨的任務,但幸運的是,它可以相對容易地被近似模擬出來。
在我的氣候模擬中,程序生成的平均海平面氣壓(MSLP)地圖,就是一切背后的驅動力。
根據《氣候食譜》,生成MSLP圖的主要因素,就是地貌在海洋中的位置以及緯度的影響。
事實上,如果從真實的地球MSLP地圖中提取數據,根據陸地或海洋的位置將其分開,并繪制 MSLP與緯度的關系圖,就會得出陸地和海洋的兩條正弦曲線,二者的形狀略有不同。
通過適當調整參數,就可以得出了一個粗略的年平均氣壓模型(此處緯度以度為單位):
if (land) {
mslp = 1012.5 - 6. * cos(lat*PI/45.);
} else { // ocean
mslp = 1014.5 - 20. * cos(lat*PI/30.);
}
當然,這還不足以生成真實的MSLP地圖,因為分別生成陸地和海洋的數值,會導致它們之間的邊界出現明顯的不連續性。
實際上,MSLP會在從海洋到陸地的過渡過程中,發生平穩變化,這是由于氣體壓力的局部擴散造成的。
只需對MSLP地圖(標準偏差為10-15度)進行高斯模糊處理,就能很好地近似這種氣體擴散過程。
考慮到氣候會隨季節變化而變化,有必要對1月和7月之間的MSLP差異進行建模。
陸地數據再次表明,這種差異呈正弦模式。
通過調整參數和應用高斯模糊,可以將其與年度MSLP地圖相結合,生成全年變化的動態氣候模式。
if (land) {
delta = 15. * sin(lat*PI/90.);
} else { // ocean
delta = 20. * sin(lat*PI/35.) * abs(lat)/90.;
}
現在,有了MSLP,就可以生成風流和溫度。
實際上,是氣溫產生了氣壓,但相關性就是相關性。
這就需要更多的處理,才能生成真實的數值(season全年在-1和1之間波動)。
float temp = 40. * tanh(2.2 * exp(-0.5 * pow((lat + 5.season)/30., 2.)))
- 15.(mslp - 1012.) / 1.8 + 1.5 * land - 4. * elevation;
風往往從高壓流向低壓,但在全球范圍內,我們還需要考慮科里奧利力,它是導致風在氣壓帶周圍環流的原因(grad是MSLP梯度矢量)。
vec2 coriolis = 15. * sin(lat*PI/180.) * vec2(-grad.y, grad.x);
vec2 velocity = coriolis - grad;
雖然這是一種相對粗糙的模擬,但它生成的風環流模式,卻非常逼真。
如果仔細觀察,你口會發現許多自然現象都被復制了,包括季風季節印度上空的風向逆轉:
作為一個細節,降水可以通過水蒸氣從海洋通過風矢量場平移到陸地來模擬。
平流的實現方式與流體模擬類似。
5 生命
氣候影響著地球上的生命分布。降雨模式和溫度變化決定了植物的生長速度。
隨著季節的變化,食草動物會遷移到有足夠植被的地區。
隨著植被的遷移,食肉動物也跟著遷移。
所有這些動態都可以通過Lotka–Volterra擴散模型來捕獲
float dx = plant_growth - c.y;
float dy = reproduction * c.x - predation * c.z - 1.;
float dz = predation * c.y - 1.;
float dt = 0.1;
c.xyz += dt * c.xyz * vec3(dx, dy, dz);
c的xyz元素,分別代表植被、食草動物和食肉動物的種群。
在大范圍內,動物種群的動態會產生有趣的模式:
在現實生活中,這些模式最容易在培養皿中的微生物種群中看到,但同樣的規律,也適用于全球的大型動物種群。
霉菌菌落中的螺旋波紋
6 人類
早期地球的序幕結束了。
影片的節奏放慢到晝夜循環,地形變得固定,構造運動變得難以察覺。
很快,隨著人類開始在地球表面殖民,夜晚就會呈現出前所未有的光影模式。
隨著人類開始燃燒大量化石燃料,為自己的生活提供動力,這種快速擴張帶來了一系列變化。
沉睡了數百萬年的碳,被釋放到了大氣中,并且散布到了地球的各個角落。
幾百年來,人類燒盡了所有可用的化石燃料資源,向大氣釋放了五萬億噸碳。
這加劇了溫室效應,使全球的平均氣溫上升了近10攝氏度。
赤道附近的大片土地因為極端溫度而變得不適合居住,導致人類從地球上很大一部分地區消失了。