著作權沒有,請尊重翻譯成果,有翻譯錯誤請指出,規范性轉載,@秋意正寒
本文通過解讀 Scene.render 方法,觀察 WebGL 在 Cesium 1.9 中如何渲染一幀,讀者可以在 Scene.render 方法處打斷點進入除錯,
由于 Cesium 專注于可視化地理空間內容,因此多光源的場景并不擅長、不多見,Cesium 使用的是傳統的前向陰影流水線,Cesium 的流水線之所以獨特,是因為它使用了多個視錐體來支持大范圍的視距,而不需要對z軸進行扭曲變化(這句翻譯得不是很好),
起步
Cesium 把每一幀的生命周期相關的資料存盤在一個叫 FrameState(參考 FrameState.js) 的物件中,在幀最開始時,初始化相機引數、時間之類的東西,幀的狀態可用于其他的物件,例如 Primitive 物件可以呼叫當前幀的狀態資料,
UniformState(參考 UniformState.js)是 FrameState 的一部分,它具有共有的、預先計算好的 uniforms,在幀開始時,它計算視圖矩陣、太陽向量等 uniforms,
更新
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.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/226491.html
標籤:GIS
