CesiumJS 2022^ 原理[1] 使用 requestAnimationFrame 循環觸發幀動畫


0. 前置約定

  • 對類的使用,不添加 Cesium 命名空間前綴,例如對於 Viewer,不會寫 Cesium.Viewer,默認使用 ESM 格式解構導入類;
  • JavaScript 代碼使用最簡格式(源碼除外),不加分號,不用雙引號,少註釋,雙空格縮進

本系列說明

佛系連載,想到什麼寫什麼。

2022 年,寫原理類的文顯得非常“蠢”,大家都想喫快餐,看效果。法克雞絲老哥的系列博客思路跳躍很快,單步說明之間的信息量很大,需要消化很長時間才能啃完一篇文章,遂決定另開一個風格,提綱挈領地把主要關鍵邏輯大白話說說 —— 可不是真的“大白話”,還是要有一些功底的。

我寫這個,只是爲了從 CesiumJS 的渲染架構中汲取一些營養,希望對自己的程序設計能力有提高,希望能從其它繪圖 API 的角度看看能不能優化和實現。

1. 開始

很多人寫 CesiumJS 程序是從 Viewer 開始的

new Viewer('container') // div id

你若只需要一個最乾淨的場景(此場景非 Scene 類),不需要時間條、時間控制器、右上角一堆的按鈕,只需要

new CesiumWidget('container') // div id

CesiumJS 內置了大量的默認值,以至於簡單到你可以只傳遞 DOM 的 id 或本身即可創建場景。

1.1. CesiumWidget 類是控制場景對象觸發渲染的調度器

Scene 類是一個三維空間對象的容器,它在原型鏈上有一個 render 方法,寥寥百行,控制了三維場景中若干物體的更新、渲染。

Scene.prototype.render 方法調用一次,只更新並渲染一幀。

衆所周知,WebGL 一般會和 requestAnimationFrame, rAF 這個 API 循環調用渲染函數。而讓 canvas 中場景能連續多幀循環往復運行的調度者,是 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);
      }
    }
  },
}

在實例化 CesiumWidget 時,它會使用傳入的值,若沒有,則是 true

this._useDefaultRenderLoop = undefined;
this.useDefaultRenderLoop = defaultValue(
  options.useDefaultRenderLoop,
  true
);

一旦賦值,就開始了 CesiumJS 的渲染循環,是一個在 模塊內 的函數 startRenderLoop 負責控制的。

function startRenderLoop(widget) {
  // ... 節約篇幅,此處非源碼,省略大量代碼層級,有興趣自己看源碼
  function render(frameTime) {
    // ...
    widget.render()
    requestAnimationFrame(render)
    // ...
  }

  requestAnimationFrame(render)
}

傳入的 widgetCesiumWidget 實例,通過 requestAnimationFrame 的調用,則不斷地在調用這個函數內的局部函數 render

render 函數內調用 widgetrender 方法,再往下就是調用 widget 所擁有的 scene 的 render 方法了。

1.2. Scene 對象

接上文說。

於全局,CesiumWidget 負責控制 DOM 的變化情況,例如窗口尺寸變化導致 DIV 的變化等,並負責起 渲染循環 的調度。

於單幀,Scene 類則需要使用自己原型鏈上的 render 方法完成自我狀態、數據對象的更新,以及 Scene.js 模塊內的 render 函數觸發 WebGL 繪製。

Scene 類是一個場景對象容器,其 render 方法負責:

  • 生命週期事件(preUpdate、preRender、postUpdate、postRender)回調觸發;
  • 更新幀狀態和幀號
  • 更新 Scene 中的 Primitive
  • 移交渲染權給模塊內的 render 函數觸發 WebGL 繪製

2. 三維地球哪來的?

CesiumJS 的三維地球,實際上分兩大部分:

  • 地球橢球體與表面的 GIS 影像服務
  • 場景中的三維物體

我說過了,CesiumJS 內置了大量的默認值,包括地球橢球體以及影像服務(默認用的必應瓦片服務,要 token)。但是,實際上可以不需要地球橢球體和底圖的:

  if (defined(scene.globe)) {
    scene.globe.beginFrame(frameState);
  }

上述代碼片段是 Scene.js 模塊內的 render 函數的一小段,也就是說,若沒有定義 globe,那就不繪製橢球上的幀。

3. 本篇總結

綜上 1、2 節,我認爲 CesiumJS 的渲染循環,到本文 1.2 小節末尾提及的 Scene.js 模塊內 render 函數的調用,觸發 WebGL 繪製,就算一幀的邏輯結束,沒有必要再向下探究 Primitive、DataSource、Globe 等數據實體的更新和渲染,也沒有必要深究 WebGL 在 CesiumJS 中如何調度 —— 那都不是渲染循環的主要內容。

Scene 原型鏈上的 render 函數並沒有更新橢球體,沒有請求地形四叉樹瓦片,而是等待更重要的 Primitive 等三維物體的更新後,才判斷 globe 是否存在,從而決定要不要畫地球(的皮膚),最終才更新並執行 Command,也就是 scene.updateAndExecuteCommands(passState, backgroundColor); 一句代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章