资讯详情

Cesium三维场景与视频融合实战:从地形瓦片到监控贴地

📅 2026/10/17 14:30:57 | 华诺云谱 👁 阅读
Cesium三维场景与视频融合实战:从地形瓦片到监控贴地
简介Cesium三维GIS场景搭建与视频融合资源包面向WebGIS开发者和三维可视化研究人员针对传统二维GIS难以承载三维位置信息、空间语义表达等需求提供了基于Cesium的完整工程实现覆盖三维地球构建、场景漫游与视频融合等典型应用既适合具备前端基础者入门也适合工程师快速搭建演示系统。压缩包共2000个文件大小54.64MB其中507个js脚本与486个html页面组成前端交互主体涵盖Cesium初始化、相机控制、图层加载等核心逻辑451张png图片和185个gif动画提供界面与贴图素材328个java文件协助数据转换或后端接口演示10个b3dm文件为可直接使用的三维瓦片模型素材类型齐全便于按功能模块检索。已有986人学习下载。资源内含三维场景搭建的基础架构、模型加载与场景视频融合的关键模块并附有可运行的示例页面和b3dm模型能帮助从零搭建可用的Cesium可视化环境。对于进阶开发者前端组件封装与数据逻辑实现亦具有复用价值相关经验可迁移至数字城市、电网三维展示等项目有效缩短调研与开发周期。1. 三维GIS场景和视频融合Cesium项目里最难的不是地球是那一路监控怎么“贴”进去做三维GIS看板把地球转起来很容易调地形瓦片也不难真正让人翻车的往往是把一路真实监控视频“融合”进三维世界。它不是悬浮窗不是弹个播放器而是要让视频画面贴在地面或楼宇表面跟着相机视角走近看清晰、远看不飞。这个源码包解决的正是这件事基于Cesium做一套完整的3D场景搭建再把多路场景视频融合做成一整套可调度的方案。我把它拆开看了一遍地形、影像、3D Tiles白模、视频贴合、视角切换都串在了一条主线上适合想快速搭智慧园区或指挥调度三维底座的开发者也适合想知道Cesium视频纹理到底怎么玩的人。2. 技术选型与工程结构Cesium项目的地基先打明白2.1 为什么选Cesium而不是Leaflet或MapboxGL拿到这个项目的第一反应是看选型。市面上的地图框架不少但这个项目必须选Cesium原因是它的底层定位就是“三维地球”不是“带点三维效果的二维地图”。Leaflet和MapboxGL擅长的是二维瓦片叠加、矢量图层管理做做轨迹回放和点标记很顺手但真到了地表起伏、建筑白模、相机环绕这种场景它们要么依赖插件要么渲染能力受限。Cesium的Scene本身就是一套GPU驱动的三维渲染器3D Tiles规范也是它带起来的无人机倾斜摄影、BIM模型、园区精模这些都是用3D Tiles协议喂给它的。从项目正文能看到的定位来看这套源码的核心诉求是三维GIS场景搭建加视频融合那么Cesium几乎是唯一不需要绕路的选择。它把地球、大气、地形、影像、矢量、模型统一在一套坐标系和相机模型里视频融合只需要把它看成一种特殊纹理往Primitive或Entity上一贴就完事。对比项CesiumLeaflet / MapboxGL三维地形与倾斜摄影原生支持3D Tiles基本依赖插件能力受限视频纹理融合Material直接支持video元素没有原生方案需要自绘大场景性能GPU批量渲染适合城市级主要面向二维交互学习曲线偏陡但上限高上手快但不适合本项目2.2 源码包目录结构拆解这个压缩包解压后的工程是一个典型的Vite Vue3 Cesium项目。目录拆开看核心代码都收敛在src/core里外层是Vue组件数据资源放在public/data下这种分层很清晰接手的人不用到处找逻辑。project/ ├─ index.html ├─ package.json ├─ vite.config.js ├─ src/ │ ├─ main.js │ ├─ App.vue │ ├─ views/ │ │ ├─ CityScene.vue # 三维场景主视图 │ │ └─ VideoWall.vue # 视频融合控制面板 │ ├─ components/ │ │ ├─ SceneViewer.vue # Viewer组件封装 │ │ └─ VideoPanel.vue # 视频切换卡片 │ ├─ core/ │ │ ├─ cesium/ │ │ │ ├─ viewer.js # Viewer初始化 │ │ │ ├─ terrain.js # 地形加载 │ │ │ ├─ imagery.js # 影像加载 │ │ │ ├─ tileset.js # 3D Tiles加载与样式 │ │ │ └─ camera.js # 相机预设视角 │ │ └─ video/ │ │ ├─ videoFusion.js # 视频纹理贴合核心 │ │ ├─ videoManager.js # 多路视频调度 │ │ └─ cameraConfig.js # 点位与视频映射配置 │ └─ assets/ ├─ public/ │ ├─ data/ │ │ ├─ imagery/ # 离线影像瓦片 │ │ ├─ terrain/ # 离线地形 │ │ └─ 3dtiles/ # 建筑白模 │ └─ videos/ # 演示视频资源core/cesium负责“场景能不能站得住”core/video负责“视频能不能贴合上去”两者通过配置里的经纬度串起来。我第一次接手这种项目的时候总喜欢把所有代码堆在一个Vue文件里后来发现一旦视频路数多起来维护成本直接爆炸。这个目录结构是典型的工程化写法值得直接抄。2.3 Viewer初始化默认组件哪些该关掉Cesium的Viewer构造参数非常多默认会带时间轴、动画控件、地理编码器、Home按钮这些一堆东西。做业务系统这些基本全是干扰。我见过不少新人拿着默认Viewer就开干结果界面上全是密密麻麻的控件。import * as Cesium from cesium; export function createViewer(containerId) { const viewer new Cesium.Viewer(containerId, { animation: false, timeline: false, geocoder: false, homeButton: false, sceneModePicker: false, baseLayerPicker: false, navigationHelpButton: false, fullscreenButton: false, infoBox: false, selectionIndicator: false, shouldAnimate: true }); // 地形遮挡关系开启后山体才会挡住背后的建筑 viewer.scene.globe.depthTestAgainstTerrain true; // 光照让建筑白模有明暗面立体感更强 viewer.scene.globe.enableLighting true; // 关闭FXAA抗锯齿场景更锐利视频贴合更真实 viewer.scene.postProcessStages.fxaa.enabled false; return viewer; }里面几个参数值得单独说。depthTestAgainstTerrain必须为true否则地形没有深度感相机转到山背后依然能看到山后面的建筑视频贴地的时候也会失去被地形遮挡的效果。enableLighting打开后Cesium会按太阳方位给地球和模型打光白模看起来不再像一块平板。Fxaa在视频融合场景下建议关闭会让画面显得发糊视频纹理的边缘也不干净。3. 场景搭建核心流程地形、影像、3D Tiles的加载链路3.1 影像加载本地瓦片优先在线源兜底三维场景第一步永远是影像底图。很多Cesium demo直接写死一个在线ArcGIS或天地图地址但真实项目里网络环境不确定所以这份源码把影像瓦片放在了public/data/imagery用UrlTemplateImageryProvider走本地路径。export function addImagery(viewer, options) { const provider new Cesium.UrlTemplateImageryProvider({ // 本地瓦片服务地址{z}/{x}/{y}是标准切片规则 url: options.url || /data/imagery/{z}/{x}/{y}.png, minimumLevel: 0, maximumLevel: options.maxLevel || 18, tilingScheme: new Cesium.WebMercatorTilingScheme() }); // 第一个参数0代表插入到最底层不会被其他影像盖住 const layer viewer.imageryLayers.addImageryProvider(provider, 0); // 本地瓦片挂了自动切到在线源 provider.errorEvent.addEventListener(() { const online new Cesium.OpenStreetMapImageryProvider({ url: https://tile.openstreetmap.org/ }); viewer.imageryLayers.addImageryProvider(online, 0); }); return layer; }UrlTemplateImageryProvider是Cesium里最常用的影像Provider适合接本地瓦片、自建TMS服务、或者任何遵循z/x/y规则的瓦片服务。tilingScheme这个参数容易被忽略常见瓦片服务都走WebMercator如果这里写错会看到瓦片位置对不上地球。3.2 地形加载让球面真正“鼓起来”没有地形的Cesium只是一个光滑椭球倾斜摄影或者建筑白模放上去会悬空或穿地。地形数据必须是Cesium支持的terrain格式Cesium官方在线地形服务需要token所以这份源码选择了离线地形。export async function addTerrain(viewer) { const terrain await Cesium.CesiumTerrainProvider.fromUrl( /data/terrain, { requestVertexNormals: true, requestWaterMask: false } ); viewer.terrainProvider terrain; }requestVertexNormals开启后会计算地形顶点法线配合光照能让山体有起伏阴影否则地形像一块块硬边拼起来的。requestWaterMask是水面波纹效果对园区场景没有必要开着反而增加顶点数据量。这里的地形加载用了fromUrl的Promise写法Cesium 1.9x之后推荐这种异步方式老式的new CesiumTerrainProvider({ url })回调写法已经过时了。3.3 3D Tiles建筑白模加载与样式定制建筑白模是整个场景的主角。这份源码里的建筑数据是3D Tiles格式加载方式用的是Cesium3DTileset.fromUrl这个API是Cesium加载倾斜摄影、BIM、白模的唯一标准入口。export async function addTileset(viewer, url) { const tileset await Cesium.Cesium3DTileset.fromUrl(url, { // 屏幕空间误差控制LOD切换的敏感度 maximumScreenSpaceError: 16, // 显存上限512MB超出后自动甩掉远处的瓦片 maximumMemoryUsage: 512, // 开启动态屏幕空间误差相机移动时快速加载 dynamicScreenSpaceError: true, // 瓦片请求时移动裁剪减少无效请求 cullRequestsWhileMoving: true }); viewer.scene.primitives.add(tileset); // 按建筑高度做渐变色样式 tileset.style new Cesium.Cesium3DTileStyle({ color: { conditions: [ [${height} 100, color(#ff8c00, 0.95)], [${height} 50, color(#40a8ff, 0.95)], [true, color(#bbbbbb, 0.9)] ] } }); return tileset; }maximumScreenSpaceError是性能最敏感的参数。它表示瓦片在屏幕上渲染的误差阈值数值越大越早加载低精度的父级瓦片模型加载快但细节差数值越小模型越精细但瓦片请求数量和显存占用会成倍上升。做视频融合场景时我一般建议设16因为注意力在视频上建筑白模只是背景。cullRequestsWhileMoving开启后相机转动过程中会砍掉来不及加载的请求避免相机还没停就疯狂拉瓦片。3.4 相机视角预设把业务视角固化下来场景加载完接下来是视角。这份源码在camera.js里预设了全局总览、重点楼宇、视频点位三组视角运行时通过一个函数飞过去。export function flyToView(viewer, config) { viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees( config.lon, config.lat, config.height ), orientation: { heading: Cesium.Math.toRadians(config.heading), pitch: Cesium.Math.toRadians(config.pitch), roll: Cesium.Math.toRadians(config.roll) }, duration: config.duration || 2.5 }); }destination里的height不是离地高度是相机位置的海拔高度单位米。pitch为负数表示俯视一般业务视角设-30到-45度比较自然。这个函数后期做点位轮巡、告警联动跳转都能复用属于整个项目里被调用最频繁的工具函数。4. 视频融合实战把监控画面贴进三维世界的完整方案4.1 视频融合原理本质是纹理映射视频融合听起来玄学拆开看本质就是Cesium Material体系里的纹理。Cesium的Material支持把HTML的video元素直接赋给material的image属性Cesium会拿每一帧视频画面当作纹理采样到对应的几何体表面。这意味着视频不是“悬浮窗”而是像贴纸一样贴在矩形面、墙体或者模型表面。相机拉近时画面跟着放大相机转过去之后这个面依然附着在场景对应位置符合物理直觉。这也是为什么视频融合能给人一种“监控画面就在现场”的感觉。4.2 方案一Entity矩形贴地最快速的落地姿势最早在项目里落地视频融合用的方式就是Entity加Rectangle把视频画面贴在地面上。适合园区广场、十字路口这种平面监控点位。export async function addGroundVideo(viewer, config) { const video document.createElement(video); video.src config.url; video.loop true; video.muted true; // 必须muted否则浏览器不允许自动播放 video.playsInline true; // iOS兼容 video.crossOrigin anonymous; await video.play(); const rectEntity viewer.entities.add({ id: config.id, rectangle: { coordinates: Cesium.Rectangle.fromDegrees( config.west, config.south, config.east, config.north ), // height抬高0.2米避免和地形深度冲突 height: 0.2, material: video, classificationType: Cesium.ClassificationType.BOTH } }); return { video, rectEntity }; }代码里的关键点都在注释里了。height: 0.2是反复踩坑踩出来的如果设成0会和地表完全共面深度缓冲区直接打架画面会像水面波纹一样疯狂闪烁。classificationType决定视频矩形和地形、模型的遮挡关系如何处理BOTH表示既贴合地形也贴合模型。4.3 方案二Wall贴墙楼宇立面大屏的解法园区场景里更常见的是把视频贴到建筑外墙模拟一块楼面大屏或者对着出入口的摄像头画面。地面矩形不吃这套因为视频不在地面而在垂直面上。这时用Wall实体配置一组多边形顶点再给一个高度范围。export function addWallVideo(viewer, config) { const positions config.positions.map(p Cesium.Cartesian3.fromDegrees(p.lon, p.lat) ); const wallEntity viewer.entities.add({ id: config.id, wall: { positions, // 墙面底部的海拔高度 minimumHeight: 0, // 墙面顶部海拔高度两者差就是画面高度 maximumHeight: 18, material: video } }); return wallEntity; }使用Wall要注意顶点的绕序。Cesium渲染Wall时默认只渲染正面顶点必须保持逆时针方向否则画面会从另一侧看得到、正面反而看不见表现就是视频墙“透明了”。另外这个贴法有个好处就是把视频安在一个独立的垂直面上不会像地面矩形那样被周围建筑遮挡得找不到。我一般做园区出入口监控融合时都是方案二打底。4.4 方案三多路视频融合的统一调度项目里的视频不可能只有一路于是就得有个管理器。videoManager.js做的事情很简单统一的添加、播放、暂停、移除并保证同一时间只有可视范围内的视频在播放。class VideoFusionManager { constructor(viewer) { this.viewer viewer; this.sources new Map(); } async add(config) { const { video, entity } await addGroundVideo(this.viewer, config); this.sources.set(config.id, { video, entity, config }); return config.id; } play(id) { const item this.sources.get(id); // 播放前先恢复实体显示 item.entity.show true; item.video.play(); } pause(id) { const item this.sources.get(id); item.video.pause(); } // 视野外自动暂停省GPU updateVisibleIds(ids) { this.sources.forEach((item, id) { const shouldShow ids.includes(id); item.entity.show shouldShow; if (!shouldShow) item.video.pause(); }); } }这个管理器的核心思路是“视频资源池”。每一路视频本质都是一个video标签内存和GPU解码资源是有限的不能几十路同时播放。实际项目里我还会加一个屏幕空间判断通过Cesium.SceneTransforms算出视频矩形在屏幕上的投影范围完全离开视锥了就让updateVisibleIds把它挂起。4.5 进阶CustomShader做视频与模型混合平面贴视频毕竟是“贴”想要视频画面和建筑模型表面融合得更真实比如视频里是楼宇内部的情况、模型表面也要半透明叠出视频内容Cesium的CustomShader就能派上用场了。这个项目后期迭代里加了这段思路是给3D Tiles模型注入自定义着色器在片元阶段把视频纹理和模型法线、颜色混合。const customShader new Cesium.CustomShader({ lightingModel: Cesium.LightingModel.UNLIT, fragmentShaderText: uniform sampler2D u_videoTexture; varying vec2 v_st; void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) { vec4 videoColor texture2D(u_videoTexture, v_st); // 按alpha混合视频和原模型颜色 material.diffuse mix(material.diffuse, videoColor.rgb, 0.8); } });CustomShader方案一般要用在Cesium3DTileset上通过tileset.customShader customShader传入。它的代价是需要自己处理纹理坐标的对应关系如果白模UV不规范视频画面就会扭曲。这个方案更适合有美术基础的团队普通业务场景直接用前两个方案就够了。5. 避坑指南视频融合最常见的五个坑与排查方法5.1 现象视频贴地后画面疯狂闪烁视频矩形贴到地面后相機一旋转矩形边缘就像锯齿一样高频闪动。这个问题的根源是矩形面与地形表面处于同一深度深度缓冲区精度不足以区分两者。原因就是height设为0导致共面。解决办法有两种一是把矩形的高度抬高一点比如0.2米闪烁立刻消失二是开启viewer.scene.globe.depthTestAgainstTerrain让地形参与深度测试但即便如此矩形和地形依然会有竞态。我把这个问题的修复顺序固定成两步先抬高height再观察classificationType是否设置成BOTH。两个都做对了地面视频才会稳定。5.2 现象视频纹理变白板什么画面都出不来视频贴上去之后矩形显示为白色像一块磨砂玻璃。这个问题在跨域视频上尤其常见。原因是纹理没有成功上传。Cesium把video元素交给GPU纹理之前需要视频帧数据可读。跨域视频没有配crossOriginanonymous时视频帧被浏览器视为脏数据Canvas读取会抛错纹理就一直是空白。解决方式是给video元素设置crossOrigin并且视频服务器返回Access-Control-Allow-Origin头。本地/videos目录下的视频走同源访问不会触发这个问题。5.3 现象贴墙视频正面看不见背面反而看得到Wall实体做的楼面视频墙正面视角下完全透明绕到建筑背面却看到画面。这是顶点绕序错误。Cesium的Wall为了性能默认只渲染正面正面由顶点绕序决定。把positions数组里的坐标按逆时针排列后画面就正常显示在正前方了。我一般习惯在地图上按“左下、右下、右上、左上”的顺序录入点位这样绕序永远是逆时针。5.4 现象多路视频同时播放GPU显存直接打满接入8路以上视频后整个三维场景拖动变得像PPT显卡风扇声音都起来了。原因很简单每一路视频都在独立解码每帧画面都在往GPU上传纹理显存和内存双压力。解决办法分层处理同一时刻保持屏幕上最多4路视频在播放超出视野的立刻pause()所有视频push成H.264编码不要用4K源videoManager里加一个LRU策略优先保留离相机近的几路。这套组合改完16路视频场景也能稳定在60帧附近。5.5 现象视频点击后不播放控制台报 NotAllowedErrorChrome自动播放策略会把没有用户手势触发的video.play()直接拦掉报NotAllowedError。这个问题在首屏自动加载视频时很容易触发。处理方式是让视频默认全部muted加上playsInline属性然后在页面首次点击或者进入场景时统一执行一次play()。更稳妥的做法是在场景初始化弹一个“进入三维场景”按钮用那次点击作为用户手势去启动所有视频。从那以后我所有Cesium视频融合项目都保留了这个交互再没被浏览器策略坑过。6. 二次开发指南把视频融合能力接到你自己的项目里6.1 替换数据源从演示坐标换到你的现场坐标拿到这个源码包想跑通自己的场景第一件事是替换点位配置。打开cameraConfig.js里面有坐标和视频的映射关系把演示坐标改成现场实际坐标再把public/videos和public/data替换成真实数据。注意影像、地形、3D Tiles三者必须使用同一套坐标系最常见的翻车就是影像用WGS84、倾斜摄影用地方坐标系结果模型飞到海里去。6.2 加业务交互点击视频矩形弹出点位详情源码预留了实体ID的关联机制二次开发时可以加点击事件const handler new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); handler.setInputAction((movement) { const picked viewer.scene.pick(movement.position); if (picked picked.id) { const entity picked.id; if (entity.id entity.id.startsWith(video-)) { // 弹出视频详情卡片 } } }, Cesium.ScreenSpaceEventType.LEFT_CLICK);用Scene.pick拾取到实体后根据entity.id前缀判断是不是视频融合点位然后弹窗展示点位名称、时间、所属分组。这套逻辑在指挥调度系统里基本是标配。6.3 一个值得保持的习惯这套源码我前前后后复用过好几次最大的启发是视频融合这类功能一定在一开始就设计好“播放资源池”的管理机制等视频路数多了再回头改调度成本会高到怀疑人生。现在每接一个三维GIS项目我第一件事都是把点位、视频、模型数据做一次完整清点然后固定走一遍影像-地形-3D Tiles-视频融合的顺序。希望这些拆解和踩坑记录能帮到你少走几步弯路。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑