【Cesium 歷史博客】Cesium 中的圖形技術:渲染一幀

版權沒有,請尊重翻譯成果,有翻譯錯誤請指出,規範性轉載。@秋意正寒html

本文經過解讀 Scene.render 方法,觀察 WebGL 在 Cesium 1.9 中如何渲染一幀。讀者能夠在 Scene.render 方法處打斷點進入調試。git

因爲 Cesium 專一於可視化地理空間內容,所以多光源的場景並不擅長、很少見,Cesium 使用的是傳統的前向陰影流水線。Cesium 的流水線之因此獨特,是由於它使用了多個視錐體來支持大範圍的視距,而不須要對z軸進行扭曲變化(這句翻譯得不是很好)。github

起步

Cesium 把每一幀的生命週期相關的數據存儲在一個叫 FrameState(參考 FrameState.js) 的對象中。在幀最開始時,初始化相機參數、時間之類的東西。幀的狀態可用於其餘的對象,例如 Primitive 對象能夠調用當前幀的狀態數據。web

UniformState(參考 UniformState.js)是 FrameState 的一部分,它具備共有的、預先計算好的 uniforms。在幀開始時,它計算視圖矩陣、太陽向量等 uniforms。canvas

更新

Cesium 的動畫、更新、渲染流水線是很經典的,動畫的步驟多是對 WebGL 無交互地對 primitive 的移動、改變其材質屬性、添加刪除 primitive 等。這並非 Scene.render 的一部分,這些動畫可能會在渲染一幀以前經過代碼顯式指定,或者使用 Entity API 的 Property 在後面默默改變。數組

經典的 動畫-更新-渲染 流水線。緩存

Scene.update 這個方法最主要的第一步是更新全部在 Scene 中的 primitives。(參考Scene.js > function updatePrimitives(scene) {})框架

在這一步,每個 primitive 將會:函數

  • 建立/更新其對應的 WebGL 資源,即編譯、連接着色器,加載紋理,刷新頂點緩衝區等;Cesium 永遠不會在 Scene.render 方法外調用 WebGL,由於這樣會浪費 requestAnimationFrame() 這個函數的時間,並使其與其餘的 WebGL 引擎集成變得困難。
  • 返回一列 DrawCommand 對象,這些對象表明的是 primitive 們建立的 drawcall 和 WebGL 資源。像 polyline、billboard集合可能會返回一個 DrawCommand,Globe 對象或 Model 等則可能會返回數百個 DrawCommands。大多數幀會包含幾百到幾千個 DrawCommand。

譯者注:下面代碼選自 1.9,在 1.75 已經找不到這個函數了,commandList 也找不到了。不過,這個 updatePrimitives 函數是在 function render 函數中調用的,render 函數(不是Scene.prototype.render 方法)在1.75版本中卻還在,_primitives.update() 這一步也移動到了 scene.updateAndExecuteCommands() 方法中的 executeCommandsInViewport() 函數裏了,commandList 也被拆開了。有興趣的讀者能夠對比研究研究。性能

// Scene.js 中的 updatePrimitives() 函數 -- v1.9
function updatePrimitives(scene) {
  var context = scene.context;
  var frameState = scene._frameState;
  var commandList = scene._commandList;

  if (scene._globe) {
    scene._globe.update(context, frameState, commandList);
  }

  scene._primitives.update(context, frameState, commandList);

  if (defined(scene.moon)) {
    scene.moon.update(context, frameState, commandList);
  }
}

// 調用
function render(scene, time) {
  // ... 
  updatePrimitives(scene);
  // ...
}

Cesium 中的地球對象:Globe,地形和衛星影像瓦片的引擎,同樣是一個 「primitive」。它的更新功能指揮着瓦片的層次調度、剔除,以及負責管理加載地形瓦片和影像瓦片的內存。

潛在的可見數據集

剔除,是圖形引擎對看不見的物體進行消除的優化方法,這樣流水線就沒必要處理那些看不到的對象了。經過了可見性測試的物體,被稱做「潛在可見性數據集」,將隨着流水線傳遞下去。爲了提升速度,可見性測試使用了不精確的測試方法,全部這些 「潛在可見性數據集」 可能最終是可見的,也多是不可見的。

對於獨立的繪製命令,Cesium 支持使用命令的的 boundingVolume (世界座標空間下)進行視錐體和地平線的自動剔除。(這句話翻譯得不太好,不太懂表達了什麼)對於能自我剔除的 primitive,例如 Globe 對象,能夠關閉這個功能。

傳統的圖形引擎檢查每個繪製命令,進行可見性測試,從而找到潛在的可見數據集。Cesium 的 createPotentiallyVisibleSet 函數(譯者注:如今移動到 Scene.view 屬性內了)先走了第一步,它將繪製命令動態地分爲多個視錐體(一般是三個),這些視錐體把全部的繪製命令綁定在一塊兒,並保持必定的遠近比例以免z值衝突。每一個視錐體的截頭體的張角和寬高比是同樣的,只有近平面和遠平面的舉例不一樣。

這個函數作了優化。它利用時間上的連續,若是先後幀的繪製命令條件合適,那麼已經計算好的視錐體及其截頭體將會被重用,以減小計算量。

上圖左邊:多個視錐體(紫橙綠);右邊:一個視錐體的截頭體的繪製命令

譯者注

這段文章啃得生硬,不知道講了什麼東西,應該是 Cesium 的多視錐體機制能更好地優化剔除吧,源碼要去了解 createPotentiallyVisibleSet 是怎麼作的。注意版本。

渲染

每一個視錐體都有本身的繪製命令列表,如今就能夠觸發 WebGL 的 drawElements 和 drawArrays 了。

Cesium 的渲染流水線核心是 executeCommand 函數,你能在 Scene.js 中找到。

首先,清除顏色緩存。若是使用了與順序無關的透明度、快速近似抗鋸齒(FXAA),則它們的緩存也被清除。

而後,使用整個視錐體(不是上面分開的那三個)繪製一些特殊的 primitive:

  • 天空盒。老式的優化方法是跳過清除顏色緩存,先渲染天空盒。實際上這很損耗性能,由於清除顏色緩存有助於壓縮GPU(與清除深度緩存相似)最佳的實踐是,先渲染天空盒。Cesium 必須這麼作,由於繪製完視錐後深度緩存會被清除(這裏翻譯不太懂)
  • 大氣層。
  • 太陽。若是太陽被設爲可見,則渲染太陽。若是還啓用了輝光濾鏡,則剔除太陽,而後對顏色緩存進行採樣、變亮、模糊等操做,而後混合成輝光效果。

接下來,從最遠的視錐體開始,按如下步驟執行每一個視錐體中的繪製命令:

  • 賦予當前視錐體的 uniform,僅爲視錐體的近距離和遠距離;
  • 清除深度緩存
  • 執行不透明圖元的繪製命令。執行一個繪製命令會設置一些 WebGL 的狀態,例如渲染狀態(深度、混合模式等)、頂點數組、紋理、着色器程序、uniforms等,而後觸發 drawcall。
  • 接下來執行半透明的繪製命令。若是沒有浮點數紋理致使 OIT 不被支持,則將繪製命令從頭至尾排序並執行命令。(這句話又不是很懂了)不然,OIT 將用於提升重疊的半透明對象的顯示質量,避免排序時 CPU 的開銷。繪製命令的着色器對 OIT 進行了修正並緩存,若是支持 MRT,則執行一次 OIT 並進行渲染,或者做爲回調進行兩次渲染(仍是不懂說什麼)。見 OIT.js 中的 executeCommands() 函數。

使用多個視錐體會致使一些有趣的狀況,例如一個繪製命令跨了兩個視錐,那麼命令就會被執行兩次。

至此,每一個視錐體的截頭體的全部繪製命令已經執行,若是使用 OIT,則執行最後的 OIT 複合遍歷將被執行。若是 FXAA (快速抗鋸齒)啓用了,那麼還會執行全屏傳遞以進行抗鋸齒。

與 Heads-Up Display 相似,最後執行的是 overlay pass 繪製命令。(這句話仍是不懂)

Cesium 在 1.9 版本時的渲染流水線。

排序併合批

在每一個視錐體中,由 primitive 傳過來的繪製命令是按順序執行的。例如,Globe 對其繪製命令進行了從前到後的排序,以利用 GPU early-z 的優點(z優先?不懂,須要查資料學習)。

繪製命令的數量決定了性能如何,所以 primitive 經過把多個對象的組合在一塊兒,僅發出一條繪製命令來提升性能。例如,BillboardCollection 在一個頂點緩存中存儲儘量多的 Billboard,並使用同一個着色器進行渲染。

拾取

Cesium 的拾取功能利用了顏色緩存。每個可拾取的對象都有一個惟一的 id(即顏色)。

給定視窗座標系的 (x,y) 座標,爲了肯定拾取了什麼,則須要將幀渲染到屏幕以外的幀緩存,這個寫在外面的幀緩存記錄的顏色值即爲拾取的 id。隨後,使用 WebGL 的 readPixels 函數,讀取顏色,拿到id,而後就能返回拾取的對象了。

Scene.pick 方法的流水線和 Scene.render 方法很相似,不過拾取的東西並不須要包括天空盒、太陽、大氣層,因此能簡化一些。

以後要作的

下列計劃將提高幀渲染的性能。

地面遍歷(原文 Ground Pass 不知道怎麼翻譯好)

上面關於在 Scene.render 方法中的遍歷順序( opaque不透明,半透明translucent,overlay覆蓋)其實在普通的圖形引擎中很常見。實際上,不透明還要分開成 globe 和 opaque(不太懂是什麼意思,應該說的是不透明的東西還能繼續分解爲地球對象和其餘不透明對象)。

能夠這麼說,分開後的不透明物體順序是:基本的 globe對象、貼地的矢量數據和通常的不透明對象。

陰影

陰影將經過陰影映射實現。場景從可產生陰影的光線觸發,進行渲染,每一個能投射的物體均做用於深度緩存,或者陰影貼圖(模型到光源的距離?)。而後,在主色通道中,每一個能接收陰影的對象檢查每一個燈光的陰影貼圖中的距離值,檢查是否在陰影內。

這個實現很是複雜,須要解決混疊僞影、柔和陰影、多視錐截頭體以及地形引擎等因素。

深度紋理

陰影子集添加了對深度紋理的支持。例如,深度紋理能用來對 billboards 在地形上的深度測試,並根據深度值從新構造它在世界空間中的位置。

WebVR

添加陰影后,提供了對不一樣角度進行渲染的能力。WebVR基於此。

每一個眼睛使用一個視錐體進行渲染便可。

立方體貼圖

陰影的另外一個擴展能力是對立方體進行六面貼圖,以進行環境渲染,將環境貼到盒子的六個面上,以顯示盒子位於場景中的何處。這個功能會很是消耗計算資源(做者猜想),因此可能並不會用在實時計算上。

後處理效果

Scene.render 具備一些後處理效果,例如輝光、FXAA和OIT合成等。

官方計劃建立一個通用的後處理框架,將紋理做爲輸入,經過一或多個後處理階段來處理它們。這些後處理操做基本上是在視圖窗口上的幀運行片元着色器,而後輸出。

與其用硬算的方式製造輝光,能夠用後處理的方式更好地完成,還能作景深、SSAO、發光、運動模糊等效果。

參考 屏幕空間渲染細節 wiki

Compute pass

Cesium 使用舊式的 GPGPU 進行 GPU 加速來計算圖像重投影。過程當中,離屏渲染一個與屏幕(就是canvas)對齊的幀,而後推到着色器中。(不知道說了什麼)

將來的渲染流水線

譯者注

渲染流水線這個過程是至關的長,並且這個「將來的渲染流水線」 不必定適用了,不過大致框架的思路已經闡明,但願各位讀者能獲得一些啓發,有些地方翻譯得並非很好。

致謝

做者和 Dan Bagnell 編寫了大多數的渲染器。要得到細節能夠參考 Cesium Wiki。做者還在念高中時,Ed Mackey 在90年代就在 AGI 進行了原生的多視錐體實現。

參考

[Bagnell13] Dan Bagnell. Weighted Blended Order-Independent Transparency. 2013

[Cozzi13] Patrick Cozzi. Using Multiple Frustums for Massive Worlds. In Rendering Massive Virtual Worlds Course. SIGGRAPH 2013.

[McGuire13] McGuire and Bavoil, Weighted Blended Order-Independent Transparency, Journal of Computer Graphics Techniques (JCGT), vol. 2, no. 2, 122–141, 2013

[ONeil05] Sean O’Neil. Accurate Atmospheric Scattering. In GPU Gems. Edited by Matt Pharr and Randima Fernando. 2005.

[Ring13a] Kevin Ring. Horizon Culling. 2013.

[Ring13b] Kevin Ring. Computing the horizon occlusion point. 2013.

相關文章
相關標籤/搜索