CesiumJS 2022^ 源碼解讀[1] 使用 requestAnimationFrame 循環(huán)觸發(fā)幀動畫
0. 前置約定
- 對類的使用,不添加
Cesium命名空間前綴,例如對于Viewer,不會寫Cesium.Viewer,默認(rèn)使用ESM格式解構(gòu)導(dǎo)入類; - JavaScript 代碼使用最簡格式(源碼除外),不加分號,不用雙引號,少注釋,雙空格縮進(jìn)
本系列說明
佛系連載,想到什么寫什么。
2022 年,寫原理類的文顯得非常“蠢”,大家都想吃快餐,看效果。法克雞絲老哥的系列博客思路跳躍很快,單步說明之間的信息量很大,需要消化很長時間才能啃完一篇文章,遂決定另開一個風(fēng)格,提綱挈領(lǐng)地把主要關(guān)鍵邏輯大白話說說 —— 可不是真的“大白話”,還是要有一些功底的。
我寫這個,只是為了從 CesiumJS 的渲染架構(gòu)中汲取一些營養(yǎng),希望對自己的程序設(shè)計能力有提高,希望能從其它繪圖 API 的角度看看能不能優(yōu)化和實(shí)現(xiàn)。
1. 開始
很多人寫 CesiumJS 程序是從 Viewer 開始的
new Viewer('container') // div id
你若只需要一個最干凈的場景(此場景非 Scene 類),不需要時間條、時間控制器、右上角一堆的按鈕,只需要
new CesiumWidget('container') // div id
CesiumJS 內(nèi)置了大量的默認(rèn)值,以至于簡單到你可以只傳遞 DOM 的 id 或本身即可創(chuàng)建場景。
1.1. CesiumWidget 類是控制場景對象觸發(fā)渲染的調(diào)度器
Scene 類是一個三維空間對象的容器,它在原型鏈上有一個 render 方法,寥寥百行,控制了三維場景中若干物體的更新、渲染。
Scene.prototype.render 方法調(diào)用一次,只更新并渲染一幀。
眾所周知,WebGL 一般會和 requestAnimationFrame, rAF 這個 API 循環(huán)調(diào)用渲染函數(shù)。而讓 canvas 中場景能連續(xù)多幀循環(huán)往復(fù)運(yùn)行的調(diào)度者,是 CesiumWidget 類。
CesiumWidget 類有一個使用 Object.defineProperties() 方法定義的 setter:
useDefaultRenderLoop: {
get: function () {
return this._useDefaultRenderLoop;
},
set: function (value) {
if (this._useDefaultRenderLoop !== value) {
this._useDefaultRenderLoop = value;
if (value && !this._renderLoopRunning) {
startRenderLoop(this);
}
}
},
}
在實(shí)例化 CesiumWidget 時,它會使用傳入的值,若沒有,則是 true:
this._useDefaultRenderLoop = undefined;
this.useDefaultRenderLoop = defaultValue(
options.useDefaultRenderLoop,
true
);
一旦賦值,就開始了 CesiumJS 的渲染循環(huán),是一個在 模塊內(nèi) 的函數(shù) startRenderLoop 負(fù)責(zé)控制的。
function startRenderLoop(widget) {
// ... 節(jié)約篇幅,此處非源碼,省略大量代碼層級,有興趣自己看源碼
function render(frameTime) {
// ...
widget.render()
requestAnimationFrame(render)
// ...
}
requestAnimationFrame(render)
}
傳入的 widget 是 CesiumWidget 實(shí)例,通過 requestAnimationFrame 的調(diào)用,則不斷地在調(diào)用這個函數(shù)內(nèi)的局部函數(shù) render。
render 函數(shù)內(nèi)調(diào)用 widget 的 render 方法,再往下就是調(diào)用 widget 所擁有的 scene 的 render 方法了。
1.2. Scene 對象
接上文說。
于全局,CesiumWidget 負(fù)責(zé)控制 DOM 的變化情況,例如窗口尺寸變化導(dǎo)致 DIV 的變化等,并負(fù)責(zé)起 渲染循環(huán) 的調(diào)度。
于單幀,Scene 類則需要使用自己原型鏈上的 render 方法完成自我狀態(tài)、數(shù)據(jù)對象的更新,以及 Scene.js 模塊內(nèi)的 render 函數(shù)觸發(fā) WebGL 繪制。
Scene 類是一個場景對象容器,其 render 方法負(fù)責(zé):
- 生命周期事件(preUpdate、preRender、postUpdate、postRender)回調(diào)觸發(fā);
- 更新幀狀態(tài)和幀號
- 更新 Scene 中的 Primitive
- 移交渲染權(quán)給模塊內(nèi)的
render函數(shù)觸發(fā) WebGL 繪制
2. 三維地球哪來的?
CesiumJS 的三維地球,實(shí)際上分兩大部分:
- 地球橢球體與表面的 GIS 影像服務(wù)
- 場景中的三維物體
我說過了,CesiumJS 內(nèi)置了大量的默認(rèn)值,包括地球橢球體以及影像服務(wù)(默認(rèn)用的必應(yīng)瓦片服務(wù),要 token)。但是,實(shí)際上可以不需要地球橢球體和底圖的:
if (defined(scene.globe)) {
scene.globe.beginFrame(frameState);
}
上述代碼片段是 Scene.js 模塊內(nèi)的 render 函數(shù)的一小段,也就是說,若沒有定義 globe,那就不繪制橢球上的幀。
3. 本篇總結(jié)
綜上 1、2 節(jié),我認(rèn)為 CesiumJS 的渲染循環(huán),到本文 1.2 小節(jié)末尾提及的 Scene.js 模塊內(nèi) render 函數(shù)的調(diào)用,觸發(fā) WebGL 繪制,就算一幀的邏輯結(jié)束,沒有必要再向下探究 Primitive、DataSource、Globe 等數(shù)據(jù)實(shí)體的更新和渲染,也沒有必要深究 WebGL 在 CesiumJS 中如何調(diào)度 —— 那都不是渲染循環(huán)的主要內(nèi)容。
Scene 原型鏈上的 render 函數(shù)并沒有更新橢球體,沒有請求地形四叉樹瓦片,而是等待更重要的 Primitive 等三維物體的更新后,才判斷 globe 是否存在,從而決定要不要畫地球(的皮膚),最終才更新并執(zhí)行 Command,也就是 scene.updateAndExecuteCommands(passState, backgroundColor); 一句代碼。

浙公網(wǎng)安備 33010602011771號