简介:面向 Unity 开发者,提供 Cesium for Unity 1.17.0 的离线插件包,解决因网络或源不可达导致 Package Manager 无法直接下载安装的痛点。包内共 1158 个文件,压缩后约 316.6MB,涵盖 C# 脚本、编译好的原生库(.a/.pc/.so/.dylib 等)、Shader 相关资源、DLL 动态链接库、Prefab 与纹理文件等,包含托管层代码与跨平台原生实现,导入项目后即可调用 Cesium 地形、3D Tiles 等核心能力。附带同名文章说明具体导入与配置流程,适合无法通过官方 Package Manager 拉取插件的开发者,以及需要离线部署或固定版本管理的团队。目前已有 411 人学习下载,在离线场景下具备较高实用价值。
1. Cesium for Unity 1.17.0 离线插件包:内网环境下数字孪生项目的救命稻草
很多做智慧园区、电力管理、航天仿真的人,到了内网环境才发现,Unity 里装个 Cesium 插件原来这么痛。用 Package Manager 拉 com.cesium.unity,要么仓库连不上,要么等半天只出来一个“Network Error”。Cesium for Unity 1.17.0 离线插件包,就是把这套官方插件连同原生 C++ 运行库、Editor 工具和样例场景完整打好的安装包,让一台不联网的开发机也能把 Cesium 跑起来。它能解决的是“插件安装”的离线问题,而不是“数据”的离线。装完以后,你仍然要准备本地地形、影像或倾斜摄影数据,通过 Cesium3DTileset 加载进去。适合 Unity 2021 LTS 及以后版本的数字孪生、三维 GIS 和仿真训练场景,也适合从零开始搭一个不依赖 Cesium ion 的私有化底座。
2. 先搞懂 Cesium for Unity 1.17.0 的架构:离线包不等于离线数据
2.1 插件离线与数据离线的边界
Cesium for Unity 本质上是 Cesium 原生引擎的 Unity 封装。它由 C# 层和 C++ 原生库组成,C# 层负责 Unity 生命周期、组件、Editor 菜单,原生库负责 3D Tiles 的请求、解析、调度、栅格化和渲染。离线插件包把这部分预编译好的东西带过来,省去了从 GitHub 和官方 NuGet 拉依赖的过程。但插件本身不会内置地形和影像瓦片。
我在离线项目里最常说的一句话是:离线插件包只是第一步,数据离不了网,项目照样转不起来。因为 Cesium for Unity 的默认行为是访问 Cesium ion 的 REST API,把托管在云端的 3D Tiles 资产拉回本地。即使安装了离线包,一旦运行它的默认设置,它还是会尝试连 ion。所以你要做的第二件事,是切断它对 ion 的依赖,把 Cesium3DTileset 的加载地址改成局域网或本机的数据源。
这里有三个层级需要分清:
- 引擎层离线:指插件本体和依赖库都已经装入 Unity,安装不再需要网络。
- 数据源离线:指地形、影像、倾斜摄影以 3D Tiles 或栅格格式存在本地磁盘,通过 HTTP 服务供运行时读取。
- 凭证离线:指 Cesium ion 的 Token 和私有资源访问不再被需要,代码里不出现 Ion Asset ID,也不调用需要联网的 API。
很多团队拿到离线包后直接拖进场景,结果还是要配 token,那是因为没完成第二层和第三层的配置。后面第 3 章会专门讲怎么把数据源切成本地。
2.2 1.17.0 里最核心的四个组件
Cesium for Unity 的组件体系并不复杂,但新手容易混淆。我一般把最常用的四个组件列成一个表,按依赖顺序排:
| 组件 | 作用 | 关键参数 |
|---|---|---|
| CesiumGeoreference | 定义 Unity 世界原点对应的经纬度,是整个地球坐标系的锚点 | latitude, longitude, height |
| Cesium3DTileset | 加载 3D Tiles 数据流,支持本地 URL 或 ion 资产 | url, ionAssetID, maximumScreenSpaceError |
| CesiumGlobeAnchor | 把 Unity 对象绑定到指定经纬度,跟随地球坐标变换 | longitude, latitude, height |
| CesiumCameraController | 控制相机在地球表面移动,处理 Roll、Pitch、Yaw | 输入模式、最大俯仰角 |
CesiumGeoreference 不能缺。我见过有人只挂了 Cesium3DTileset 就运行,结果所有瓦片都朝一个方向飞出去,其实是坐标系原点没有定义。1.17.0 里 Georeference 的默认位置是美国西海岸,如果你要加载的是中国某城市的倾斜摄影,不把经纬度改过来,永远对不上。
Cesium3DTileset 的 url 参数比 ionAssetID 优先级更直接。在离线场景中,你只需要把 url 指向本地tileset.json,例如http://192.168.1.10:8080/data/tileset.json,它就会按 json 里的 content 和 tile 层级去请求子瓦片。maximumScreenSpaceError 控制瓦片细分的阈值,默认 16,数字越小越精细,但开销成倍翻。离线包自带的样例场景里通常有一个清晰度较高的城市示例,你可以拿它做基准测试,再按项目需要改参数。
2.3 为什么离线场景必须用 HTTP 协议喂数据
切到本地数据源后,很多第一次做的同事习惯直接把file:///C:/tiles/tileset.json填进 url,结果运行时一片黑。这不是 Cesium 的 bug,而是 Unity 默认的 UnityWebRequest 对 file 协议限制很多,尤其打包成 Windows 桌面程序后,file 访问会被识别为不安全的本地资源。再加上 3D Tiles 的 Tile URL 都是相对路径,需要按 HTTP 解析,file 根本没法正确处理。
所以,本地化数据源最常见的部署方式,是用一个轻量的静态服务器把切片目录暴露出去。开发机上用什么无所谓,到了现场,局域网一台普通 Windows 工控机,配个 Nginx 或 Caddy 就够了。切片数据通常有几种形态:
- 标准 3D Tiles:
tileset.json加若干.b3dm、.pnts、.glb文件。 - 栅格影像:GeoTIFF 或 PNG 切片,作为 CesiumRasterOverlay 叠加到地形上。
- 地形高程:terrain tiles,常用 quantized-mesh 格式。
Cesium for Unity 的 RasterOverlay 目前对本地栅格的支持,比较稳的是先转成 Web Map Service 或 WMTS,再发布到本地服务。当然,如果只是做一个室内小范围的倾斜摄影,直接在 Tileset url 里填入带端口号的本地地址就够了。
3. 离线安装 Cesium for Unity 1.17.0:从零导入到跑通本地 3D Tiles
3.1 用 Package Manager 从离线包安装
Cesium for Unity 1.17.0 离线包的形式通常有两种:.tgz的 npm 包格式,以及.unitypackage的传统格式。如果是.tgz,最省事的方式是把文件放在项目文件夹外,然后在Packages/manifest.json里增加一条本地路径依赖:
{ "dependencies": { "com.cesium.unity": "file:../offline_packages/cesium-unity-1.17.0.tgz" } }这段配置的逻辑是让 Unity Package Manager 从本地文件系统读取com.cesium.unity的包描述,不访问任何远程仓库。路径支持相对路径和绝对路径,建议相对路径,方便团队里其他人拉代码后自行调整目录。
如果你的离线包是.unitypackage,直接双击会在 Unity 里弹出导入窗口,勾选全部内容后等待导入完成。这里要注意,.unitypackage导入后不会自动写入 manifest,如果是旧版本项目,建议导入后确认Packages目录下存在 Cesium 相关包,如果没有,还是要回到 tgz 方式。我一般更喜欢 tgz,因为版本可追踪、可回退,同时能被 Package Manager 的依赖解析识别。
3.2 创建最小场景:挂上 Georeference 和 Cesium3DTileset
导入完成后,新建空场景,创建根物体命名为 CesiumScene,然后挂一个 CesiumGeoreference。接下来要确定你的坐标原点。比如你要加载杭州某产业园,就在 Inspector 里把纬度设为 30.2741,经度 120.1551,高度 0。这个值会决定后续所有对象的 Unity 坐标零点。
场景里再创建一个空物体,挂 Cesium3DTileset。这里可以用编辑器手动填 url,也可以写一段初始化脚本来在运行时设置。下面的代码是一个最小可跑的例子:
using CesiumForUnity; using UnityEngine; public class OfflineTilesetLoader : MonoBehaviour { public CesiumGeoreference georeference; public Cesium3DTileset tileset; void Start() { // 设置本地3D Tiles的根地址 tileset.url = "http://127.0.0.1:8080/tiles/tileset.json"; // CesiumGeoreference 必须存在,经纬度要指向数据覆盖区域 georeference.latitude = 30.2741; georeference.longitude = 120.1551; georeference.height = 0; tileset.maximumScreenSpaceError = 8; } }这段脚本把 Tileset 的加载地址指向本机 8080 端口,并动态调整了几何误差阈值。maximumScreenSpaceError 是控制瓦片细分的关键参数:值越小,瓦片切得越精细,远处细节也越多,但每帧绘制的三角形数量会明显上涨;值越大,加载越快,远处会看到明显的简化模型。在离线内网项目里,如果机器是工控机,我建议先改成 16 跑起来,确认不掉帧后再往 8 或 6 压。
3.3 起一个本地静态文件服务
数据源推荐用 HTTP 而不是 file。最简单的方法是打开命令行,进入 tileset.json 所在目录,用 Python 起一个单线程服务:
cd D:\offline_data\hangzhou_tiles python -m http.server 8080这会监听 8080 端口,并把当前目录作为根目录。访问http://127.0.0.1:8080/tileset.json时,Cesium 就能按相对路径请求Content目录下的瓦片。注意,Python 的http.server默认只支持单线程,如果项目同时有多个 Tileset 或大量用户请求,会卡在 IO 上。这种玩法只适合开发阶段。正式部署,我一般会在现场的服务器上丢一个 Nginx:
server { listen 8080; root E:/tiles; location / { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept"; if ($request_method = OPTIONS) { return 204; } } }为什么非要加 CORS 头?因为 Cesium for Unity 在某些渲染线程里发起的请求,会被 Unity WebRequest 判定为跨域,尤其是 Tileset 的 URL 与项目的 URL 不是同一个 Host 时。刚才 Python 服务没有加 CORS 头,但本地回环请求一般能过,换成另一台机器或打包后在局域网部署,就会出现“可以下载 tileset.json,但子瓦片全部失败”的诡异现象。因此,离线包导入后,数据服务也要按内网标准配置好。断开外网后,Cesium 不会再往 ion 上报任何请求,只依赖你指定的 URL。
3.4 验证加载成功:看 Console 而不是看画面
3D Tiles 加载失败时,画面黑得很容易让人误以为是渲染问题,其实第一个要看的是 Console 日志。在 Console 窗口打开后,运行场景,把日志级别调到 Info。Cesium for Unity 1.17.0 加载成功时,会看到类似Loading tileset...然后Tileset loaded successfully的信息。如果 URL 返回 404,则会出现Failed to resolve tileset URL,这时检查服务目录是否正确。
另外,也可以在运行时打开 Profiler 的 Memory 面板,看 Web Stream 的 Native Memory 是否在持续增长。瓦片一旦开始加载,原生内存会阶梯式增加,同时帧率会短暂下降。如果内存一直为零且没有报错,说明 Cesium 认为没有需要加载的瓦片,常见原因是 Georeference 的经纬度和数据覆盖区域不重合,或者经纬度反了。这时把 Georeference 改成数据中心的实际坐标,再重新运行。
4. 避坑:Cesium for Unity 1.17.0 离线包最常见翻车点
4.1 黑屏:URL 挂了、CORS 拦了、坐标飞了,三种现象混在一起
最让人崩溃的是运行后什么也不显示。我第一次搭离线 Tileset 时,花了两个小时找原因,最后发现是 URL 里少了一个s。这里有三个判断步骤。
先看 Console 有没有红色报错。如果报的是 404,说明tileset.json路径不存在,检查静态服务的根目录与 URL 是否一致。如果报的是 CORS 错误,说明服务器响应头没有Access-Control-Allow-Origin,我在上面 Nginx 段里写的配置就是用来解决这个的。
如果没有任何报错,画面依然黑,把相机移到tileset.json所描述的地图范围上方,用 Debug.Log 打印瓦片数量:
void Update() { if (tileset != null && tileset.isLoaded) { Debug.Log($"Tileset loaded: {tileset.url}"); } }这个例子是示意,实际 API 可能略有差异,但思路是看瓦片是否进入调度队列。如果一直看不到加载日志,多半是 Georeference 的经纬度和瓦片数据的坐标原点冲突。比如数据是杭州三十公里范围,你却把原点设在上海,Cesium 会认为视野内没有瓦片,直接跳过。
4.2 模型位置跑到地心:CesiumGlobeAnchor 的坐标单位
Cesium for Unity 使用地球椭球体作为基础,Unity 中的单位是米。很多在 CAD 和 GIS 里习惯用经纬度加高程放置模型的人,直接把 GPS 坐标填入 Transform.position,然后发现模型掉到地心或者横向漂移几百米。正确做法是利用 CesiumGlobeAnchor 组件,它会把经纬度和高度自动换算成以 Georeference 为原点的局部坐标。
我在项目里处理一个 OBJ 模型时也踩过这个坑。OBJ 本身没有地理参考,我在加载后手动设置globeAnchor.longitude和latitude,但模型还是歪的。原因是 OBJ 的局部坐标没有归一到地心坐标系。最后解决办法是把 OBJ 转成 GLTF,并在转换时把模型原点放到模型地面中心,然后再挂 CesiumGlobeAnchor。对于倾斜摄影,不要在 Cesium 场景里手动给 Tileset 加 Transform 偏移,而应该用 Georeference 或 Tileset 的maximumScreenSpaceError来调节显示效果。
这里还要注意,CesiumGlobeAnchor 在父子层级中会影响 Unity 的 scale。如果父节点是非均匀缩放,子节点很容易出现透视变化。建议把模型放在一个干净的根节点下,GlobeAnchor 挂在根节点,Scale 保持 1,1,1。
4.3 粒子特效与动态光照导致内存只升不降
数字孪生项目里,粒子特效和动态光照几乎是标配。比如在 Cesium 场景里加一个下雨粒子和一个跟随太阳的平行光,一跑起来,内存每十分钟上涨两三百 MB。这不是 Cesium for Unity 的协程管理出了问题,而是粒子系统和 Cesium 的瓦片缓存叠加在一起,导致 GC 压力过大。
Cesium 的瓦片数据进入 Unity 后,每帧需要更新 Renderer 的 Transform、Material 和 Mesh。粒子系统每帧也要更新,CPU 反复触发堆分配。遇到这种情况,先把粒子的Simulation Space改成 World,避免每一个粒子都随 Transform 变换;同时把粒子系统的Max Particles控制在 3000 以下。另一个方法是限制 Cesium 的瓦片缓存,在 Cesium3DTileset 的缓存配置里把 Tile 缓存上限从默认的 1024 降到 256,尤其当场景视野集中在一栋楼时,没必要缓存不在视野内的低层级瓦片。
4.4 渲染管线和 Shader 的冲突
Cesium for Unity 1.17.0 底层用了大量自定义 Shader,它在 Built-in 渲染管线里最稳。如果项目用了 URP 或 HDRP,导入后常见的表现是 Cesium 的地面变成紫色或闪屏。这里不能简单改 Rendering Path,需要给 Cesium 的 Shader 包加 URP 兼容的变体。
我有一个经验:离线包导入后先不要急着切换渲染管线,先用默认 Built-in 跑通 Unity 2021.3 或 2022.3。如果必须用 URP,就把 Cesium 的镜头单独拎出来,使用 Cesium 自带的 Camera Controller,不要叠加大型后处理栈。Cesium for Unity 1.17.0 在 URP 下的后处理兼容性还有不少边界,比如 AO 会影响瓦片边缘出现黑线。
5. 进阶玩法:离线场景里把拖拽模型、动态光照和本地数据放进 Unity
到了这一步,你的离线包已经能稳定加载本地 3D Tiles。接下来最能提升项目体验的,是把模型和光照也做成离线、可交互的状态。
先说说拖拽模型。Cesium for Unity 不像普通 Unity 游戏可以直接用ScreenPointToRay打在 Collider 上,因为场景是围绕地球表面构建的。我的做法是先用鼠标射线检测屏幕位置处的 Cesium 地表高度,然后把模型放到那一点。用CesiumGlobeAnchor的经纬度更新方法,拖拽过程中每帧更新,模型就在地理表面流畅移动。代码如下:
using CesiumForUnity; using UnityEngine; public class DragGeoModel : MonoBehaviour { public CesiumGlobeAnchor anchor; public float height = 0; void Update() { if (Input.GetMouseButton(0)) { // 这里的射线转换方法要根据你的相机实现 // 返回经纬度后更新anchor anchor.longitude = GetLonFromMouse(); anchor.latitude = GetLatFromMouse(); anchor.height = height; } } }这里的关键是经纬度更新函数需要自己实现,可以用 CesiumGeoreference 的屏幕转地理坐标接口。参数上唯一要注意的是高度是否贴合地形,否则模型会悬空或穿地。
动态光照方面,Cesium for Unity 1.17.0 有 CesiumSunSky 组件,可以根据系统和地理位置自动计算太阳高度和方位,模拟随时间变化的光照。我在离线项目里会配合一个 Unity 方向光,让方向光的旋转角度和太阳位置同步。这个功能对航拍场景特别有利,可以直观看到不同时间段下的阴影变化。
最后,本地气象数据展示是很多应急项目的高频需求。NetCDF 这类二进制数据没法直接被 Cesium 加载,我一般先用工具转成 GeoTIFF,再用 GIS 服务器发布成 WMS,最后在 Cesium 场景里作为 RasterOverlay 叠加。MVT 矢量瓦片同理,Cesium for Unity 对 MVT 的原生支持还比较新,建议先转 GeoJSON 再考虑绘制。
以前我总图省事,不想起本地服务器,硬把 file:// 路径塞进 Tileset,结果黑屏一下午。后来老老实实把 Nginx 配好,所有离线包项目都顺利跑通。离线插件包解决的是安装问题,真正要上生产,数据准备和网络边界才是逃不掉的活。希望帮到你。
本文还有配套的精品资源,点击获取