系轉(zhuǎn)換全解析:從經(jīng)緯高到虛幻世界坐標(biāo))
1. 從經(jīng)緯度到虛幻單位Cesium for UE4 坐標(biāo)系到底在折騰什么如果你剛把 Cesium for UE4 插件拖進(jìn)引擎興沖沖地加載了一塊真實(shí)地形結(jié)果發(fā)現(xiàn)相機(jī)飛到了十萬八千里外或者模型像被揉皺的紙團(tuán)一樣扭曲那大概率不是插件壞了而是坐標(biāo)系在跟你鬧脾氣。Cesium for UE4 的核心價值在于把真實(shí)地理空間數(shù)據(jù)無縫接入虛幻引擎而它最讓人頭疼也最迷人的地方就是那一套橫跨地理坐標(biāo)系、投影坐標(biāo)系和引擎世界坐標(biāo)的轉(zhuǎn)換體系。這篇文章不打算給你背手冊而是把我自己在項(xiàng)目里反復(fù)調(diào)試、踩坑、推翻重來的經(jīng)驗(yàn)攤開來講從底層邏輯到實(shí)操細(xì)節(jié)把坐標(biāo)系轉(zhuǎn)換這件事徹底聊透。先說清楚這套東西適合誰看。如果你只是想在 UE4 里擺幾個靜態(tài)模型那完全用不著 Cesium原生坐標(biāo)就夠了。但只要你涉及真實(shí)地形、傾斜攝影、BIM 模型落位、飛行軌跡模擬、或者任何需要把現(xiàn)實(shí)世界經(jīng)緯度映射到虛幻場景里的活兒坐標(biāo)系轉(zhuǎn)換就是繞不過去的坎。我見過太多項(xiàng)目卡在這一步地形加載出來了但建筑模型飄在半空相機(jī)能飛但飛到特定經(jīng)緯度就抖動兩個不同來源的數(shù)據(jù)疊在一起偏差幾百米。這些問題表面上看是“位置不對”根子上都是坐標(biāo)系沒對齊。Cesium for UE4 的坐標(biāo)系體系可以粗暴地分成三層最底層是地理坐標(biāo)經(jīng)度、緯度、高度中間層是地心直角坐標(biāo)ECEF最上層是虛幻引擎的世界坐標(biāo)UE World Coordinates。這三層之間的轉(zhuǎn)換不是簡單的加減乘除而是涉及橢球體參數(shù)、原點(diǎn)偏移、單位縮放、以及一個叫“原點(diǎn)重定位”的機(jī)制。很多人第一次接觸會覺得這套設(shè)計(jì)多此一舉直接給經(jīng)緯度不行嗎不行。因?yàn)樘摶靡娴母↑c(diǎn)精度有限如果把整個地球的坐標(biāo)直接塞進(jìn)場景離原點(diǎn)稍微遠(yuǎn)一點(diǎn)模型就會開始抖動甚至撕裂。Cesium 的解決方案是動態(tài)選擇一個“原點(diǎn)”把附近的數(shù)據(jù)拉近到引擎原點(diǎn)附近保證精度。這個原點(diǎn)選在哪里、怎么選、什么時候切換就是坐標(biāo)系轉(zhuǎn)換的核心戰(zhàn)場。我剛開始用的時候以為只要調(diào)用一個LongitudeLatitudeHeightToUnreal函數(shù)就完事了。結(jié)果發(fā)現(xiàn)同一個經(jīng)緯度在不同時間、不同相機(jī)位置下轉(zhuǎn)換出來的 UE 坐標(biāo)居然不一樣。當(dāng)時差點(diǎn)以為是插件有 bug后來才明白這是原點(diǎn)重定位在動態(tài)調(diào)整。理解這一點(diǎn)之后很多看似詭異的現(xiàn)象就都能解釋了。接下來的內(nèi)容我會從最基礎(chǔ)的地理坐標(biāo)講起一步步推到 ECEF再講到 UE 世界坐標(biāo)中間穿插大量實(shí)際調(diào)試中遇到的坑和解決方案。你不需要有測繪背景但需要有一點(diǎn)耐心因?yàn)檫@套東西一旦理順后面做地理空間項(xiàng)目就會像呼吸一樣自然。2. 地理坐標(biāo)與 ECEF一切轉(zhuǎn)換的起點(diǎn)2.1 經(jīng)緯高到底是怎么描述一個點(diǎn)的地理坐標(biāo)系用經(jīng)度、緯度、高度來描述地球表面或附近的點(diǎn)。經(jīng)度范圍是 -180 到 180 度緯度范圍是 -90 到 90 度高度是相對于參考橢球面的距離。這里有個容易混淆的地方高度通常指的是橢球高而不是海拔高。橢球高和海拔高之間差了一個大地水準(zhǔn)面差距這個差距在不同地區(qū)可能達(dá)到幾十米。Cesium for UE4 默認(rèn)使用的是 WGS84 橢球這也是 GPS 使用的標(biāo)準(zhǔn)。如果你從外部數(shù)據(jù)源拿到的是海拔高直接丟給 Cesium 可能會產(chǎn)生垂直方向的偏移這一點(diǎn)在精度要求高的項(xiàng)目里必須注意。經(jīng)度和緯度本身是角度不是距離。一度緯度對應(yīng)的地面距離大約是 111 公里但一度經(jīng)度對應(yīng)的距離隨緯度變化在赤道附近約 111 公里在兩極附近趨近于零。這個非線性關(guān)系是很多位置計(jì)算錯誤的根源。比如你想讓相機(jī)沿著經(jīng)度方向移動固定距離不能簡單地把經(jīng)度加一個固定值而要根據(jù)當(dāng)前緯度做換算。Cesium for UE4 提供了一些輔助函數(shù)來處理這類問題但理解背后的數(shù)學(xué)仍然很重要否則你很難判斷結(jié)果對不對。在實(shí)際項(xiàng)目中我通常會把所有外部數(shù)據(jù)先統(tǒng)一成 WGS84 經(jīng)緯高格式然后再交給 Cesium 處理。這樣做的好處是有一個明確的中間格式方便排查問題。如果數(shù)據(jù)源用的是其他橢球比如 CGCS2000雖然和 WGS84 差異很小但在大范圍場景下仍然可能累積出可觀的偏差。統(tǒng)一格式這一步看起來麻煩但能省掉后面無數(shù)次的“為什么位置不對”的困惑。2.2 ECEF 才是轉(zhuǎn)換的樞紐地心直角坐標(biāo)系ECEF以地球質(zhì)心為原點(diǎn)X 軸指向本初子午線與赤道的交點(diǎn)Z 軸指向北極Y 軸按右手定則確定。任何一個地理坐標(biāo)點(diǎn)都可以通過一組數(shù)學(xué)公式轉(zhuǎn)換成 ECEF 坐標(biāo)。這個轉(zhuǎn)換是確定性的不涉及任何動態(tài)調(diào)整所以它是整個坐標(biāo)系體系里最穩(wěn)定的一環(huán)。Cesium for UE4 內(nèi)部大量使用 ECEF 作為中間表示因?yàn)?ECEF 是笛卡爾坐標(biāo)可以直接做向量運(yùn)算而經(jīng)緯高是角度和距離的混合運(yùn)算起來很別扭。從經(jīng)緯高到 ECEF 的轉(zhuǎn)換公式涉及橢球的長半軸、短半軸和第一偏心率。具體來說先根據(jù)緯度計(jì)算卯酉圈曲率半徑然后分別計(jì)算 X、Y、Z 分量。這個公式在教科書上都能查到但實(shí)際寫代碼的時候要注意單位經(jīng)度緯度要先轉(zhuǎn)成弧度高度單位要和橢球參數(shù)一致。我見過有人把角度和弧度搞混結(jié)果點(diǎn)跑到了地球另一邊。Cesium for UE4 的 API 通常接受角度制但內(nèi)部會轉(zhuǎn)成弧度所以如果你自己寫轉(zhuǎn)換邏輯一定要確認(rèn)單位。ECEF 坐標(biāo)的數(shù)值范圍很大地球半徑約 6371 公里所以 ECEF 坐標(biāo)的絕對值在百萬米量級。這個量級直接放進(jìn)虛幻引擎的 float 變量里精度會嚴(yán)重不足。float 只有大約 7 位有效數(shù)字在百萬量級下最小分辨率大約是 0.1 米。對于大多數(shù)場景0.1 米的誤差可能可以接受但對于高精度應(yīng)用比如建筑構(gòu)件對齊就不夠了。這就是為什么 Cesium for UE4 不直接把 ECEF 坐標(biāo)給引擎而是要做原點(diǎn)重定位。理解這一點(diǎn)你就能明白為什么插件要設(shè)計(jì)得這么“繞”。2.3 橢球參數(shù)不是擺設(shè)WGS84 橢球的長半軸是 6378137 米扁率是 1/298.257223563。這兩個參數(shù)決定了地球的形狀模型。很多人覺得地球就是個球用球體公式算就行了但在大范圍場景下橢球和球體的差異會累積到幾十公里。比如在緯度 45 度附近橢球模型和球體模型計(jì)算出的 ECEF 坐標(biāo)差異可能達(dá)到十幾公里。對于地理空間項(xiàng)目這個誤差是不可接受的。Cesium for UE4 嚴(yán)格使用橢球模型所以你在做任何自定義轉(zhuǎn)換時也必須用橢球參數(shù)不能偷懶用球體近似。扁率的影響在極地附近尤其明顯。如果你在極地做項(xiàng)目用球體模型會導(dǎo)致垂直方向出現(xiàn)顯著偏差。我曾經(jīng)在一個高緯度地區(qū)的項(xiàng)目里因?yàn)樽远x的某個轉(zhuǎn)換函數(shù)用了球體近似導(dǎo)致地形和模型之間出現(xiàn)了明顯的錯位。排查了很久才發(fā)現(xiàn)是橢球參數(shù)的問題。從那以后我養(yǎng)成了一個習(xí)慣任何涉及地理坐標(biāo)的代碼第一行先確認(rèn)橢球參數(shù)是否正確。這個習(xí)慣幫我省了很多時間。除了長半軸和扁率有時候還會用到第二偏心率、卯酉圈曲率半徑等派生參數(shù)。這些參數(shù)不需要你手動計(jì)算Cesium for UE4 的 API 會處理。但如果你要自己實(shí)現(xiàn)轉(zhuǎn)換最好把這些公式封裝成函數(shù)避免每次手寫。我通常會把地理坐標(biāo)轉(zhuǎn)換相關(guān)的函數(shù)集中放在一個工具類里加上詳細(xì)的注釋和單位說明這樣團(tuán)隊(duì)里其他人用的時候不容易出錯。3. 原點(diǎn)重定位Cesium for UE4 的精度魔法3.1 為什么需要原點(diǎn)重定位虛幻引擎的世界坐標(biāo)使用 float 存儲float 的精度隨著數(shù)值增大而降低。在原點(diǎn)附近float 可以精確到微米級但在 100 公里外精度可能只有幾厘米在 1000 公里外精度可能達(dá)到米級。對于地理空間場景如果直接把 ECEF 坐標(biāo)減去一個固定偏移比如地球半徑得到的坐標(biāo)仍然在百萬米量級精度依然不夠。原點(diǎn)重定位的核心思想是動態(tài)選擇一個靠近當(dāng)前關(guān)注區(qū)域的原點(diǎn)把該區(qū)域附近的坐標(biāo)拉回到引擎原點(diǎn)附近從而保證 float 精度。這個原點(diǎn)不是固定的而是隨著相機(jī)或玩家位置移動而調(diào)整。Cesium for UE4 會根據(jù)當(dāng)前視圖中心或指定的原點(diǎn)位置計(jì)算一個局部的東-北-天ENU坐標(biāo)系然后把 ECEF 坐標(biāo)轉(zhuǎn)換到這個 ENU 坐標(biāo)系下再映射到 UE 的世界坐標(biāo)。ENU 坐標(biāo)系以原點(diǎn)為參考東方向?yàn)?X 軸北方向?yàn)?Y 軸天方向?yàn)?Z 軸。這個映射關(guān)系是線性的所以轉(zhuǎn)換效率很高。但要注意ENU 坐標(biāo)系是局部的只在小范圍內(nèi)近似平面大范圍下仍然有曲率影響。原點(diǎn)重定位帶來的一個直接后果是同一個地理坐標(biāo)在不同原點(diǎn)下轉(zhuǎn)換出的 UE 坐標(biāo)是不同的。這解釋了為什么你移動相機(jī)后某些物體的 UE 坐標(biāo)會變化。如果你在代碼里緩存了 UE 坐標(biāo)然后移動了原點(diǎn)緩存就會失效。我踩過這個坑在一個飛行模擬項(xiàng)目里我緩存了航路點(diǎn)的 UE 坐標(biāo)結(jié)果飛機(jī)飛到一半原點(diǎn)切換了飛機(jī)突然跳了一下。后來改成每次需要時重新轉(zhuǎn)換或者監(jiān)聽原點(diǎn)變化事件來更新緩存問題才解決。3.2 原點(diǎn)是怎么選出來的Cesium for UE4 默認(rèn)會根據(jù)相機(jī)的視點(diǎn)來自動選擇原點(diǎn)。具體策略是取相機(jī)前方一定距離處的點(diǎn)或者取場景中某個關(guān)鍵對象的經(jīng)緯度作為原點(diǎn)。這個策略在大多數(shù)情況下工作良好但在某些場景下會出問題。比如相機(jī)快速移動時原點(diǎn)頻繁切換導(dǎo)致物體抖動。或者相機(jī)看向天空時前方?jīng)]有地面點(diǎn)原點(diǎn)選擇可能不穩(wěn)定。在這些情況下手動指定原點(diǎn)會更可靠。手動指定原點(diǎn)的方法是通過CesiumGeoreference組件設(shè)置 Origin Longitude、Origin Latitude 和 Origin Height。這三個值決定了 ENU 坐標(biāo)系的原點(diǎn)位置。設(shè)置好之后所有坐標(biāo)轉(zhuǎn)換都基于這個固定原點(diǎn)不再動態(tài)調(diào)整。這樣做的好處是穩(wěn)定壞處是如果場景范圍很大遠(yuǎn)離原點(diǎn)的區(qū)域精度會下降。所以手動原點(diǎn)適合場景范圍有限、精度要求高的項(xiàng)目比如單個建筑群或小范圍地形。我通常的做法是先讓自動原點(diǎn)跑起來觀察場景中是否有抖動或跳變。如果有再切換到手動原點(diǎn)把原點(diǎn)設(shè)在場景中心或主要關(guān)注區(qū)域。設(shè)置原點(diǎn)時高度不要設(shè)為零最好設(shè)在實(shí)際地形高度附近這樣可以減少垂直方向的偏移。另外原點(diǎn)一旦設(shè)定就不要頻繁修改因?yàn)樾薷脑c(diǎn)會導(dǎo)致所有已轉(zhuǎn)換的坐標(biāo)失效。如果確實(shí)需要修改要確保所有依賴坐標(biāo)的組件都能正確響應(yīng)。3.3 原點(diǎn)切換時的平滑處理即使使用自動原點(diǎn)Cesium for UE4 也會盡量平滑地切換原點(diǎn)避免突兀的跳變。它通過一個過渡機(jī)制在舊原點(diǎn)和新原點(diǎn)之間做插值讓物體看起來是連續(xù)移動的。但這個平滑處理不是萬能的如果原點(diǎn)切換幅度太大或者物體本身在快速移動仍然可能出現(xiàn)視覺上的異常。我在調(diào)試一個地面車輛模擬時發(fā)現(xiàn)車輛在原點(diǎn)切換時會輕微下沉后來發(fā)現(xiàn)是因?yàn)檐囕v的高度計(jì)算依賴于原點(diǎn)高度原點(diǎn)切換時高度參考變了。要減少這類問題可以在原點(diǎn)切換時暫停物理模擬或者對關(guān)鍵物體的坐標(biāo)做額外補(bǔ)償。Cesium for UE4 提供了一些事件和委托可以監(jiān)聽原點(diǎn)變化。我通常會在原點(diǎn)變化時重新計(jì)算所有動態(tài)物體的位置而不是依賴引擎的自動更新。這樣做雖然多寫一些代碼但能保證行為可控。另外如果場景中有多個相機(jī)或視口原點(diǎn)選擇可能會沖突這時候最好統(tǒng)一使用一個主相機(jī)來決定原點(diǎn)其他相機(jī)通過相對坐標(biāo)來定位。還有一個細(xì)節(jié)原點(diǎn)重定位不僅影響位置還影響旋轉(zhuǎn)。ENU 坐標(biāo)系的軸向和 UE 世界坐標(biāo)系的軸向不是簡單對齊的需要根據(jù)原點(diǎn)的經(jīng)緯度計(jì)算旋轉(zhuǎn)矩陣。這個旋轉(zhuǎn)矩陣把東-北-天方向映射到 UE 的 X-Y-Z 方向。如果你手動設(shè)置物體的旋轉(zhuǎn)要注意這個映射關(guān)系。我見過有人直接把地理方向的角度賦給 UE 的旋轉(zhuǎn)結(jié)果方向完全不對。正確的做法是先計(jì)算 ENU 坐標(biāo)系下的方向向量再通過旋轉(zhuǎn)矩陣轉(zhuǎn)換到 UE 世界坐標(biāo)。4. 從 ENU 到 UE 世界坐標(biāo)最后的落地4.1 ENU 坐標(biāo)系的定義與軸向ENU 坐標(biāo)系以原點(diǎn)為基準(zhǔn)東方向?yàn)?X 軸北方向?yàn)?Y 軸天方向?yàn)?Z 軸。這個定義和 UE 的世界坐標(biāo)系有一個根本差異UE 的 X 軸是前Y 軸是右Z 軸是上。所以從 ENU 到 UE 的轉(zhuǎn)換不是簡單的軸交換而是涉及一個旋轉(zhuǎn)。具體來說ENU 的東方向?qū)?yīng) UE 的哪個方向取決于原點(diǎn)的經(jīng)緯度。在赤道附近東方向大致對應(yīng) UE 的 Y 軸北方向大致對應(yīng) UE 的 X 軸。但在其他緯度這個對應(yīng)關(guān)系會旋轉(zhuǎn)。Cesium for UE4 內(nèi)部使用一個旋轉(zhuǎn)矩陣來處理這個轉(zhuǎn)換。這個矩陣由原點(diǎn)的經(jīng)度和緯度決定。經(jīng)度決定了東方向相對于 UE 坐標(biāo)系的旋轉(zhuǎn)緯度決定了天方向相對于 UE 坐標(biāo)系的傾斜。如果你需要自己實(shí)現(xiàn)轉(zhuǎn)換可以按照以下步驟先把 ECEF 坐標(biāo)減去原點(diǎn) ECEF 坐標(biāo)得到相對向量然后把這個向量投影到 ENU 三個軸上得到 ENU 坐標(biāo)最后通過旋轉(zhuǎn)矩陣把 ENU 坐標(biāo)轉(zhuǎn)換成 UE 坐標(biāo)。這個過程中單位始終是米不需要額外縮放因?yàn)?UE 默認(rèn)單位也是厘米但 Cesium 的 API 通常會處理好單位轉(zhuǎn)換。我建議在實(shí)際項(xiàng)目中盡量使用 Cesium for UE4 提供的轉(zhuǎn)換函數(shù)而不是自己實(shí)現(xiàn)。因?yàn)樽约簩?shí)現(xiàn)容易忽略一些細(xì)節(jié)比如橢球參數(shù)、單位轉(zhuǎn)換、旋轉(zhuǎn)矩陣的符號等。但理解這些細(xì)節(jié)仍然很重要因?yàn)楫?dāng)轉(zhuǎn)換結(jié)果不符合預(yù)期時你需要知道從哪里入手排查。我通常會在關(guān)鍵位置打印出經(jīng)緯高、ECEF、ENU 和 UE 坐標(biāo)對比每一步的結(jié)果這樣能快速定位問題出在哪一層。4.2 實(shí)際轉(zhuǎn)換中的單位與縮放虛幻引擎的世界坐標(biāo)默認(rèn)單位是厘米而 Cesium 的坐標(biāo)單位是米。這意味著從 Cesium 轉(zhuǎn)換到 UE 時需要乘以 100。這個縮放看起來簡單但容易出錯。比如你在計(jì)算速度或加速度時如果忘記縮放結(jié)果會差兩個數(shù)量級。我在一個飛行軌跡項(xiàng)目中就因?yàn)樗俣葐挝粵]轉(zhuǎn)換飛機(jī)飛得像蝸牛一樣慢。后來檢查代碼發(fā)現(xiàn)速度是從 Cesium 的米每秒直接賦給 UE 的厘米每秒少了 100 倍。除了長度單位角度單位也要注意。UE 的旋轉(zhuǎn)通常用度而 Cesium 的 API 可能用弧度。轉(zhuǎn)換時要注意。另外UE 的旋轉(zhuǎn)順序和 Cesium 的旋轉(zhuǎn)表示也可能不同。Cesium 常用四元數(shù)表示旋轉(zhuǎn)UE 也支持四元數(shù)但兩者的坐標(biāo)系定義不同所以不能直接復(fù)制。我通常會把 Cesium 的四元數(shù)轉(zhuǎn)換成旋轉(zhuǎn)矩陣再根據(jù) ENU 到 UE 的旋轉(zhuǎn)矩陣做復(fù)合最后再轉(zhuǎn)成 UE 的四元數(shù)。這個過程比較繁瑣但能保證正確性。還有一個容易忽略的點(diǎn)UE 的坐標(biāo)系是左手系而 ECEF 和 ENU 通常是右手系。這個手性差異會導(dǎo)致旋轉(zhuǎn)方向相反。比如在 ENU 中逆時針旋轉(zhuǎn)在 UE 中可能變成順時針。處理這個問題的方法是在轉(zhuǎn)換矩陣中引入一個負(fù)號或者調(diào)整旋轉(zhuǎn)順序。我剛開始的時候因?yàn)闆]注意手性導(dǎo)致相機(jī)的朝向總是反的。后來在旋轉(zhuǎn)矩陣?yán)锛恿艘粋€軸翻轉(zhuǎn)問題才解決。這個坑很隱蔽因?yàn)槲恢棉D(zhuǎn)換通常不受手性影響只有旋轉(zhuǎn)才會暴露。4.3 轉(zhuǎn)換函數(shù)的封裝與調(diào)試技巧在實(shí)際項(xiàng)目中我會把坐標(biāo)系轉(zhuǎn)換相關(guān)的邏輯封裝成幾個函數(shù)GeographicToECEF、ECEFToENU、ENUToUnreal以及它們的逆函數(shù)。每個函數(shù)都加上詳細(xì)的注釋說明輸入輸出的單位、坐標(biāo)系定義和參考橢球。這樣團(tuán)隊(duì)里其他人用的時候不容易搞混。我還會寫一個簡單的測試用例用幾個已知點(diǎn)驗(yàn)證轉(zhuǎn)換的正確性。比如取一個經(jīng)緯度手動計(jì)算 ECEF再和函數(shù)結(jié)果對比。這個測試用例在每次修改轉(zhuǎn)換邏輯后都跑一遍能快速發(fā)現(xiàn)回歸問題。調(diào)試坐標(biāo)系問題時可視化是關(guān)鍵。我會在場景中放置一些標(biāo)記點(diǎn)用不同的顏色表示不同坐標(biāo)系的轉(zhuǎn)換結(jié)果。比如紅色表示直接使用經(jīng)緯度轉(zhuǎn)換的結(jié)果綠色表示經(jīng)過 ECEF 轉(zhuǎn)換的結(jié)果藍(lán)色表示最終 UE 坐標(biāo)。如果紅色和綠色重合說明地理到 ECEF 的轉(zhuǎn)換正確如果綠色和藍(lán)色重合說明 ECEF 到 UE 的轉(zhuǎn)換正確。這樣一步步縮小范圍能快速定位問題。另外我會在屏幕上打印出關(guān)鍵數(shù)值比如原點(diǎn)的經(jīng)緯高、當(dāng)前相機(jī)的經(jīng)緯高、某個物體的 UE 坐標(biāo)等。這些數(shù)值在排查時非常有用。還有一個技巧使用 Cesium for UE4 的調(diào)試工具。插件提供了一些調(diào)試選項(xiàng)比如顯示原點(diǎn)位置、顯示坐標(biāo)軸等。打開這些選項(xiàng)能直觀地看到原點(diǎn)在哪里、坐標(biāo)軸朝向如何。我通常會在項(xiàng)目初期就打開這些調(diào)試選項(xiàng)直到確認(rèn)坐標(biāo)系轉(zhuǎn)換沒問題后再關(guān)閉。這樣能避免很多“想當(dāng)然”的錯誤。比如我曾經(jīng)以為原點(diǎn)在場景中心結(jié)果調(diào)試顯示原點(diǎn)在相機(jī)前方幾公里處導(dǎo)致我之前的計(jì)算全錯了。從那以后我養(yǎng)成了先看調(diào)試信息再動手的習(xí)慣。5. 常見坐標(biāo)系轉(zhuǎn)換問題與排查鏈路5.1 模型位置偏移幾百米從數(shù)據(jù)源開始查模型位置偏移是坐標(biāo)系轉(zhuǎn)換中最常見的問題。表現(xiàn)是模型加載后位置和預(yù)期不符可能偏移幾十米到幾公里。排查這類問題我通常從數(shù)據(jù)源開始。先確認(rèn)數(shù)據(jù)源的坐標(biāo)系是什么是 WGS84 還是其他橢球是經(jīng)緯高還是投影坐標(biāo)。如果數(shù)據(jù)源是投影坐標(biāo)比如 UTM需要先轉(zhuǎn)換成經(jīng)緯高再交給 Cesium。這一步經(jīng)常被忽略因?yàn)橛行?shù)據(jù)看起來是經(jīng)緯度但實(shí)際上是投影坐標(biāo)的某種變體。確認(rèn)數(shù)據(jù)源坐標(biāo)系后檢查轉(zhuǎn)換參數(shù)。比如如果數(shù)據(jù)源用的是 CGCS2000雖然和 WGS84 差異很小但在大范圍場景下仍然可能累積出偏差。我遇到過一個案例數(shù)據(jù)源是 CGCS2000直接當(dāng) WGS84 用結(jié)果在場景邊緣出現(xiàn)了幾十米的偏移。后來加上了橢球轉(zhuǎn)換參數(shù)問題才解決。另外如果數(shù)據(jù)源有高程異常也要處理。高程異常是橢球高和海拔高的差異不同地區(qū)數(shù)值不同。如果忽略這個差異垂直方向會有偏差。排查時我會先用一個已知點(diǎn)驗(yàn)證。比如取數(shù)據(jù)源中的一個特征點(diǎn)手動計(jì)算它的 UE 坐標(biāo)然后和實(shí)際加載后的位置對比。如果偏差很大說明轉(zhuǎn)換鏈路有問題。然后逐步檢查每一步經(jīng)緯高轉(zhuǎn) ECEF 是否正確ECEF 轉(zhuǎn) ENU 是否正確ENU 轉(zhuǎn) UE 是否正確。我通常會在每一步打印出坐標(biāo)值和手動計(jì)算的結(jié)果對比。這樣能快速定位是哪一步出了問題。如果每一步都正確但最終結(jié)果還是偏那可能是原點(diǎn)設(shè)置的問題。檢查原點(diǎn)的經(jīng)緯高是否和數(shù)據(jù)源匹配如果不匹配調(diào)整原點(diǎn)。5.2 相機(jī)飛行時抖動原點(diǎn)切換的副作用相機(jī)飛行時抖動尤其是在快速移動或跨越較大范圍時通常是原點(diǎn)切換引起的。原點(diǎn)切換時所有物體的 UE 坐標(biāo)都會重新計(jì)算如果處理不當(dāng)就會出現(xiàn)視覺上的跳變。Cesium for UE4 雖然做了平滑處理但在某些情況下仍然不夠。比如相機(jī)快速移動時原點(diǎn)可能頻繁切換導(dǎo)致物體持續(xù)抖動?;蛘呦鄼C(jī)看向天空時原點(diǎn)選擇不穩(wěn)定導(dǎo)致物體位置漂移。解決這個問題的方法有幾種。一是手動指定原點(diǎn)避免自動切換。如果場景范圍有限手動原點(diǎn)是最穩(wěn)定的方案。二是調(diào)整原點(diǎn)切換的閾值讓切換不那么頻繁。Cesium for UE4 提供了一些參數(shù)來控制原點(diǎn)切換的行為比如切換距離閾值。增大閾值可以減少切換頻率但會犧牲遠(yuǎn)離原點(diǎn)區(qū)域的精度。三是使用相對坐標(biāo)把動態(tài)物體掛在一個相對原點(diǎn)的父節(jié)點(diǎn)下這樣原點(diǎn)切換時相對坐標(biāo)不變物體不會跳變。我通常會把相機(jī)和主要動態(tài)物體放在一個相對原點(diǎn)的坐標(biāo)系下這樣即使原點(diǎn)切換它們的位置也是連續(xù)的。還有一個技巧在原點(diǎn)切換時暫停物理模擬或動畫更新等切換完成后再恢復(fù)。這樣可以避免切換過程中的計(jì)算錯誤。Cesium for UE4 提供了原點(diǎn)變化的事件可以監(jiān)聽這個事件在事件觸發(fā)時暫停和恢復(fù)。我試過這個方法效果不錯但要注意暫停時間不能太長否則會影響用戶體驗(yàn)。通常原點(diǎn)切換很快暫停幾毫秒就夠了。5.3 旋轉(zhuǎn)方向反了手性差異的坑旋轉(zhuǎn)方向反了是坐標(biāo)系轉(zhuǎn)換中比較隱蔽的問題。表現(xiàn)是物體的朝向和預(yù)期相反或者相機(jī)的旋轉(zhuǎn)方向不對。這個問題的根源是 ECEF 和 ENU 通常是右手系而 UE 是左手系。手性差異導(dǎo)致旋轉(zhuǎn)矩陣的行列式為負(fù)從而翻轉(zhuǎn)了旋轉(zhuǎn)方向。解決方法是調(diào)整旋轉(zhuǎn)矩陣或者在轉(zhuǎn)換時對某個軸取反。我遇到這個問題時一開始以為是旋轉(zhuǎn)順序的問題試了各種旋轉(zhuǎn)順序都不對。后來才想到手性差異。具體來說ENU 的東-北-天是右手系UE 的 X-Y-Z 是左手系。如果直接把 ENU 的旋轉(zhuǎn)矩陣賦給 UE旋轉(zhuǎn)方向就會反。正確的做法是在旋轉(zhuǎn)矩陣中引入一個軸翻轉(zhuǎn)比如把 Y 軸取反或者調(diào)整旋轉(zhuǎn)順序。我通常會把 ENU 的旋轉(zhuǎn)矩陣轉(zhuǎn)換成 UE 的旋轉(zhuǎn)矩陣時對 Y 軸取反這樣手性就一致了。除了手性還要注意旋轉(zhuǎn)的參考系。Cesium 的旋轉(zhuǎn)通常是相對于 ECEF 或 ENU 的而 UE 的旋轉(zhuǎn)是相對于世界坐標(biāo)的。所以不能直接把 Cesium 的旋轉(zhuǎn)賦給 UE需要先轉(zhuǎn)換到 UE 的世界坐標(biāo)。我通常會把 Cesium 的四元數(shù)轉(zhuǎn)換成旋轉(zhuǎn)矩陣然后乘以 ENU 到 UE 的旋轉(zhuǎn)矩陣再轉(zhuǎn)成 UE 的四元數(shù)。這個過程雖然繁瑣但能保證旋轉(zhuǎn)正確。如果旋轉(zhuǎn)仍然不對檢查旋轉(zhuǎn)矩陣的乘法順序矩陣乘法不滿足交換律順序錯了結(jié)果就錯了。5.4 高度不對橢球高與海拔高的混淆高度不對是另一個常見問題。表現(xiàn)是模型在地面之上或之下或者地形和模型之間的垂直關(guān)系不對。這個問題的根源通常是橢球高和海拔高的混淆。Cesium for UE4 使用的是橢球高而很多數(shù)據(jù)源提供的是海拔高。兩者之間差了一個大地水準(zhǔn)面差距這個差距在不同地區(qū)可能達(dá)到幾十米。如果忽略這個差異垂直方向就會有偏差。解決方法是把海拔高轉(zhuǎn)換成橢球高。這需要一個大地水準(zhǔn)面模型比如 EGM96。Cesium for UE4 可能沒有內(nèi)置這個轉(zhuǎn)換需要自己處理。我通常會在數(shù)據(jù)預(yù)處理階段把海拔高轉(zhuǎn)換成橢球高然后再交給 Cesium。轉(zhuǎn)換的方法是用大地水準(zhǔn)面模型計(jì)算差距然后加到海拔高上。這個差距是經(jīng)緯度的函數(shù)所以需要查表或使用近似公式。如果精度要求不高可以用一個平均差距值但這樣在不同地區(qū)會有誤差。排查高度問題時我會先確認(rèn)數(shù)據(jù)源的高度類型。如果是海拔高就做轉(zhuǎn)換如果是橢球高就直接用。然后檢查原點(diǎn)的原點(diǎn)高度設(shè)置。原點(diǎn)高度應(yīng)該是橢球高而不是海拔高。如果原點(diǎn)高度設(shè)錯了所有物體的垂直位置都會偏。我通常會把原點(diǎn)高度設(shè)在實(shí)際地形高度附近這樣垂直方向的偏移最小。另外如果場景中有多個數(shù)據(jù)源要確保它們的高度類型一致否則會出現(xiàn)有的模型在地面上有的在地下。6. 把坐標(biāo)系轉(zhuǎn)換用進(jìn)實(shí)際項(xiàng)目幾個經(jīng)驗(yàn)之談6.1 項(xiàng)目初期就統(tǒng)一坐標(biāo)規(guī)范我在多個地理空間項(xiàng)目里最大的體會是項(xiàng)目初期就要統(tǒng)一坐標(biāo)規(guī)范。具體來說確定一個標(biāo)準(zhǔn)的地理坐標(biāo)系通常是 WGS84確定一個標(biāo)準(zhǔn)的高度類型橢球高或海拔高確定一個標(biāo)準(zhǔn)的單位米或厘米然后所有數(shù)據(jù)都往這個標(biāo)準(zhǔn)上靠。這個工作看起來簡單但能省掉后期無數(shù)次的轉(zhuǎn)換和排查。我見過一個項(xiàng)目因?yàn)椴煌瑘F(tuán)隊(duì)用了不同的坐標(biāo)系最后整合時花了大量時間做坐標(biāo)對齊還出現(xiàn)了無法消除的偏差。統(tǒng)一坐標(biāo)規(guī)范還包括確定原點(diǎn)的策略。是使用自動原點(diǎn)還是手動原點(diǎn)原點(diǎn)的經(jīng)緯高怎么定這些都要在項(xiàng)目初期確定。如果項(xiàng)目范圍小手動原點(diǎn)更穩(wěn)定如果范圍大自動原點(diǎn)更靈活。我通常會在項(xiàng)目初期做一個測試場景加載一些已知數(shù)據(jù)觀察原點(diǎn)切換的行為然后決定用哪種策略。這個測試場景不需要很復(fù)雜幾個標(biāo)記點(diǎn)加一個相機(jī)就夠了。另外文檔化也很重要。把坐標(biāo)規(guī)范寫進(jìn)項(xiàng)目文檔包括坐標(biāo)系定義、單位、原點(diǎn)策略、轉(zhuǎn)換函數(shù)的使用方法等。這樣新加入的團(tuán)隊(duì)成員能快速上手不會因?yàn)樽鴺?biāo)系問題浪費(fèi)時間。我通常會在代碼倉庫里放一個CoordinateSystem.md文件詳細(xì)說明這些內(nèi)容。這個文件在項(xiàng)目后期排查問題時也非常有用因?yàn)榭梢钥焖倩仡櫘?dāng)初的決策。6.2 轉(zhuǎn)換函數(shù)的性能優(yōu)化坐標(biāo)系轉(zhuǎn)換在實(shí)時應(yīng)用中可能被頻繁調(diào)用比如每幀更新相機(jī)位置或物體位置。如果轉(zhuǎn)換函數(shù)效率低會影響幀率。Cesium for UE4 的轉(zhuǎn)換函數(shù)已經(jīng)做了優(yōu)化但如果自己實(shí)現(xiàn)要注意避免重復(fù)計(jì)算。比如原點(diǎn)的 ECEF 坐標(biāo)和旋轉(zhuǎn)矩陣可以緩存起來不需要每次轉(zhuǎn)換都重新計(jì)算。我通常會把原點(diǎn)相關(guān)的數(shù)據(jù)緩存在一個結(jié)構(gòu)體里轉(zhuǎn)換時直接使用。另外如果需要對大量點(diǎn)做轉(zhuǎn)換可以考慮批量處理。比如把多個經(jīng)緯度點(diǎn)一次性轉(zhuǎn)換成 UE 坐標(biāo)而不是逐個轉(zhuǎn)換。批量處理可以減少函數(shù)調(diào)用開銷也能利用 SIMD 指令加速。Cesium for UE4 可能沒有提供批量轉(zhuǎn)換的 API但可以自己實(shí)現(xiàn)。我通常會把點(diǎn)集組織成數(shù)組然后在一個循環(huán)里轉(zhuǎn)換同時緩存中間結(jié)果。如果點(diǎn)集很大還可以考慮多線程處理但要注意線程安全。還有一個優(yōu)化點(diǎn)是減少不必要的轉(zhuǎn)換。比如如果物體的位置在短時間內(nèi)不變就不需要每幀重新轉(zhuǎn)換??梢跃彺孓D(zhuǎn)換結(jié)果只在原點(diǎn)變化或物體移動時更新。我通常會給每個需要轉(zhuǎn)換的物體加一個臟標(biāo)記只有標(biāo)記為臟時才重新轉(zhuǎn)換。這樣能顯著減少轉(zhuǎn)換次數(shù)尤其是在場景中有大量靜態(tài)物體時。6.3 與其他系統(tǒng)的坐標(biāo)對接地理空間項(xiàng)目往往需要和其他系統(tǒng)對接比如 GIS 系統(tǒng)、BIM 系統(tǒng)、仿真系統(tǒng)等。這些系統(tǒng)可能使用不同的坐標(biāo)系對接時要做轉(zhuǎn)換。我通常會在對接層做一個坐標(biāo)適配器把外部系統(tǒng)的坐標(biāo)轉(zhuǎn)換成項(xiàng)目內(nèi)部的標(biāo)準(zhǔn)坐標(biāo)。這個適配器要處理坐標(biāo)系差異、單位差異、高度類型差異等。適配器的接口要簡單明了輸入輸出都明確標(biāo)注坐標(biāo)系和單位。對接時最容易出問題的是旋轉(zhuǎn)和朝向。不同系統(tǒng)對旋轉(zhuǎn)的定義可能不同比如有的用歐拉角有的用四元數(shù)有的用旋轉(zhuǎn)矩陣。而且旋轉(zhuǎn)的參考系也可能不同。我通常會把所有旋轉(zhuǎn)統(tǒng)一成四元數(shù)然后根據(jù)參考系做轉(zhuǎn)換。如果外部系統(tǒng)用的是歐拉角要先轉(zhuǎn)成四元數(shù)再轉(zhuǎn)成項(xiàng)目內(nèi)部的表示。這個過程中要注意旋轉(zhuǎn)順序和手性。另外時間同步也很重要。如果外部系統(tǒng)是動態(tài)的比如實(shí)時仿真坐標(biāo)轉(zhuǎn)換要考慮時間戳。不同系統(tǒng)的時鐘可能不同步導(dǎo)致坐標(biāo)在時間上不對齊。我通常會在對接層加一個時間同步機(jī)制確保坐標(biāo)轉(zhuǎn)換基于同一時刻。這個機(jī)制可以簡單到用一個共享的時間戳也可以復(fù)雜到用網(wǎng)絡(luò)時間協(xié)議。具體取決于項(xiàng)目需求。6.4 調(diào)試工具與可視化的重要性最后再強(qiáng)調(diào)一下調(diào)試工具和可視化的重要性。坐標(biāo)系轉(zhuǎn)換的問題往往很隱蔽光看代碼很難發(fā)現(xiàn)。我通常會在場景中放置坐標(biāo)軸標(biāo)記顯示原點(diǎn)的位置和朝向。還會用不同顏色的線連接不同坐標(biāo)系的轉(zhuǎn)換結(jié)果直觀地看到偏差。Cesium for UE4 提供了一些調(diào)試選項(xiàng)比如顯示原點(diǎn)、顯示坐標(biāo)軸這些都要充分利用。另外我會寫一個簡單的坐標(biāo)轉(zhuǎn)換測試工具輸入經(jīng)緯高輸出 ECEF、ENU 和 UE 坐標(biāo)并和手動計(jì)算的結(jié)果對比。這個工具在開發(fā)過程中隨時可用能快速驗(yàn)證轉(zhuǎn)換邏輯。我還會把常見的轉(zhuǎn)換問題整理成檢查清單比如“高度類型是否正確”“原點(diǎn)是否匹配”“手性是否處理”等排查時逐項(xiàng)檢查。這個清單在團(tuán)隊(duì)里共享能提高排查效率??梢暬矫嫖疫€會用一些第三方工具比如把 UE 坐標(biāo)導(dǎo)出成 CSV然后在外部工具里繪制和原始地理數(shù)據(jù)對比。這樣能從宏觀上看到偏差的分布和趨勢。如果偏差是系統(tǒng)性的比如所有點(diǎn)都偏同一個方向那可能是原點(diǎn)或旋轉(zhuǎn)矩陣的問題如果偏差是隨機(jī)的那可能是數(shù)據(jù)源或精度的問題。通過可視化能快速判斷問題的性質(zhì)從而選擇正確的排查方向。