简介:面向三维GIS开发者与地理信息可视化学习者,这份Cesium实战资料包围绕三维场景搭建和场景视频融合展开,针对二维GIS难以表达真实三维空间的问题,提供可参考的Web3DGIS工程实现。压缩包共2000个文件、约54.64MB,以JavaScript与HTML构建前端交互页面,Java代码承载服务端逻辑,PNG、GIF、CSS等支撑界面与图标资源;同时包含b3dm、gltf、kml等三维模型与地理数据,以及wav、mp4等视频融合素材,便于对照场景加载、模型组织与视频贴图流程。已有992人学习下载。读者可从中获得较完整的目录结构、前后端代码范例、三维瓦片与模型示例及视频融合实现思路,适合用于课程设计、项目原型搭建和Cesium功能验证时查阅借鉴。
1. 从一份 rar 到能转的三维地球:Cesium 场景搭建最容易翻车的一步
解压那个 rar,npm install,npm run dev,浏览器里一片白——这是我见过最多的 3D 三维 GIS 场景搭建现场。有意思的是,控制台里通常没有一行报错指向 Cesium 的 API,全是/Workers/目录下的 404。
Cesium 这类把地形、影像瓦片、WebGL 渲染、视频纹理全塞进浏览器的库,装不好时的表现非常一致:地球出不来,或者出来了但转两下就卡死,视频那一路更是黑成一块铁板。它适合已经在写前端、但要接手一个三维场景 Demo 的人,也适合要把监控画面投到模型上的实施岗。下面这套流程从解压开始,版本、路径、参数,以及装坏之后怎么救,都写清楚。
2. 环境清点:Node 18 与 CesiumJS 1.x 的版本对齐清单
在动手装依赖之前,我更愿意先花十分钟把环境钉死。三维场景这类工程对版本的敏感度比普通前端项目高一个档次,因为它同时踩在三条线上:构建工具链、WebGL 运行时、以及 Cesium 自身的静态资源目录结构。任何一条线错位,表现出来的都是「白屏」这一个结果,排查成本极高。
2.1 先把三样东西定死,再谈装依赖
我一般会锁定下面这三样,其余的(编辑器、Git、终端)随习惯即可。表里的「选错会怎样」不是我编的恐吓,是这几年反复遇到的真实症状。
| 组件 | 我一般锁定 | 选错会怎样 |
|---|---|---|
| Node.js | 18 LTS 或 20 LTS | 低于 16 时装 Vite 5、Rollup 4 这类新构建链会直接报 engine 不匹配;某些 22 的早期小版本上,老工程里的原生模块需要重新编译,npm install卡在 rebuild 阶段 |
| 包管理器 | npm 9+ 或 pnpm 8+ | 同一个工程里混用 npm 和 pnpm,node_modules里会留下两套依赖树,Cesium 被解析出两份实例,运行时抛Cannot read properties of undefined |
| 浏览器 | Chrome / Edge 最新版 | WebGL2 不可用时,Viewer 构造完直接抛初始化失败,页面只剩一个空 div |
先在终端里把版本念一遍,确认没有惊喜:
node -v # 期望 v18.x 或 v20.x npm -v # 期望 9.x 以上 npm config get registry # 确认没有指向某个已经失效的内网源三条命令的含义很直白:Node 版本决定构建链能不能装上,npm 版本决定 lockfile 格式是否兼容(lockfileVersion 在 npm 7 之后才有 2、3 的差异),registry 决定你等的是三十秒还是三十分钟。最后一条最容易被忽略——很多人从别人机器上拷来的工程里带着.npmrc,指向一个你根本访问不到的内网地址,然后npm install挂在那里一动不动,看着像网络慢,其实是源不可达。
顺手确认一下显卡和浏览器这一侧:
// 粘到浏览器控制台执行,返回版本号就说明 WebGL2 可用 const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl2'); console.log(gl ? gl.getParameter(gl.VERSION) : 'no webgl2');如果这里返回no webgl2,后面所有的配置都是白费力气。常见原因是浏览器开了「精简模式」、或者远程桌面环境下硬件加速被关掉。这种情况下去chrome://gpu看一眼 WebGL 那一行是不是被标成了 Software only,是的话把硬件加速打开再重启浏览器。
2.2 解压之后先判读三个文件,不要急着 install
rar 包在 Linux 和 macOS 上不是默认支持的,别用unzip硬怼,会得到一个损坏的目录。我一般用unar或者 7-Zip:
# Linux / macOS:没有 unar 先装一个;Windows 直接右键用 7-Zip 解压 unar "Cesium实现三维GIS场景搭建及场景视频融合.rar" -o ./cesium-demo cd ./cesium-demo ls -la解压完先别npm install,先看三样东西:package.json、lock 文件、以及有没有残留的node_modules。
# 只打印关键字段,避免一屏几百行看不清 node -e "const p=require('./package.json');console.log('name:',p.name);console.log('scripts:',p.scripts);console.log('cesium:',(p.dependencies&&p.dependencies.cesium)||(p.devDependencies&&p.devDependencies.cesium))"这条命令关心三件事。第一,scripts里是vite还是webpack——这决定了后面走哪条静态资源接入路线,两条路线的配置文件完全不同,不能混。第二,cesium是挂在dependencies还是peerDependencies——挂在 peer 里的意思是「宿主工程自己提供」,你直接npm install是装不上的,需要显式npm install cesium。第三,包名和版本号,这是后面所有路径拼接的基准。
如果目录里有node_modules,删掉。从别的机器上拷来的依赖目录,里面写死了绝对路径的软链接和二进制文件,换台机器九成跑不起来,留着只会让你在报错里多绕两小时。同理,.npmrc如果指向你不认识的源,先注释掉再装。
提示:把工程放在含中文或空格的路径下,某些构建工具在解析静态资源目录时会出错,Windows 上尤其明显。我一般放在
D:/work/cesium-demo这种纯英文短路径下。
3. 工程搭建:让 Cesium Viewer 在本地跑起来的三处配置
环境清点完之后,真正决定「能不能出地球」的其实只有三处配置:依赖怎么接进来、静态资源指向哪里、Viewer 初始化时用什么底图和地形。这三处任意一处错,症状都是白屏,但排查方向完全不同,所以要分开配、分开验。
3.1 两条接入路线:插件自动拷贝,还是手动搬目录
Cesium 和普通 npm 包最大的差别在于,它除了 JS 模块之外还带一大堆运行时资源:Web Worker 脚本、着色器、图标、第三方库。这些文件必须原样出现在构建产物的对应目录下,浏览器才能按路径取到。接入方式就是解决「谁来搬这些文件」。
如果工程用的是 Vite,最省事的是插件路线:
// vite.config.js import { defineConfig } from 'vite'; import cesium from 'vite-plugin-cesium'; export default defineConfig({ plugins: [ cesium() // 负责把 Cesium 的 Workers / Assets / Widgets / ThirdParty 拷进产物 ], server: { host: '0.0.0.0', port: 5173 }, build: { chunkSizeWarningLimit: 4096 // Cesium 单个 chunk 体积大,调高阈值避免满屏警告 } });plugins里的这一行做的事情,等价于构建时自动执行一次目录拷贝,并在开发服务器上把/cesium/这个前缀映射到node_modules/cesium/Build/Cesium。chunkSizeWarningLimit只是压掉警告,不影响功能,但不设的话每次 build 会刷一屏黄色提示,容易把真正的报错淹掉。
如果工程是 Webpack,就得手动搬,一个目录都不能漏:
// webpack.config.js const CopyWebpackPlugin = require('copy-webpack-plugin'); const webpack = require('webpack'); module.exports = { plugins: [ new CopyWebpackPlugin({ patterns: [ { from: 'node_modules/cesium/Build/Cesium/Workers', to: 'cesium/Workers' }, { from: 'node_modules/cesium/Build/Cesium/ThirdParty', to: 'cesium/ThirdParty' }, { from: 'node_modules/cesium/Build/Cesium/Assets', to: 'cesium/Assets' }, { from: 'node_modules/cesium/Build/Cesium/Widgets', to: 'cesium/Widgets' } ] }), new webpack.DefinePlugin({ // 关键:告诉 Cesium 运行时去哪里找上面拷出来的东西 CESIUM_BASE_URL: JSON.stringify('cesium') }) ] };四个目录各管一摊:Workers是地形解码、几何计算这些跑在后台线程里的脚本,缺了它地球直接不出来;Assets是纹理、近似星图、默认图标;Widgets是那个带时间轴和动画控件的 CSS 和图标;ThirdParty是 Cesium 内部依赖的第三方库。我见过只拷Workers就以为万事大吉的,结果控件全是没样式的裸按钮。
DefinePlugin里那个值写的是相对路径cesium,不是/cesium,这个差别在部署到子目录时会要命:带前导斜杠表示从域名根开始找,部署在https://example.com/gis/下就会 404。
3.2 CESIUM_BASE_URL 的三种写法与踩坑点
除了构建期注入,还有两种写法,实际项目里经常混着出现,得知道优先级。
第一种是全局变量,写在index.html里,且必须在任何 Cesium 模块执行之前:
<!-- index.html --> <script> // 必须早于 Cesium 模块加载,否则运行时读不到 window.CESIUM_BASE_URL = '/cesium/'; </script>第二种是纯静态部署,直接把node_modules/cesium/Build/Cesium整个目录扔到服务器上,然后配一个反向映射。这种方式在离线内网环境里很常见,好处是不依赖构建工具。
判断路径对不对有个很笨但很快的办法:启动开发服务器,打开浏览器 Network 面板,筛Workers。正常情况下应该能看到一批.js请求返回 200。如果全是 404,把 404 的 URL 复制出来,对照你配置的CESIUM_BASE_URL和实际文件位置,一定能看出差在哪一层目录。
注意:改了
CESIUM_BASE_URL之后要重启开发服务器,热更新不会重新注入这个值,改了没生效八成是没重启。
3.3 Viewer 初始化:底图、地形和一堆该关掉的控件
到这一步地球应该能出来了,但默认的 Viewer 带一大堆控件和在线服务,直接上线既不好看也不可控。我一般初始化时就一次性配干净:
import * as Cesium from 'cesium'; import 'cesium/Build/Cesium/Widgets/widgets.css'; // 离线环境把这个置空,并同步去掉 baseLayer / terrainProvider 里的在线服务 Cesium.Ion.defaultAccessToken = '<换成你自己的访问令牌>'; const viewer = new Cesium.Viewer('cesiumContainer', { animation: false, // 时间轴动画控件 timeline: false, // 底部时间轴 baseLayerPicker: false, // 底图选择器,会去拉在线列表 geocoder: false, // 地名搜索 homeButton: false, sceneModePicker: false, // 2D/3D 切换 navigationHelpButton: false, fullscreenButton: false, infoBox: false, // 点击要素弹出的信息框 selectionIndicator: false, // 选中时的绿色取景框 // 地形换成自己的瓦片服务 terrainProvider: new Cesium.CesiumTerrainProvider({ url: '/terrain/', // 指向 layer.json 所在目录 requestVertexNormals: true // 需要光照效果时打开,会增加显存占用 }), // 影像换成自己的瓦片服务 baseLayer: new Cesium.ImageryLayer( new Cesium.UrlTemplateImageryProvider({ url: '/tiles/{z}/{x}/{y}.png', minimumLevel: 1, maximumLevel: 18, // 限定请求范围,避免相机一拉远就发出上百个无效请求 rectangle: Cesium.Rectangle.fromDegrees(116.0, 39.5, 116.8, 40.1) }) ) }); // 让地物与地形做深度比较,否则贴着地面的东西会被地形穿透 viewer.scene.globe.depthTestAgainstTerrain = true;逐个说关键参数。terrainProvider的url要指向layer.json所在的那一层目录,不是.terrain文件本身,写错了会得到一个「地形加载完成但全是平地」的诡异结果。requestVertexNormals打开后地形会有正确的光照,代价是每个顶点多一份法线数据,瓦片数据量大概涨三成,显卡吃紧时先关它。rectangle是我强烈建议加的:不限定范围时,相机拉远后 Cesium 会按当前视锥去请求全球范围的瓦片,请求数瞬间上去,本地服务直接被打满。
depthTestAgainstTerrain这一行是后面视频融合能否贴住地面的前提,先记住它。
4. 视频融合:把摄像头画面贴进三维场景的投影配置
三维场景跑通了,接下来才是这个工程真正的核心:把摄像头的实时画面投到三维场景的表面上。这块的难点不在 Cesium 的 API,在于「对位」——视频是二维的,场景是三维的,中间靠一组相机参数和投影矩阵把它们绑起来。参数差一点,画面就歪出去几十米。
4.1 三条实现路线的取舍
| 路线 | 做法 | 适用场景 | 主要代价 |
|---|---|---|---|
| 立面贴片 | 用一个矩形或墙体贴上视频材质,人工摆到建筑立面上 | 固定机位、画面本来就正对墙面、对精度要求不高 | 换个机位就得重摆,贴多面墙要切分几何 |
| 视锥投影纹理 | 用自定义着色器,把片元的世界坐标经投影矩阵反算成视频 uv 再采样 | 任意机位、需要同时贴合地形与建筑、多路视频拼接 | 需要标定相机内外参,着色器调试成本高 |
| 离线烘焙 | 提前把视频投影成纹理或模型文件,运行时只加载结果 | 视频内容固定、只做演示回放 | 不是实时,换个视频要重跑一遍 |
大部分工程最后都会走到第二条。第一条做 Demo 够用,但一旦客户说「摄像头往前挪了两米」,所有手工摆位全部作废。第三条看起来省事,实际上把问题推到了离线流程里,调试更痛苦。
4.2 最小可跑的视频纹理
先用最土的办法确认视频链路是通的,再上投影。核心就两步:准备一个能自动播放的 video 元素,把它当材质挂到几何体上。
<!-- 必须带 muted 和 playsinline,否则大部分浏览器会拦截自动播放 --> <video id="cam01" src="/video/cam01.mp4" autoplay muted loop playsinline crossorigin="anonymous"></video>const videoEl = document.getElementById('cam01'); // play() 返回 Promise,被拦截时不会抛全局错误,一定要接住 videoEl.play().catch(err => console.warn('视频自动播放被拦截:', err)); const entity = viewer.entities.add({ rectangle: { coordinates: Cesium.Rectangle.fromDegrees(116.300, 39.900, 116.320, 39.920), height: 0.5, // 抬高半米,避免和地形 z-fighting 导致闪烁 material: new Cesium.VideoMaterialProperty(videoEl) } });几个参数值得单独讲。muted和playsinline不是可选项,是自动播放策略的硬门槛,少了任何一个,play()就会被拒绝,材质上显示的是视频的第一帧或者一片黑,看起来像「纹理没加载」。crossorigin="anonymous"是为了走 CORS 请求,否则视频虽然能播,但作为纹理使用时会被判定为跨域污染。height: 0.5这个半米偏移是血泪经验,视频平面和地形完全共面时,深度值打架,画面会随相机角度一闪一闪。
VideoMaterialProperty接受HTMLVideoElement,也接受 URL 字符串。接受字符串时由 Cesium 内部创建 video 元素,省事但你拿不到播放状态;我一般传元素,因为后面要做暂停、切流、销毁,都需要拿到这个对象。
4.3 投影对齐:相机位姿与视锥的四个参数
真正的投影融合,需要把现场摄像头的内外参搬进代码。外参是位置和姿态,内参主要是视场角和宽高比。这一组值通常来自现场标定,或者从设备文档里抄,抄错了画面就会整体偏移。
// 用一台"虚拟相机"承载现场摄像头的位姿,再取出它的投影矩阵 const virtualCamera = new Cesium.Camera(viewer.scene); virtualCamera.setView({ destination: Cesium.Cartesian3.fromDegrees(cam.lon, cam.lat, cam.height), orientation: { heading: Cesium.Math.toRadians(cam.heading), // 方位角,正北为 0,顺时针 pitch: Cesium.Math.toRadians(cam.pitch), // 俯仰角,水平为 0,向下为负 roll: Cesium.Math.toRadians(cam.roll) // 横滚角,多数枪机为 0 } }); virtualCamera.frustum = new Cesium.PerspectiveFrustum({ fov: Cesium.Math.toRadians(cam.hFov), // 水平视场角,常见 60 到 90 aspectRatio: cam.width / cam.height, // 视频宽高比 near: 0.5, far: 5000 // 超过这个距离的片元不参与投影 }); // 视图矩阵 × 投影矩阵,交给着色器把世界坐标反算成 uv const viewProj = Cesium.Matrix4.multiply( virtualCamera.frustum.projectionMatrix, virtualCamera.viewMatrix, new Cesium.Matrix4() );heading、pitch、roll三个角的单位和符号是最容易错的地方。Cesium 里heading是弧度、正北为 0、顺时针为正,而设备文档给的往往是度、正东为 0,中间要做一次换算加一次偏移。pitch向下为负这一点也和不少设备的约定相反。我第一次接的时候就是符号搞反了,画面整个上下颠倒,看了半天以为是纹理坐标的问题。
far不宜设得太大,它决定了投影的深度范围,设成几万会把远处建筑的片元也算进来,白白增加采样开销。
着色器那一侧负责把世界坐标投回屏幕坐标再采样:
const shader = new Cesium.CustomShader({ uniforms: { u_video: { type: Cesium.UniformType.SAMPLER_2D, value: videoTexture }, u_viewProj: { type: Cesium.UniformType.MAT4, value: viewProj } }, fragmentShaderText: ` void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) { // positionMC 经模型矩阵换到世界坐标,属性名以所用版本的自定义着色器文档为准 vec4 worldPos = czm_inverseView * vec4(fsInput.attributes.positionMC, 1.0); vec4 clip = u_viewProj * worldPos; vec2 uv = clip.xy / clip.w * 0.5 + 0.5; // NDC 映射到 0 到 1 // 视锥之外的片元不投影,避免画面在背面重复出现 float inside = step(0.0, uv.x) * step(uv.x, 1.0) * step(0.0, uv.y) * step(uv.y, 1.0); vec4 color = texture(u_video, vec2(uv.x, 1.0 - uv.y)); // 视频纹理通常要翻转 Y material.diffuse = mix(material.diffuse, color.rgb, inside * color.a); }` });uv.x, 1.0 - uv.y这个翻转来自视频纹理的原点在左上,而 WebGL 纹理原点在左下,不翻的话画面上下颠倒。inside这个系数解决的是「背面的建筑也被投上画面」的问题,不加它,视锥反方向的墙面上会鬼影一样出现同一段视频。
4.4 多路视频与性能调参
单路通了之后就是多路。我实测下来,普通办公机同时解码四到六路 1080p 已经比较吃紧,再多就开始掉帧,而且掉的不是渲染帧,是视频解码本身。几个能立刻见效的点:
一是同一个页面只保留一个Viewer。我见过每路视频建一个 Viewer 的写法,八个 WebGL 上下文直接把浏览器拖死。多路视频应该作为多个 entity 或 primitive 挂在同一个场景里。
二是控制纹理上传频率。视频每秒 25 到 30 帧,但三维场景的相机不动时其实不需要每帧都重新上传纹理。可以用requestVideoFrameCallback在视频真有新帧时才更新 uniform,静止场景下能省下不少开销。
// 只在视频产生新帧时才更新材质,避免空转上传 function bindFrameUpdate(videoEl, material) { const step = () => { material.uniforms.image = videoEl; videoEl.requestVideoFrameCallback(step); }; videoEl.requestVideoFrameCallback(step); }三是清理要彻底。切换摄像头或者切页面时,video 元素不释放会一直占着解码器和内存,页面开久了内存曲线一路往上。我一般在组件卸载时统一走一遍:
function disposeVideo(viewer, videoEl, entity) { if (entity) viewer.entities.remove(entity); videoEl.pause(); videoEl.removeAttribute('src'); videoEl.load(); // 关键:清空 src 后调 load,才会真正触发资源释放 videoEl.remove(); }removeAttribute('src')之后必须再调一次load(),这是 HTML 规范里让浏览器终止当前资源下载并重置状态的方式,只清属性不调load(),解码器仍然挂着。
5. 避坑与排查:五类装完就崩的典型现场
下面这几条都是我或者身边的人真实撞过的,按症状、原因、解决写,遇到了可以直接对号入座。
5.1 构建期:白屏与 404
现象:npm run dev之后页面纯白,控制台里刷屏的 404 都指向/Workers/xxx.js或者/Assets/xxx.png。
原因:Cesium 的运行时资源没有出现在它期望的路径下。要么CESIUM_BASE_URL没配,要么配了但指向的目录和实际拷贝的位置对不上,要么只拷了Workers忘了Assets和Widgets。
解决:打开 Network 面板筛Workers,把 404 的完整路径抄出来,对照构建产物目录逐层比对。Vite 工程直接用vite-plugin-cesium,Webpack 工程确认四个目录都在patterns里。改完重启开发服务器。
现象:npm install卡住不动,或者装完启动时报Cannot find module但目录里明明有。
原因:工程里残留了从别处拷来的node_modules或者指向不可达源的.npmrc。
解决:删掉node_modules和 lock 文件重装,.npmrc先注释掉。这一步看着粗暴,实际比逐个排查软链接快得多。
5.2 运行期:地形、纹理与视频
现象:地球出来了,但是完全平的,一点起伏都没有,控制台偶尔报 401 或 403。
原因:地形服务的地址写错,或者访问令牌失效。CesiumTerrainProvider的url必须指向layer.json所在目录,写到具体瓦片文件上会静默失败。
解决:浏览器直接访问那个url,能看到 JSON 就说明路径对。完全离线的环境记得把默认令牌置空,并且不要在构造 Viewer 时传任何在线服务,否则它还是会去请求。
现象:视频材质一片黑,或者只显示第一帧。
原因:自动播放被拦截、视频编码不被浏览器支持、或者play()的 Promise 被拒绝而代码里没接。
解决:确认muted和playsinline都在,先单独在页面上放一个 video 标签手点播放看能不能出画面,能出说明是策略问题,不能出说明是编码问题。H.265 在很多浏览器里就是不支持,转成 H.264 基线。
现象:视频能播,但贴上去之后画面发黑、或者报跨域相关错误。
原因:视频资源跨域,且没有声明crossorigin,纹理上传时被判定为污染。
解决:video 标签加crossorigin="anonymous",服务端补Access-Control-Allow-Origin。两边缺一不可,只加一边等于没加。
现象:视频贴上去之后,相机一转就一闪一闪,或者被地面切掉一半。
原因:视频面和地形共面导致深度冲突,或者depthTestAgainstTerrain没开导致遮挡关系错乱。
解决:把平面抬高 0.1 到 0.5 米,同时打开viewer.scene.globe.depthTestAgainstTerrain = true。抬高量别太大,超过一米肉眼就能看出画面浮在空中。
注意:这几条里最容易误判的是「视频黑屏」,它可能是播放策略、编码、跨域、纹理坐标四个原因中的任意一个。排查顺序建议从「单独播放能不能出画面」开始,一次只改一个变量。
6. 验证跑通:三步确认场景与视频真的对上了
装完不等于跑通。三维场景这类东西最坑的地方在于,它「看起来能转」,但地形可能没加载、视频可能贴偏了五十米。我在交付前一定会走一遍下面三步。
第一步,资源自检。开发服务器起来后,Network 面板筛Workers和Assets,全部 200 才算过。顺带看一眼有没有重复请求同一个瓦片,重复请求多说明rectangle没限定或者相机在抖。
第二步,运行时自检。把下面这段粘到控制台,五秒后看输出:
setTimeout(() => { const scene = viewer.scene; console.log('地形瓦片是否加载完成:', scene.globe.tilesLoaded); console.log('场景内实体数量:', viewer.entities.values.length); console.log('相机高度(米):', Math.round(viewer.camera.positionCartographic.height)); }, 5000); // 采样三秒的平均帧率 let frames = 0; const t0 = performance.now(); (function tick() { frames++; if (performance.now() - t0 < 3000) requestAnimationFrame(tick); else console.log('平均帧率:', (frames / 3).toFixed(1)); })();tilesLoaded为false说明还有瓦片在飞或者请求失败,这时候去看 Network 里有没有红色请求。帧率在静止相机下应该接近屏幕刷新率,转起来掉到 30 以下就得回头查视频路数和纹理尺寸。
第三步,对位自检。这是最容易被跳过的一步。在场景里找一个已知坐标的地标(一栋楼的角、一个路口),把视频画面里同一个地标的位置和三维场景里对齐,误差超过两三米就说明heading或pitch的符号有问题。我一般会在场景里临时加三个点标记,对完再删掉。
| 检查项 | 期望结果 | 不通过时先看哪里 |
|---|---|---|
| Workers / Assets 请求 | 全部 200 | CESIUM_BASE_URL与构建产物目录 |
globe.tilesLoaded | 五秒内变 true | 地形 url 是否指向layer.json目录 |
| 静止帧率 | 接近刷新率 | 视频路数、纹理尺寸、是否每帧上传 |
| 地标对位误差 | 三米以内 | heading/pitch的符号与单位 |
| 页面关闭后内存 | 回落到初始水平 | disposeVideo是否调了load() |
这套流程我吃过亏才固定下来的。早些年交付一个演示环境,现场看着一切正常,回来之后对方说「视频偏了」,一查是标定给的heading是度、我按弧度用了,差了五十七倍,画面整个甩到场景外面去。从那以后我每次接视频融合,都强制走一遍「地标对位」这一步,哪怕时间再紧也不跳。希望帮到你。
本文还有配套的精品资源,点击获取