1. 程式人生 > 其它 >3dTiles 幾何誤差詳解【轉】

3dTiles 幾何誤差詳解【轉】

3dTiles 幾何誤差詳解

轉載請註明出處。全網@秋意正寒

1. 瓦片的排程

查閱tileset.json的規範,有一個屬性是refine,它有兩個值:"ADD""REPLACE"

還有另一個屬性,叫geometricError,是一個數字。

"ADD"的含義是,當這一級瓦片顯示不夠精細時,渲染下一級瓦片,這一級的瓦片保留繼續顯示(增加下一級的內容)。

"REPLACE"的含義是,當這一級瓦片顯示不夠精細時,渲染下一級瓦片,這一級的瓦片被銷燬(被下一級“替換”)。

如何衡量這個“不夠精細”?

一個很簡單的思路是利用觀察點(也就是相機)到觀察瓦片的距離來判斷。這個相機與瓦片的距離超過我指定的某個閾值的時候,就要渲染下一級瓦片,而這一級瓦片則根據refine

的值進行保留或銷燬。

所謂的 “指定的某個閾值”,在這裡有一個專有名詞:最大螢幕空間誤差(maximumScreenSpaceError)。

這個值是Cesium3DTileset類中的例項屬性,預設值是16.

暫且不說這個16的具體含義,先回顧剛才的思路:計算相機到瓦片的距離,設為distance,就能與這個值進行比較了嗎?不是的。

1.1 螢幕空間誤差(ScreenSpaceError, sse)

計算當前瓦片的螢幕空間誤差值,才能與maximumScreenSpaceError進行比較,因為這兩個才是同一種東西嘛。

先說結論:螢幕空間誤差(ScreenSpaceError, sse)由幾何誤差、相機狀態有關的各項引數計算而來。

也就是說,只要 Cesium 在跑,這個 sse 就是一幀一幀實時計算的,每時每刻都在計算。

查閱Cesium3DTile的原始碼,不難得知它的計算方法被定義在Cesium3DTile中(可以跳過程式碼不看):

// Cesium3DTile.js >> Cesium3DTile.prototype.getScreenSpaceError()
Cesium3DTile.prototype.getScreenSpaceError = function (
  frameState,
  useParentGeometricError,
  progressiveResolutionHeightFraction
) {
  
var tileset = this._tileset; var heightFraction = defaultValue(progressiveResolutionHeightFraction, 1.0); var parentGeometricError = defined(this.parent) ? this.parent.geometricError : tileset._geometricError; var geometricError = useParentGeometricError ? parentGeometricError : this.geometricError; if (geometricError === 0.0) { // Leaf tiles do not have any error so save the computation return 0.0; } var camera = frameState.camera; var frustum = camera.frustum; var context = frameState.context; var width = context.drawingBufferWidth; var height = context.drawingBufferHeight * heightFraction; var error; if ( frameState.mode === SceneMode.SCENE2D || frustum instanceof OrthographicFrustum ) { if (defined(frustum._offCenterFrustum)) { frustum = frustum._offCenterFrustum; } var pixelSize = Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) / Math.max(width, height); error = geometricError / pixelSize; } else { // Avoid divide by zero when viewer is inside the tile var distance = Math.max(this._distanceToCamera, CesiumMath.EPSILON7); var sseDenominator = camera.frustum.sseDenominator; error = (geometricError * height) / (distance * sseDenominator); if (tileset.dynamicScreenSpaceError) { var density = tileset._dynamicScreenSpaceErrorComputedDensity; var factor = tileset.dynamicScreenSpaceErrorFactor; var dynamicError = CesiumMath.fog(distance, density) * factor; error -= dynamicError; } } error /= frameState.pixelRatio; return error; };

這麼長,其實在我們關心的三維模式(即 frameState.mode 為 SceneMode.SCENE3D)下,最核心的只有一句程式碼:

error = (geometricError * height) / (distance * sseDenominator);

其中,

  • error即計算得到的 sse 螢幕空間誤差
  • geometricError即當前瓦片設定好的幾何誤差,寫在 tileset.json 中
  • height即瀏覽器當前執行著 Cesium 的那個 canvas 的畫素高度,如果沒有自己設定progressiveResolutionHeightFraction值,通常height值就是canvas 的畫素高度,如果你的 Cesium 佔據了全屏,你的顯示器解析度是 1920 × 1080,那麼這個height在你瀏覽器全屏時,通常是 936 畫素。
  • distance是當前狀態下,攝像機的世界座標位置到瓦片中心位置的距離,單位是米
  • sseDenominator是一個根據當前相機狀態下,根據視錐體的張角(fov)、長寬比引數進行一系列三角計算、四則運算而來的一個引數,具體含義我沒有深究,但是通常狀態下,很少會去修改預設相機的引數,即張角 60 度,寬高比就是 1920÷936(就是canvas的畫素寬高比啦),所以這個值也是固定的,有興趣的讀者可以跟蹤這個引數的計算過程,還要往裡套五六層程式碼才知道計算過程。它翻譯過來就是“sse的分母”。

2. 推演

如果不對相機進行修改,使用預設的,而且你的螢幕是1920×1080,恰好你的 canvas 佔滿了 body,而且你的瀏覽器是最大化的狀態,那麼這個sseDenominator的值約為 0.5629165124598852

顯而易見,為了遮蔽螢幕解析度差異、瀏覽器是否最大化的差異,這個sseDenominator的值是會根據瀏覽器視窗狀態、canvas大小以及攝像機的狀態進行變化的。在此,我們假定就是 0.5629165124598852

那麼 上述程式碼改寫成:

sse=geometricError×936distance×0.5629165124598852sse=geometricError×936distance×0.5629165124598852

是否還記得一個引數:maximumScreenSpaceError?它的預設值是16

那麼,這個16就是一個臨界值,當sse<defaultMaximumScreenSpaceError=16sse<defaultMaximumScreenSpaceError=16時,下一級瓦片載入,此瓦片根據refine進行調整。

假設sse剛好等於16,那麼得到一個二元方程:

16=geometricError×936distance×0.562916512459885216=geometricError×936distance×0.5629165124598852

所以,這個等式表達的含義就是,當幾何誤差越大(分子變大),要想等式保持相等,那麼分母:distance(相機到瓦片的距離)也應變大,變大就意味著——根據refine來控制瓦片的顯隱或增補時,此瓦片觀察距離由此變大。

所以,這個幾何誤差是一個經驗值。

下結論:

在幾何誤差、相機狀態是固定值時,只要觀察距離 > 計算此幾何誤差的經驗距離,就會渲染下一級瓦片,此瓦片的refine規則若是REPLACE則消失,若是ADD則保留。
若渲染下一級瓦片,則當前瓦片的 sse 必定 < maximumScreenSpaceError。

3. 經驗值下的幾何誤差計算

還是以剛好到臨界值,也即預設的 16 時,為例。

設定某瓦片距離相機超過200米時,該瓦片到達臨界狀態。

代入上式,計算得到 geometricError 為:

geometricError=200×0.5629165124598852×16÷936=1.9245008972987geometricError=200×0.5629165124598852×16÷936=1.9245008972987

那麼現在這個瓦片的 sse 公式變成了:

sse=1.9245008972987×936distance×0.5629165124598852sse=1.9245008972987×936distance×0.5629165124598852

也就是距離越大,sse 越小。當距離超過200米,sse一定小於16,不妨設refineREPLACE

在檢視中觀察到小於200米時,此瓦片正常顯示,距離一旦大於200米,該瓦片就被REPLACE了,此時的sse肯定也小於16,只需調整maximumScreenSpaceError,在 CesiumLab 中叫顯示精度,調小一些,該瓦片又被顯示了。

不妨假設就按 1080p螢幕 + 全屏canvas + 最大化瀏覽器視窗 + 預設相機引數來算,列舉常見觀察距離的幾何誤差設定:

觀察距離幾何誤差
100 0.96225045
200 1.92450090
300 2.88675134
400 3.84900179
500 4.81125224
1000 9.62250447
2000 19.24500897

觀察不難得知,這是一個一次函式:

geometricError=f(distance)=distance×0.5629165124598852×16÷936geometricError=f(distance)=distance×0.5629165124598852×16÷936

而後面三個數字,則與相機、瀏覽器等因素有關,只要瀏覽器不變,相機不變,顯示器不變,那麼無論怎麼操作檢視,幾何誤差的計算都只跟經驗上的觀察距離有關。

4. 調參經驗

4.1. 當前視角如果不繼續放大,傾斜攝影的層級比較低(看起來模糊)怎麼辦

方案① 調整最大螢幕空間誤差

調大 maximumScreenSpaceError,因為幾何誤差不變、距離不變,等式左邊放大,勢必等號要變成大於號:

maximumScreenSpaceError>16=geometricError×936distance×0.5629165124598852maximumScreenSpaceError>16=geometricError×936distance×0.5629165124598852

根據上述結論,同等條件下最大螢幕空間誤差變大,導致計算結果小於最大螢幕空間誤差,從而引發下一級瓦片渲染,當前瓦片執行 "refine" 策略。

方案② 根治法:調整幾何誤差

不渲染下一級瓦片的原因無非是當前幾何誤差所代表的經驗距離太大了,不小於這個距離就沒法渲染。
解決方法很簡單,把幾何誤差調小即可,參考上文。

4.2. 想不載入這麼快

同 4.1. 的思路,減小maximumScreenSpaceError增大幾何誤差。

分類:開源GIS/Cesium原始碼 標籤:螢幕空間誤差,幾何誤差,Cesium,sse,geometricError,3dtiles,ScreenSpaceError