天天看點

[譯] 如何使用 WebGL 技術進行風力地圖可視化

翻譯:@四季留歌 部分翻譯。原文:https://blog.mapbox.com/how-i-built-a-wind-map-with-webgl-b63022b5537f

目錄

如果使用 CPU 進行風向可視化: 慢

OpenGL 基礎

擷取風力資料

使用 GPU 移動粒子

繪制粒子

繪制粒子軌迹

插值以擷取風力值

使用 GPU 上的僞随機算法

展望

有很多風力可視化線上網站,最出名的莫過于 earth.nullschool.net。它并不是開源的,但是它有一個開源的舊版本,大多數現有方案都基于此實作。

[譯] 如何使用 WebGL 技術進行風力地圖可視化

通常,這種可視化依賴于 <code>Canvas 2D API</code>,大緻的邏輯是這樣的:

生成一組随機粒子并繪制它們

對于每個粒子,查詢其位置上的風力值,并用此風力值移動此粒子

将一小部分粒子重置,這樣能保證粒子所覆寫的區域看起來比較豐滿

淡化目前幀,并在其上一層繪制新的一幀

這是有性能限制的:

風粒子數量不能太多,大約 5000 個

每次更新資料或者視圖都會有很大的延遲,因為處理這些資料所用的 JavaScript 運作在 CPU 上,相當耗時

原作者在文章中提出一種使用 WebGL 的新繪制邏輯,這樣速度很快,能畫上百萬個粒子,而且打算和 Mapbox GL 進行結合展示。

原作者找到了 Chris Wellons 寫的關于如何使用 WebGL 粒子實體學的絕棒教程,認為風力可視化可以使用類似的方。

簡單概括原文就是,OpenGL 等圖形技術就是在畫三角形,雖然也能畫點和線,但是用的比較少。

這節的重點是,在頂點着色器或者片元着色器添加一個紋理參數,然後在這個紋理上查找顔色。

這是本文關于風力可視化的重中之重。

美國國家氣象局每 6 個鐘釋出全球天氣資料,這種資料叫做 <code>GFS</code>,大概就是經緯度的網格攜帶了有關的資料值。這種資料稱為 GRIB,是一種特殊的二進制格式。可以用一些其他的工具解析為人類可讀的 JSON(工具)。

原作者寫了一些腳本,将風力資料下載下傳下來并轉為 PNG 圖像,風速編碼為 RGB 色彩灰階值 —— 像素坐标代表經緯度,紅色灰階值代表水準風速,綠色灰階值代表垂直風速。大概長這樣:

[譯] 如何使用 WebGL 技術進行風力地圖可視化

分辨率可以更大,但是原作者認為全球可視化來說,這個分辨率夠用了。

風粒子是存在 JavaScript 數組中的,如何通過 GPU 運算去操作這些粒子對象?或許可以上計算着色器,但是裝置相容性會成大問題。

是以隻能是這個選擇:使用紋理。

OpenGL 規範不僅僅可以把 GPU 計算的結果畫到螢幕上,還能把它畫到紋理上(這個紋理有個特别的名字,叫幀緩存)。

是以,可以把粒子的坐标編碼為 RGBA 值,然後傳遞到渲染管線中進行計算,計算完畢後,再編碼到 RGBA 并繪制為新的圖像。

[譯] 如何使用 WebGL 技術進行風力地圖可視化

為了滿足 X 和 Y 坐标的精度,物盡其用,R和G通道這兩個位元組存儲 X,B和A通道則存儲 Y。那麼,\(2^{16}=65536\)​ 個數字給到每個數字,應該夠了。

一張分辨率為 500×500 的圖像可以存儲 25w 個粒子:

[譯] 如何使用 WebGL 技術進行風力地圖可視化

在片元着色器裡操作這些像素代表的粒子即可。

下列是從 RGBA 四個通道灰階值解碼、編碼的 glsl 代碼:

在下一幀,就可以繪制出這一個圖像來。

每一幀,重複這個過程,即,隻需兩個紋理對象,交替計算和繪制就可以實作将風場模拟計算轉移到 GPU 上來。

在極點附近的粒子和赤道附近的粒子相比,沿着 X 軸移動的速度會快得多,因為同樣經度(X軸是緯線)跨度,赤道跨過的距離和極點跨過的距離是不一樣的。

通過下列着色器進行改進:

雖然 WebGL 大多數時候适合繪制三角形,但是這個場景下,繪制點就很合适了。

在頂點着色器中,對粒子紋理進行采樣,以擷取其坐标,然後,在風速紋理上采樣擷取 u 和 v 值,計算其風速(\(speed^2=u^2+v^2\)),然後将這個風速映射到漸變色帶上,以進行着色。

此時,大概是這樣的:

[譯] 如何使用 WebGL 技術進行風力地圖可視化

還行。看起來有點空空的,沒有風的感覺,需要繪制軌迹線來完成可視化。

繪制粒子到一個紋理上,然後在下一幀時,将其作為背景(略微變暗),并将另一張在上一幀已經用完的紋理設為本幀的繪制目标,實作交換繪制。

風速資料是經緯網格上特定格網點的一些正北正東向的速度值。例如 <code>(50°N, 30°E)</code>、<code>(51°N, 30°E)</code>、<code>(50°N, 31°E)</code>、<code>(51°N, 31°E)</code> 等。那麼,如何擷取位于這四個點之間的中間值,例如 <code>(50.123°N, 30.744°E)</code>?

使用 <code>texture2D</code> 函數采樣時,OpenGL 會幫你完成這事兒。

但是,風速資料那張紋理圖檔放大後鋸齒、馬賽克效應很明顯,大概這樣:

[譯] 如何使用 WebGL 技術進行風力地圖可視化

使用 雙線性插值 算法,額外擷取某個點附近的 4 個點,可以插值得到比較平滑的結果,這個可以在片元着色器上完成,效果如下:

[譯] 如何使用 WebGL 技術進行風力地圖可視化

在着色器程式中還有一個棘手的邏輯要實作,那就是粒子繪制完後,要重置它,如何随機重置呢?

先說明,着色器程式是沒有内置的随機數生成器的。但是在 StackOverflow 上有一個用于生成僞随機數的函數:

有了這個函數,就可以判斷是否需要重置粒子狀态了:

難點在于,如何讓粒子重置的時候重置到一個足夠随機的位置。

直接用粒子的坐标是不行的,因為相同的粒子的坐标總是會得到一樣的随機數,即在哪兒産生,就在哪兒消失。

最終,作者決定使用三個值作為随機數的輸入二維向量:

其中,<code>pos</code> 是粒子目前坐标,<code>v_tex_pos</code> 是粒子原始坐标,<code>u_rand_seed</code> 是每一幀中計算得到的随機值。

仍舊存在一個小問題,那就是粒子的速度非常快的區域看起來會很稠密,可以通過設定一個“重置率”數值來實作整體平衡:

其中,<code>speed_t</code> 是一個介于區間 <code>(0, 1)</code> 之間的相對值,<code>u_drop_rate</code> 和 <code>u_drop_rate_bump</code> 是自由調節的兩個參數。

作者感謝的話。不翻譯了。

附贈源代碼:https://github.com/mapbox/webgl-wind

繼續閱讀