javascript
CesiumJS 2022^ 原理[2] 渲染架构之三维物体 - 创建并执行指令
Python微信訂餐小程序課程視頻
https://blog.csdn.net/m0_56069948/article/details/122285951
Python實(shí)戰(zhàn)量化交易理財(cái)系統(tǒng)
https://blog.csdn.net/m0_56069948/article/details/122285941
目錄* 回顧
+ 預(yù)備知識:指令
+ 預(yù)備知識:通道
- 1. 生成并執(zhí)行指令
- 1.1. Primitive 生成指令
- 1.2. Context 對象負(fù)責(zé)執(zhí)行 WebGL 底層代碼
- 2. 多段視錐體技術(shù)
- 3. 指令對象的轉(zhuǎn)移
- 篩選可見集
- 4. 本篇總結(jié)
回顧
書接上文,Scene.js 模塊內(nèi)的 render 函數(shù)會將控制權(quán)交給 WebGL,執(zhí)行 CesiumJS 自己封裝的指令對象,畫出每一幀來。
模塊內(nèi)的 render 函數(shù)首先會更新一批狀態(tài)信息,譬如幀狀態(tài)、霧效、Uniform 值、通道狀態(tài)、三維場景中的環(huán)境信息等,然后就開始更新并執(zhí)行指令,調(diào)用的是 Scene 原型鏈上的 updateAndExecuteCommands 方法。
整個(gè)過程大致的調(diào)用鏈?zhǔn)沁@樣的(function 關(guān)鍵字簡寫為 fn):
[Module Scene.js] - fn render()- Scene.prototype.updateAndExecuteCommands()- fn executeCommandsInViewport()- fn updateAndRenderPrimitives()[Module Primitive.js]- fn createCommands()- fn updateAndQueueCommands()- fn executeCommands()- fn executeCommand()本篇講解的是從 Scene 原型鏈上的 updateAndExcuteCommands() 方法開始,期間 Scene 中的 Primitives 是如何創(chuàng)建指令,又最終如何被 WebGL 執(zhí)行的。
這個(gè)過程涉及非常多細(xì)節(jié)代碼,但是為了快速聚焦整個(gè)過程,本篇先介紹兩個(gè) CesiumJS 封裝的概念:指令和通道。
預(yù)備知識:指令
WebGL 是一種依賴于“全局狀態(tài)”的繪圖 API,面向?qū)ο筇卣鞅容^弱,為了修改全局狀態(tài)上的頂點(diǎn)數(shù)據(jù)、著色器程序、幀緩沖、紋理等“資源”,必須通過 gl.XXX 函數(shù)調(diào)用觸發(fā)全局狀態(tài)的改變。
而圖形編程基礎(chǔ)提出的渲染管線、通道等概念偏向于面向?qū)ο?#xff0c;顯然 WebGL 這種偏過程的風(fēng)格需要被 JavaScript 運(yùn)行時(shí)引擎封裝。
CesiumJS 將 WebGL 的繪制過程,也就是行為,封裝成了“指令”,不同的指令對象有不同的用途。指令對象保存的行為,具體就是指由 Primitive 對象(不一定全是 Primitive)生成的 WebGL 所需的數(shù)據(jù)資源(緩沖、紋理、唯一值等),以及著色器對象。數(shù)據(jù)資源和著色器對象仍然是 CesiumJS 封裝的對象,而不是 WebGL 原生的對象,這是為了更好地與 CesiumJS 各種對象結(jié)合去繪圖。
CesiumJS 有三類指令:
- DrawCommand 繪圖指令
- ClearCommand 清屏指令
- ComputeCommand 計(jì)算指令
繪圖指令最終會把控制權(quán)交給 Context 對象,根據(jù)自身的著色器對象,繪制自己身上的數(shù)據(jù)資源。
清屏指令比較簡單,目的就是調(diào)用 WebGL 的清屏函數(shù),清空各類緩沖區(qū)并填充清空后的顏色值,依舊會把控制權(quán)傳遞給 Context 對象。
計(jì)算指令則借助 WebGL 并行計(jì)算的特點(diǎn),將指令對象上的數(shù)據(jù)在著色器中編碼、計(jì)算、解碼,最后寫入到輸出緩沖(通常是幀緩沖的紋理上),同樣控制權(quán)會給到 Context 對象。
預(yù)備知識:通道
一幀是由多個(gè)通道順序繪制構(gòu)成的,在 CesiumJS 中,通道英文單詞是 Pass。
既然通道的繪制是有順序的,其順序保存在 Renderer/Pass.js 模塊導(dǎo)出的凍結(jié)對象中,目前(1.92版本)有 10 個(gè)優(yōu)先順序等級:
const Pass = {ENVIRONMENT: 0,COMPUTE: 1,GLOBE: 2,TERRAIN\_CLASSIFICATION: 3,CESIUM\_3D\_TILE: 4,CESIUM\_3D\_TILE\_CLASSIFICATION: 5,CESIUM\_3D\_TILE\_CLASSIFICATION\_IGNORE\_SHOW: 6,OPAQUE: 7,TRANSLUCENT: 8,OVERLAY: 9,NUMBER\_OF\_PASSES: 10, }以上為例,第一優(yōu)先被繪制的指令,是環(huán)境(ENVIRONMENT: 0)方面的對象、物體。而不透明(OPAQUE: 7)的三維對象的繪制指令,則要先于透明(TRANSLUCENT: 8)物體被執(zhí)行。
CesiumJS 會在每一幀即將開始繪制前,對所有已經(jīng)收集好的指令根據(jù)通道進(jìn)行排序,實(shí)現(xiàn)順序繪制,下文會細(xì)談。
1. 生成并執(zhí)行指令
原型鏈上的 updateAndExecuteCommands 方法會做模式判斷,我們一般使用的是三維模式(SceneMode.SCENE3D),所以只需要看 else if 分支即可,也就是
executeCommandsInViewport(true, this, passState, backgroundColor);此處,this 就是 Scene 自己。
executeCommandsInViewport() 是一個(gè) Scene.js 模塊內(nèi)的函數(shù),這個(gè)函數(shù)比較短,對于當(dāng)前我們關(guān)心的東西,只需要看它調(diào)用的 updateAndRenderPrimitives() 和最后的 executeCommands() 函數(shù)調(diào)用即可。
1.1. Primitive 生成指令
[Module Scene.js] - fn updateAndRenderPrimitives()[Module Primitive.js]- fn createCommands()- fn updateAndQueueCommands()Scene.js 模塊內(nèi)的函數(shù) updateAndRenderPrimitives() 負(fù)責(zé)更新 Scene 上所有的 Primitive。
期間,控制權(quán)會通過 PrimitiveCollection 轉(zhuǎn)移到 Primitive 類(或者有類似結(jié)構(gòu)的類,譬如 Cesium3DTileset 等)上,令其更新本身的數(shù)據(jù)資源,根據(jù)情況創(chuàng)建新的著色器,并隨之創(chuàng)建 繪圖指令,最終在 Primitive.js 模塊內(nèi)的 updateAndQueueCommands() 函數(shù)排序、并推入幀狀態(tài)對象的指令列表上。
1.2. Context 對象負(fù)責(zé)執(zhí)行 WebGL 底層代碼
[Module Scene.js] - fn executeCommands() - fn executeCommand() // 收到 Command 和 Context[Module Context.js]- Context.prototype.draw()另一個(gè)模塊內(nèi)的函數(shù) executeCommands() 則負(fù)責(zé)執(zhí)行這些指令(中間還有一些小插曲,下文再提)。
此時(shí),上文的“通道”再次起作用,此函數(shù)內(nèi)會根據(jù) Pass 的優(yōu)先順序依次更新唯一值狀態(tài)(UniformState),然后下發(fā)給 executeCommand() 函數(shù)(注意少了個(gè)s)以具體的某個(gè)指令對象以及 Context 對象。
除了 executeCommand() 函數(shù)外,Scene.js 模塊內(nèi)仍還有其它類似的函數(shù),例如 executeIdCommand() 負(fù)責(zé)執(zhí)行繪制 ID 信息紋理的指令,executeTranslucentCommandsBackToFront() / executeTranslucentCommandsFrontToBack() 函數(shù)負(fù)責(zé)透明物體的指令等。甚至在 Scene 對象自己的屬性中,就有清屏指令字段,會在 executeCommands() 函數(shù)中直接執(zhí)行,不經(jīng)過上述幾個(gè)執(zhí)行具體指令的函數(shù)。
Context 對象是對 WebGL(2)RenderingContext 等 WebGL 原生接口、參數(shù)的封裝,所有指令對象最終都會由其進(jìn)行拆包裝、組裝 WebGL 函數(shù)調(diào)用并執(zhí)行繪圖、計(jì)算、清屏(見上文介紹的預(yù)備知識:指令)。
2. 多段視錐體技術(shù)
先介紹一個(gè)概念,WebGL 中的深度。
簡單的說,屏幕朝里,三維物體的前后順序就是深度。CesiumJS 的深度非常大,即使不考慮遠(yuǎn)太空,只考慮地球表面附近的范圍,WebGL 的數(shù)值范圍也不太夠用。聰明的前輩們想到了使用對數(shù)函數(shù)壓縮深度的值域,因?yàn)橐粋€(gè)非常大的數(shù)字只需取其對數(shù),很快就能小下來。
Scene 對象上有一個(gè)可讀可寫訪問器 logarithmicDepthBuffer,它指示是否啟用對數(shù)深度。
現(xiàn)在,CesiumJS 通常使用的就是對數(shù)深度。
對數(shù)深度解決的不僅僅只是普通深度值值域不夠的問題,還解決了不支持對數(shù)深度技術(shù)之前使用的多段視錐技術(shù)問題。
再次簡單的說,多段視錐技術(shù)將視錐體由遠(yuǎn)及近切成多個(gè)段,保證了相機(jī)近段的指令足夠多以保證效果,遠(yuǎn)段盡量少滿足性能。
你在 Scene.js 模塊中的 executeCommands() 函數(shù)的最后能找到一個(gè)循環(huán)體:
// Execute commands in each frustum in back to front order let j; for (let i = 0; i < numFrustums; ++i) {// ... }打開調(diào)試工具,很容易擊中這個(gè)斷點(diǎn),查看 numFrustums 變量的值,有啟用對數(shù)深度的 CesiumJS 程序,一般 numFrustums 都是 1。
3. 指令對象的轉(zhuǎn)移
在本文第 1 節(jié)中,詳細(xì)說明了指令對象的生成與被執(zhí)行。
上述其實(shí)忽略了很多細(xì)節(jié),現(xiàn)在撿起來說。
指令對象在 Primitive(或類似的類)生成后,由 模塊內(nèi)的 updateAndQueueCommands() 函數(shù)排序并推入幀狀態(tài)對象的指令列表上:
// updateAndQueueCommands 函數(shù)中,函數(shù)接收來自 Scene 逐級傳入的幀狀態(tài)對象 -- frameState const commandList = frameState.commandList; const passes = frameState.passes; if (passes.render || passes.pick) {// ... 省略部分代碼commandList.push(colorCommand); }frameState.commandList 就是個(gè)普通的數(shù)組。
而在執(zhí)行時(shí),卻是從 View 對象上的 frustumCommandsList 上取的指令:
// Scene.js 模塊的 executeCommands 函數(shù)中const frustumCommandsList = view.frustumCommandsList; const numFrustums = frustumCommandsList.length;let j; for (let i = 0; i < numFrustums; ++i) {const index = numFrustums - i - 1;const frustumCommands = frustumCommandsList[index];// ...// 截取不透明物體的通道指令執(zhí)行代碼片段us.updatePass(Pass.OPAQUE);commands = frustumCommands.commands[Pass.OPAQUE];length = frustumCommands.indices[Pass.OPAQUE];for (j = 0; j < length; ++j) {executeCommand(commands[j], scene, context, passState);}// ... }其中,下發(fā)給 executeCommand() 函數(shù)的 commands[j] 參數(shù),就是具體的某個(gè)指令對象。
所以這兩個(gè)過程之間,是怎么做指令對象傳遞的?
答案就在 View 原型鏈上的 createPotentiallyVisibleSet 方法中。
篩選可見集
View 對象是 Scene、Camera 之間的紐帶,負(fù)責(zé)“眼睛”與“世界”之間信息的處理,即視圖。
View 原型鏈上的 createPotentiallyVisibleSet 方法的作用,就是把 Scene 上的計(jì)算指令、覆蓋物指令,幀狀態(tài)上的指令列表,根據(jù) View 的可見范圍做一次篩選,減少要執(zhí)行指令個(gè)數(shù)提升性能。
具體來說,就是把分散在各處的指令,篩選后綁至 View 對象的 frustumCommandsList 列表中,借助 View.js 模塊內(nèi)的 insertIntoBin() 函數(shù)完成:
// View.js 模塊內(nèi)function insertIntoBin(view, scene, command, commandNear, commandFar) {// ...const frustumCommandsList = view.frustumCommandsList;const length = frustumCommandsList.length;for (let i = 0; i < length; ++i) {// ...frustumCommands.commands[pass][index] = command;// ...}// ... }這個(gè)函數(shù)內(nèi)做了對指令的篩選判斷。
4. 本篇總結(jié)
本篇調(diào)查清楚了 Scene 對象上各種三維物體是如何繪制的,即借助 指令 對象保存待繪制的信息,最后交由 Context 對象完成 WebGL 代碼的執(zhí)行。
期間,發(fā)生了指令的分類和可見集的篩選;篇幅原因,CesiumJS 在這漫長的渲染過程中還做了很多細(xì)節(jié)的事情。
不過,Cesium 的三維物體的渲染架構(gòu)就算講完了。
接下來,則是另兩個(gè)比較頭痛的話題:
- 地球的渲染架構(gòu)(瓦片四叉樹)
- 具備創(chuàng)建指令的各路數(shù)據(jù)源(Entity、DataSource、Model、Cesium3DTileset等)
指令和通道的概念仍然會繼續(xù)使用,敬請期待。
-
回顧
-
預(yù)備知識:指令
-
預(yù)備知識:通道
-
1. 生成并執(zhí)行指令
-
1.1. Primitive 生成指令
-
1.2. Context 對象負(fù)責(zé)執(zhí)行 WebGL 底層代碼
-
2. 多段視錐體技術(shù)
-
3. 指令對象的轉(zhuǎn)移
-
篩選可見集
-
4. 本篇總結(jié)
__EOF__
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-16LUkhXl-1649652132633)(https://blog.csdn.net/onsummer)]四季留歌 - 本文鏈接: https://blog.csdn.net/onsummer/p/16129435.html
- 關(guān)于博主: 評論和私信會在第一時(shí)間回復(fù)。或者直接私信我。
- 版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請注明出處!
- 聲援博主: 如果您覺得文章對您有幫助,可以點(diǎn)擊文章右下角**【[推薦](javascript:void(0)😉】**一下。
總結(jié)
以上是生活随笔為你收集整理的CesiumJS 2022^ 原理[2] 渲染架构之三维物体 - 创建并执行指令的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Rockwell AB PLC 控制器E
- 下一篇: JavaScript 函数循环、延时、节