Cesium地形生成:從DEM到terrain瓦片的完整實(shí)踐)
前陣子接了個(gè)活兒在 Cesium 三維地球里展示一塊真實(shí)地形。需求看著不復(fù)雜但有一個(gè)硬條件——地形數(shù)據(jù)不能走公網(wǎng)必須本地生成、本地托底。這就意味著不能靠 Cesium 內(nèi)置的在線(xiàn)地形也不能直接丟到云端平臺(tái)一傳了之。我花了兩天把整條鏈路跑通從 DEM數(shù)字高程模型裸數(shù)據(jù)到切出來(lái)能被 CesiumTerrainProvider 直接消費(fèi)的地形瓦片中間踩了不少坑。這篇就完整記一下我的處理過(guò)程、參數(shù)選擇和避坑方法給后面要自建地形數(shù)據(jù)的同學(xué)一個(gè)可復(fù)制的參考。適合有 Cesium 基礎(chǔ)但沒(méi)碰過(guò)地形生成的人也適合要離線(xiàn)化部署三維地圖的項(xiàng)目組。1. 先想清楚技術(shù)鏈路自建地形數(shù)據(jù)到底在解決什么問(wèn)題1.1 Cesium 地形渲染的本質(zhì)流式 LOD 瓦片很多人第一次用 Cesium 就發(fā)現(xiàn)new Cesium.Viewer(...)之后場(chǎng)景里默認(rèn)帶了一塊橢球表面但是上面沒(méi)有山川起伏。Cesium 的“地形”本質(zhì)不是貼圖而是一張精細(xì)的三角網(wǎng)高度場(chǎng)。它按照四叉樹(shù)結(jié)構(gòu)把全球劃分成密密麻麻的瓦片每個(gè)瓦片又是一個(gè)小網(wǎng)格網(wǎng)格頂點(diǎn)上帶有高程值。瀏覽器拿到這些頂點(diǎn)坐標(biāo)和高程再結(jié)合法線(xiàn)、光照和相機(jī)視距才渲染出能看到的起伏地表。這里最關(guān)鍵的一點(diǎn)是Cesium 的地形瓦片是流式加載的相機(jī)離哪里近哪里就加載更精細(xì)的子瓦片離遠(yuǎn)了就統(tǒng)一合并成粗糙的父瓦片。這種 LOD細(xì)節(jié)層次機(jī)制決定了地形文件的組織方式必須嚴(yán)格遵循固定的目錄結(jié)構(gòu)和元數(shù)據(jù)約定。Cesium 支持的離線(xiàn)地形格式主要有兩種一種叫 Heightmap高度圖一種叫 Quantized-Mesh量化網(wǎng)格后者是當(dāng)前主流后面我會(huì)展開(kāi)說(shuō)。很多人會(huì)把“地形瓦片”和“影像瓦片”混為一談。影像瓦片是一張張 256x256 的 JPEG/PNG 圖片而地形瓦片是包含頂點(diǎn)坐標(biāo)、高程索引、法線(xiàn)信息甚至水紋信息的二進(jìn)制文件。這個(gè)區(qū)別直接決定了自建地形的處理方式和存儲(chǔ)結(jié)構(gòu)千萬(wàn)別用做影像切片的心態(tài)去套。1.2 為什么必須自建地形數(shù)據(jù)如果只是在公網(wǎng)演示直接用默認(rèn)地形當(dāng)然最簡(jiǎn)單。但我遇到的場(chǎng)景里項(xiàng)目必須斷網(wǎng)運(yùn)行地形數(shù)據(jù)不能經(jīng)過(guò)第三方服務(wù)器。即使允許聯(lián)網(wǎng)默認(rèn)地形在部分網(wǎng)絡(luò)環(huán)境的加載速度也相當(dāng)不穩(wěn)定用戶(hù)的真實(shí)體驗(yàn)是“影像都加載出來(lái)了地形還是白板”。還有一類(lèi)需求更直接客戶(hù)手里有一份自己采集的高精度 DEM比如 2 米、5 米的無(wú)人機(jī)航測(cè)數(shù)據(jù)默認(rèn)地形分辨率太低根本沒(méi)法拿來(lái)展現(xiàn)真實(shí)地貌。這時(shí)唯一靠譜的方式就是自己把 DEM 切成 Cesium 需要的地形瓦片再部署到內(nèi)網(wǎng)靜態(tài)服務(wù)器上。自建地形還有另一個(gè)隱性收益可以對(duì)業(yè)務(wù)范圍做裁剪和加密只發(fā)布項(xiàng)目涉及的區(qū)域。比如只切某片山區(qū)范圍數(shù)據(jù)量會(huì)從全球級(jí)縮到十幾萬(wàn)個(gè)瓦片以?xún)?nèi)本地加載性能反而比默認(rèn)全球地形快得多。更重要的是離線(xiàn)環(huán)境下所有地形數(shù)據(jù)都在自己手里版本、精度、更新時(shí)間都可控不會(huì)因?yàn)榈谌椒?wù)變化而影響項(xiàng)目交付。1.3 從 DEM 到 Terrain 瓦片的一條主鏈路整條鏈路捋清楚其實(shí)就四步搞到原始高程數(shù)據(jù)DEM一般是一個(gè) GeoTIFF 文件用 GDAL 做格式、坐標(biāo)系、范圍的預(yù)處理用地形切片工具把 DEM 切成 quantized-mesh 瓦片形成layer.json{z}/{x}/{y}.terrain的目錄結(jié)構(gòu)把瓦片目錄掛到任意靜態(tài)服務(wù)器上在 Cesium 里配置CesiumTerrainProvider指向它。看著不多但每一步都有幾個(gè)容易翻車(chē)的細(xì)節(jié)。下面我把我實(shí)操里最影響結(jié)果的參數(shù)和判斷寫(xiě)清楚避免大家后面走彎路。2. 關(guān)鍵概念與方案選型動(dòng)手之前先避開(kāi)這幾個(gè)坑2.1 第一手材料DEM 數(shù)據(jù)的選擇與檢查地形數(shù)據(jù)生成的地基就是 DEM。DEM 本身是一個(gè)柵格每個(gè)像素記錄一個(gè)地面海拔值。不同來(lái)源的 DEM 分辨率差異很大直接影響最終地形的精細(xì)度。我這次用的是常見(jiàn)的 30 米分辨率開(kāi)源高程模型肉眼看一下山體形態(tài)完全夠用如果項(xiàng)目要展示更精細(xì)的沖溝、田坎、道路斷面就得用 5 米甚至更高精度的航測(cè) DEM。選數(shù)據(jù)源時(shí)我建議先確認(rèn)四件事下面這個(gè)表格就是我的檢查清單數(shù)據(jù)屬性檢查標(biāo)準(zhǔn)對(duì)切片結(jié)果的影響坐標(biāo)系是 WGS84 經(jīng)緯度還是投影坐標(biāo)系Cesium 地形瓦片需要 WGS84 地理坐標(biāo)投影坐標(biāo)必須重投影高程基準(zhǔn)海拔高還是橢球高相差一個(gè)大地水準(zhǔn)面起伏山區(qū)可能差幾十米像元位深16 位整型或 Float328 位只有 256 級(jí)高度山區(qū)會(huì)被量化成臺(tái)階無(wú)效值NoData是否有明確的 NoData 標(biāo)記不處理會(huì)在區(qū)域邊界生成深坑或黑洞我這次的數(shù)據(jù)源是客戶(hù)提供的一塊 GeoTIFF范圍大概覆蓋一片 20km×20km 的山區(qū)。拿到手第一件事就是跑gdalinfo先看它的坐標(biāo)系、像素尺寸、波段類(lèi)型和無(wú)效值。這一步?jīng)]做扎實(shí)后面切出來(lái)全是問(wèn)題。提示如果 DEM 的無(wú)效值沒(méi)有被正確標(biāo)記切片工具會(huì)把無(wú)效像素當(dāng)普通高程參與插值最終在數(shù)據(jù)邊緣產(chǎn)生一個(gè)向下的漏斗形凹坑。這個(gè)坑在切完瓦片后很難從目錄結(jié)構(gòu)上發(fā)現(xiàn)只有加載到 Cesium 里才能看到非常隱蔽。2.2 理解瓦片格式為什么 Quantized-Mesh 是主線(xiàn)Cesium 在早期版本里大量使用 heightmap 地形它本質(zhì)上是每瓦片固定尺寸的高度數(shù)組生成簡(jiǎn)單但有兩個(gè)短板一是頂點(diǎn)位置完全按等間隔排布無(wú)法根據(jù)地形起伏智能細(xì)化網(wǎng)格二是每片瓦片的 LOD 切換全靠客戶(hù)端容易在陡峭區(qū)域露出明顯的鋸齒。Quantized-Mesh 是 Cesium 后來(lái)主推的格式每個(gè)瓦片是一個(gè)真正的不規(guī)則三角網(wǎng)頂點(diǎn)坐標(biāo)經(jīng)過(guò)量化高度和法線(xiàn)信息都打包在.terrain文件里加載時(shí)直接按索引渲染。用起來(lái)最直觀(guān)的差別是quantized-mesh 地形在陡坡和溝壑處線(xiàn)條更貼合真實(shí)地貌同一級(jí)別下文件體積也更小。而且它原生支持requestVertexNormals和requestWaterMask這意味著你可以給地形加真實(shí)的斜坡光照效果還能在水體部分生成水面漣漪。所以我的建議很直接新項(xiàng)目無(wú)腦選 quantized-mesh除非你要兼容很老的 Cesium 版本或者只想快速出個(gè)粗糙預(yù)覽。還有一個(gè)細(xì)節(jié)值得注意Quantized-Mesh 瓦片的頂點(diǎn)數(shù)通常是 65x65而不是影像瓦片那種 256x256。也就是說(shuō)每個(gè)地形瓦片在屏幕上最多渲染 4225 個(gè)網(wǎng)格頂點(diǎn)再配合 LOD 層級(jí)形成一個(gè)兼顧渲染效率和細(xì)節(jié)的三角網(wǎng)。這個(gè) 65 就是切片工具里常見(jiàn)的--tile-size參數(shù)。2.3 工具鏈與坐標(biāo)系動(dòng)手前必須認(rèn)的兩件事生成 quantized-mesh 瓦片常見(jiàn)路線(xiàn)有兩種一種是把 DEM 上傳到云平臺(tái)讓它幫你切好瓦片再配置 token 訪(fǎng)問(wèn)另一種是用開(kāi)源工具在本地切。我這個(gè)項(xiàng)目因?yàn)楸仨氹x線(xiàn)直接走了本地開(kāi)源工具。工具選型時(shí)主要看三點(diǎn)是否支持 GeoTIFF 直接輸入、切出來(lái)的瓦片是否帶layer.json、是否支持指定最大層級(jí)和采樣范圍。大多數(shù)本地切片工具都能滿(mǎn)足關(guān)鍵是要先驗(yàn)證輸出格式是不是 Cesium 的 quantized-mesh而不是通用 3D Tiles。坐標(biāo)系這塊是最容易出錯(cuò)的環(huán)節(jié)。Cesium 的地形瓦片默認(rèn)使用 WGS84 經(jīng)緯度坐標(biāo)而這個(gè)坐標(biāo)系下經(jīng)緯度是角度不是米。如果你的原始 DEM 是 UTM 投影或者高斯投影里面每個(gè)像素的寬高是米必須先用gdalwarp重投影成 EPSG:4326。重投影之后柵格像素不再是規(guī)則的方形米格而是規(guī)則的角度格Cesium 切片的 LOD 才符合預(yù)期。3. 實(shí)操記錄從 DEM 到 Cesium 地形的完整過(guò)程3.1 動(dòng)手第一步gdalinfo 檢查數(shù)據(jù)底子我拿到 GeoTIFF 后先跑了一條命令gdalinfo input_dem.tif看幾個(gè)關(guān)鍵字段Size is表示寬高像元數(shù)Coordinate System is顯示坐標(biāo)系Pixel Size表示地面分辨率Band 1下的Type表示像元類(lèi)型NoData Value表示無(wú)效值。我這次的數(shù)據(jù)是 WGS84 經(jīng)緯度坐標(biāo)30 米分辨率16 位整型。如果你看到的是 UTM 投影或者沒(méi)有 NoData 值后續(xù)就必須先處理。另一個(gè)要確認(rèn)的是數(shù)據(jù)范圍和業(yè)務(wù)范圍是否匹配。我拿到數(shù)據(jù)時(shí)范圍比業(yè)務(wù)區(qū)大了一圈而且周邊是無(wú)效的黑色區(qū)域。Cesium 地形切片工具遇到邊緣無(wú)效值可能生成異常地形所以動(dòng)手前我先把 DEM 裁剪到實(shí)際需要的地塊。這一步其實(shí)還減少了后面瓦片的數(shù)量屬于一舉兩得。如果有人給你的是多塊碎 DEM千萬(wàn)別直接切片。多塊 DEM 之間可能存在重疊、縫隙和高程不一致切出來(lái)的瓦片接縫處會(huì)非常難看。我的習(xí)慣是先看每塊數(shù)據(jù)的gdalinfo確認(rèn)四至范圍和坐標(biāo)系完全一致再?zèng)Q定要不要合并。3.2 預(yù)處理重投影、合并與裁剪如果 DEM 坐標(biāo)系不是 WGS84先重投影。我用了雙線(xiàn)性插值因?yàn)榈匦问沁B續(xù)表面這種插值不會(huì)像最近鄰那樣產(chǎn)生生硬的臺(tái)階又比三次卷積速度快gdalwarp -t_srs EPSG:4326 -r bilinear -of GTiff input_dem.tif wgs84_dem.tif如果業(yè)務(wù)區(qū)跨多塊 DEM先用gdal_merge.py合并python3 gdal_merge.py -o merged_dem.tif -init 0 -n 0 tile1.tif tile2.tif tile3.tif參數(shù)-n 0表示把 0 當(dāng)作 NoData這樣可以排除無(wú)效區(qū)-init 0指定背景值。合并完再按目標(biāo)范圍裁剪gdalwarp -te 12.00 56.00 12.20 56.20 -tr 0.00027777778 0.00027777778 -r bilinear -of GTiff merged_dem.tif clip_dem.tif-te后面依次是最小經(jīng)度、最小緯度、最大經(jīng)度、最大緯度-tr表示目標(biāo)分辨率30 米換算成度大概是1/3600也就是0.0002777778。這樣輸出柵格的每像素就是規(guī)整的 30 米采樣后面切片工具計(jì)算層級(jí)時(shí)會(huì)很清爽。預(yù)處理做完再用gdalinfo clip_dem.tif看一遍確認(rèn)范圍、分辨率、NoData 都符合預(yù)期。我踩過(guò)的坑是裁剪之后忘記確認(rèn) NoData 值切出來(lái)的瓦片在區(qū)域邊界出現(xiàn)一大塊凹陷的黑洞。后來(lái)我在切片參數(shù)里顯式聲明 NoData 值才解決。3.3 切片生成 quantized-mesh 瓦片預(yù)處理完成后進(jìn)入核心環(huán)節(jié)。我這里用的工具命令大致如下terrain-tiler \ --input clip_dem.tif \ --output ./cesium_terrain \ --max-level 15 \ --format cesium \ --tile-size 65 \ --no-data -32768不同工具的參數(shù)名有差異但核心邏輯一致讀入 GeoTIFF從 level 0 開(kāi)始逐級(jí)切分每個(gè)瓦片固定為 65x65 個(gè)頂點(diǎn)生成layer.json和一堆.terrain文件。--max-level 15并不是隨便拍的我估算了這個(gè)級(jí)別下的瓦片分辨率已經(jīng)接近原始 DEM 的分辨率再往上切不會(huì)產(chǎn)生更多真實(shí)細(xì)節(jié)只會(huì)白白增加瓦片數(shù)量。最大層級(jí)可以按這個(gè)思路估算Cesium 地形瓦片第z層全球大約有2^z個(gè)瓦片一塊瓦片對(duì)應(yīng)地面的尺寸大約是地球周長(zhǎng)除以2^z。當(dāng)這個(gè)尺寸接近原始 DEM 的兩倍像素大小時(shí)說(shuō)明這個(gè)層級(jí)已經(jīng)能還原出 DEM 的細(xì)節(jié)再往下走就屬于重復(fù)勞動(dòng)。30 米分辨率 DEM 我通常給max-level 14到15如果是航測(cè)的 1 米分辨率max-level 17起也正常。有些切片工具會(huì)默認(rèn)給瓦片加 skirt裙邊也就是在瓦片邊緣向下延伸一小圈三角網(wǎng)用來(lái)避免不同層級(jí)瓦片拼接時(shí)出現(xiàn)裂縫。如果你的工具默認(rèn)沒(méi)有記得手動(dòng)開(kāi)啟這是保證 LOD 切換平滑的關(guān)鍵。生成完檢查一下目錄find ./cesium_terrain -type f | wc -l我這次切出來(lái)大概 8 萬(wàn)個(gè).terrain文件和一個(gè)layer.json。文件數(shù)量有點(diǎn)多但不影響靜態(tài)部署Nginx 處理靜態(tài)小文件的能力足夠。3.4 接入環(huán)節(jié)本地起服務(wù)并配置 CesiumTerrainProvider瓦片目錄本質(zhì)上只要求靜態(tài)文件服務(wù)不需要后端處理。我在本地直接用 Python 起了一個(gè)靜態(tài)服務(wù)cd cesium_terrain python3 -m http.server 8080然后寫(xiě)一個(gè)最簡(jiǎn)單的 Cesium 頁(yè)面。如果你用的是老版本 API是這樣配置的const viewer new Cesium.Viewer(cesiumContainer, { terrainProvider: new Cesium.CesiumTerrainProvider({ url: http://localhost:8080, requestVertexNormals: true, requestWaterMask: false }) });如果你用的是新版 Cesium更推薦異步方式const terrain await Cesium.Terrain.fromUrl(http://localhost:8080, { requestVertexNormals: true }); const viewer new Cesium.Viewer(cesiumContainer, { terrain });把相機(jī)飛到業(yè)務(wù)區(qū)驗(yàn)證地形是否生效viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(12.10, 56.10, 12000), orientation: { heading: 0, pitch: Cesium.Math.toRadians(-45), roll: 0 } });如果頁(yè)面加載后能看到明顯的山體起伏和坡度陰影說(shuō)明瓦片格式和通信鏈路都通了。這里有個(gè)常見(jiàn)誤區(qū)URL 不要拼到某個(gè)具體瓦片文件只要指到包含layer.json的根目錄即可。CesiumTerrainProvider會(huì)自己請(qǐng)求layer.json然后按規(guī)則去請(qǐng)求{z}/{x}/{y}.terrain。3.5 部署后的性能與顯示調(diào)優(yōu)本地驗(yàn)證通過(guò)后我把它遷到了內(nèi)網(wǎng)一臺(tái) Nginx 靜態(tài)目錄上。Nginx 配置很簡(jiǎn)單server { listen 8080; location /terrain/ { alias /data/cesium_terrain/; add_header Access-Control-Allow-Origin *; expires 1h; } }Access-Control-Allow-Origin這一行很多人會(huì)漏掉。如果你的 Cesium 頁(yè)面和地形瓦片在不同端口或不同域名瀏覽器會(huì)因?yàn)榭缬蛑苯訑r掉地形請(qǐng)求表現(xiàn)就是地形死活不顯示但 F12 里能看到一堆 403。我這次本地http.server默認(rèn)帶了 CORS 頭所以本地沒(méi)踩到部署到 Nginx 時(shí)漏了排查了一會(huì)兒才定位。性能調(diào)優(yōu)重點(diǎn)看三個(gè)參數(shù)maximumScreenSpaceErrorCesium 認(rèn)為屏幕空間誤差小于該值時(shí)不再細(xì)分瓦片默認(rèn) 2。如果地形加載慢調(diào)高到 4 到 8會(huì)明顯減少細(xì)膩瓦片的請(qǐng)求量。maximumLevel設(shè)置瓦片服務(wù)的最大層級(jí)防止客戶(hù)端請(qǐng)求不存在的層級(jí)。enableLighting開(kāi)啟后地形會(huì)根據(jù)法線(xiàn)產(chǎn)生光影效果視覺(jué)立體感強(qiáng)但對(duì)性能有一點(diǎn)消耗。我最終把maximumScreenSpaceError設(shè)成了 4配合requestVertexNormals開(kāi)啟光照觀(guān)感不錯(cuò)加載速度也比默認(rèn)快了一截。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 地形不顯示從 403 到 provider 初始化失敗最典型的癥狀頁(yè)面能顯示地球但地形區(qū)域要么是平的一大塊要么黑乎乎一片。優(yōu)先排查順序如下跨域問(wèn)題看瀏覽器控制臺(tái)有沒(méi)有 CORS 報(bào)錯(cuò)。有的話(huà)先在后端加響應(yīng)頭。URL 配錯(cuò)確認(rèn)CesiumTerrainProvider的 URL 指向包含layer.json的目錄而不是某個(gè)具體的瓦片路徑。layer.json 缺失或格式錯(cuò)誤切片工具沒(méi)生成或者手動(dòng)改動(dòng)過(guò)內(nèi)部字段Cesium 會(huì)直接拋出 Provider 初始化失敗。瓦片請(qǐng)求 404如果請(qǐng)求的級(jí)別超出了max-level或maximumLevel說(shuō)明最大層級(jí)配置和實(shí)際生成層級(jí)對(duì)不上。我遇到過(guò)一次詭異情況切完瓦片后把目錄從服務(wù)器 A 拷貝到服務(wù)器 B 時(shí)layer.json和.terrain文件同級(jí)目錄變成了layer.json加上一個(gè)子文件夾串。原因是壓縮包解壓時(shí)多了一層目錄。后來(lái)我寫(xiě)了個(gè)小腳本在目錄啟動(dòng)時(shí)檢查路徑結(jié)構(gòu)發(fā)現(xiàn)問(wèn)題立刻報(bào)錯(cuò)。下面是一張速查表基本覆蓋我遇到過(guò)的地形不顯示問(wèn)題現(xiàn)象可能原因處理建議網(wǎng)絡(luò)請(qǐng)求報(bào) 403跨域頭缺失后端加Access-Control-Allow-OriginProvider 初始化失敗layer.json非法或 URL 未指向根目錄重新生成核對(duì) URL瓦片 404最大層級(jí)配置不一致同步max-level與maximumLevel地球變白或黑屏相機(jī)位置未設(shè)到瓦片范圍飛到業(yè)務(wù)區(qū)中心或設(shè)置imageryProvider4.2 高程數(shù)值不對(duì)坐標(biāo)系、高程基準(zhǔn)與無(wú)效值這個(gè)坑最隱藏。看現(xiàn)象是山體基本變形整體被壓扁或者拔高甚至出現(xiàn)一片負(fù)數(shù)深坑。通常是兩類(lèi)原因高程基準(zhǔn)不一致或者 NoData 值被當(dāng)成了真實(shí)高程。Cesium 的地形高度是相對(duì) WGS84 橢球面的很多 DEM 的高程是相對(duì)平均海平面的。兩者之間差一個(gè)大地水準(zhǔn)面起伏在山區(qū)可以達(dá)到十幾米甚至幾十米。這在宏觀(guān)山體上視覺(jué)看不出太大差別但你要是做水平面交匯核算就會(huì)發(fā)現(xiàn)水庫(kù)、道路的高程對(duì)不上。解決辦法是拿到數(shù)據(jù)時(shí)先問(wèn)清楚高程基準(zhǔn)如果是海拔高處理層加一個(gè) geoid 改正 DEM或者在 Cesium 端把所有地形高度做統(tǒng)一平移但后者不夠精確。NoData 值的問(wèn)題更隱蔽。如果 DEM 在岸邊和河谷區(qū)域是無(wú)效值 0切片工具可能把 0 當(dāng)作海平面高度于是原本應(yīng)是斷面的地方生成一個(gè)深坑。檢查辦法是在預(yù)處理后用gdalinfo -hist看高程直方圖確認(rèn)無(wú)效值占比切片參數(shù)里顯式聲明 NoData就能讓工具跳過(guò)無(wú)效像素。4.3 瓦片接縫與 LOD 跳變skirt 和數(shù)據(jù)邊界的雙重排查相鄰兩個(gè)不同層級(jí)的瓦片渲染時(shí)如果邊緣頂點(diǎn)對(duì)不齊會(huì)出現(xiàn)一條細(xì)小的白色或者地形斷裂線(xiàn)。數(shù)字孿生項(xiàng)目里這種裂縫特別扎眼。Cesium 對(duì) quantized-mesh 的邊緣頂點(diǎn)有裙邊機(jī)制在每個(gè)瓦片底部向下多延伸一小圈三角這樣不同層級(jí)瓦片拼合時(shí)高度差異會(huì)被裙邊遮擋住。如果裂縫很?chē)?yán)重先看工具生成參數(shù)里有沒(méi)有啟用 skirt。有的工具參數(shù)叫--skirt-height一般給 1 到 5 米即可太大反而會(huì)造成山體邊緣懸空。另外還要檢查原始 DEM 是不是瓦片接縫處存在拼接斷層這種是數(shù)據(jù)本身的問(wèn)題skirt 救不回來(lái)要用gdal_merge時(shí)做邊界羽化或者重新插值。還有一個(gè)容易被忽略的點(diǎn)瓦片層級(jí)的跳變不能跨級(jí)。Cesium 要求父子層級(jí)連續(xù)加載如果客戶(hù)端從 level 10 直接跳到 level 12中間 level 11 的數(shù)據(jù)缺失就會(huì)看到“閃跳”或者一片空白。檢查切片目錄里是否有完整的跨層級(jí)索引必要時(shí)重新生成。4.4 瓦片加載性能問(wèn)題緩存、SSE 與靜態(tài)服務(wù)器優(yōu)化地形瓦片動(dòng)輒幾萬(wàn)個(gè)瀏覽器不可能全部加載進(jìn)來(lái)。Cesium 本身有 LRU 緩存但默認(rèn)值可能不適合你的場(chǎng)景。我能直接落地的小技巧限制瓦片緩存數(shù)量如果出現(xiàn)紋理內(nèi)存不足的報(bào)錯(cuò)可以調(diào)整viewer.scene.context.cacheBytes或者按機(jī)器內(nèi)存反向調(diào)優(yōu)。降低屏幕空間誤差不是越低越好默認(rèn) 2 已經(jīng)不錯(cuò)調(diào)成 1 會(huì)瘋狂請(qǐng)求瓦片頁(yè)面卡成幻燈片。預(yù)置相機(jī)位置把相機(jī)初始視角設(shè)在業(yè)務(wù)區(qū)中心而不是默認(rèn)全球視角避免前期加載大量無(wú)關(guān)瓦片。關(guān)閉水紋、法線(xiàn)插件如果場(chǎng)景不需要水體效果requestWaterMask: false能省掉相當(dāng)一部分?jǐn)?shù)據(jù)解析和 GPU 開(kāi)銷(xiāo)。還有一點(diǎn)部署環(huán)境是虛擬機(jī)或低配服務(wù)器時(shí)瓦片讀取路徑的磁盤(pán) IO 很容易到瓶頸。建議把瓦片目錄放到 SSD 上并用 Nginxexpires開(kāi)啟瀏覽器緩存。實(shí)測(cè)下來(lái)瓦片請(qǐng)求數(shù)量可以減少 60% 以上加載時(shí)間肉眼可見(jiàn)縮短。5. 地形數(shù)據(jù)生成后的長(zhǎng)期維護(hù)經(jīng)驗(yàn)5.1 數(shù)據(jù)更新不改整體只刷局部自建瓦片不是一錘子買(mǎi)賣(mài)。如果業(yè)務(wù)區(qū) DEM 后續(xù)有更新比如房屋拆除、填方造地、道路改線(xiàn)你就得重新生成對(duì)應(yīng)區(qū)域的瓦片。這個(gè)時(shí)候全量重切不現(xiàn)實(shí)我的建議是保持原始 DEM 預(yù)處理階段的數(shù)據(jù)組織不變只在業(yè)務(wù)區(qū)新增或替換的小塊 GeoTIFF 上重新執(zhí)行同樣的預(yù)處理和切片命令然后把新瓦片覆蓋回原目錄。有一點(diǎn)要提醒覆蓋前先備份layer.json并確認(rèn)新老瓦片的層級(jí)范圍和坐標(biāo)系完全一致。我遇到過(guò)一次新瓦片是投影坐標(biāo)系老瓦片是 WGS84兩者在業(yè)務(wù)區(qū)邊緣直接錯(cuò)位了兩三百米。后來(lái)我養(yǎng)成了每次重新切片之后先用 Cesium 飛到業(yè)務(wù)區(qū)邊緣看一圈的習(xí)慣雖然土但非??煽?。5.2 視覺(jué)與功能擴(kuò)展地形數(shù)據(jù)的下一步地形數(shù)據(jù)生成成功后視覺(jué)進(jìn)一步升級(jí)的方向主要有這些疊加影像圖真實(shí)地表色調(diào)加真實(shí)地形起伏效果直接提升一個(gè)檔次。開(kāi)啟地形光照配置requestVertexNormals后再加上viewer.scene.globe.enableLighting true山體陰影會(huì)隨相機(jī)視角變化比扁平顏色真實(shí)得多。加水體遮罩如果業(yè)務(wù)區(qū)有水庫(kù)、湖泊且 DEM 高程準(zhǔn)確設(shè)置requestWaterMask: true后可以看到動(dòng)態(tài)水面效果。動(dòng)態(tài)地形分析這類(lèi)能力適合科研類(lèi)項(xiàng)目比如模擬洪水淹沒(méi)線(xiàn)、填方挖方分析普通展示項(xiàng)目用不到知道有這層能力即可。給后面要做類(lèi)似項(xiàng)目的同學(xué)一個(gè)最終建議先花十分鐘gdalinfo看清原始 DEM 的坐標(biāo)系、位深、無(wú)效值再?zèng)Q定后面怎么切切片參數(shù)里max-level寧可低一點(diǎn)也別因?yàn)楦叨a(chǎn)生海量無(wú)用的細(xì)碎瓦片加載到 Cesium 之后先用maximumScreenSpaceError 4做性能驗(yàn)證再?zèng)Q定要不要開(kāi)更高精度。我個(gè)人最終把整套流程固化成了一個(gè)腳本輸入原始 DEM 區(qū)域范圍自動(dòng)完成重投影、裁剪、切片、靜態(tài)部署最后跑一個(gè) curl 模擬 Cesium 請(qǐng)求layer.json和任意一層瓦片確認(rèn) HTTP 返回正常。這個(gè)腳本在之后幾次數(shù)據(jù)更新中幫我省了大量重復(fù)勞動(dòng)。如果你也在做 Cesium 離線(xiàn)項(xiàng)目建議盡早建立起自己的“預(yù)處理 切片 校驗(yàn)”閉環(huán)這比每次手動(dòng)操作要穩(wěn)得多。