Skip to content

高德车道级精模渲染:资源、锚点与比例尺 ​

车道级精模不是「把模型摆到地图上」这么简单:资源要从 Nova 云配按城市圈选下发,经纬度要卡在高德 SDK 的 BeginDraw/EndDraw 之间转成三角化坐标,Unity 侧再用每帧浮动的锚点把千万米级的墨卡托坐标压回厘米级精度。

首批地标3 城 11 个
定位精度厘米级
比例尺范围Level 8–24

背景 ​

6.0 一张图把车道级能力从百度 SDK 迁到高德 SDK,精模(车道级建筑物 + 城市地标)是视觉差异最大的一块。整条链路分四段:Nova 云配平台按城市发布精模资源包,车端下载校验后入库;运行时根据屏幕中心点圈选周边模型下发给 Unity;高德 SDK 负责把经纬度转成与底图对齐的三角化坐标;Unity Napa5 负责摆放和渲染。

首批地标覆盖 3 个城市 11 个点位:北京(CCTV 总部、中国尊、鸟巢、水立方)、上海(东方明珠、上海中心、环球金融中心、金茂大厦)、广州(广州塔、小鹏广州总部、猎德大桥),每个地标都要做日间(浅色)和夜间(深色)两套效果。

问题 ​

问题具体表现不处理的后果
中心点拿不到高德不透出屏幕中心点经纬度,只能靠接口轮询 GAMapCameraInfo 的 targetX/Y/Z圈选逻辑无从下手
坐标转换有时序约束经纬度 → 三角化坐标必须卡在 SDK 的 BeginDraw/EndDraw 之间调用,且要每帧跟随跨帧转换,精模和底图错位
浮点精度不够墨卡托坐标千万米级,Unity float 在千万量级只剩米级精度车道级导航要的厘米级定位直接丢光
资源治理复杂城市索引、版本号、下载中状态、重复资源都要管理重复下载、旧版本不更新、磁盘浪费
昼夜双模式地标夜间要发光,贴图通道和日间完全不同一套资源打天下,夜间效果糊掉

方案 ​

mermaid
flowchart LR
  A["Nova 云配<br>按城市发布精模包"] --> B["车端下载校验<br>MD5/AES 双 hash"]
  B --> C["本地库按 hash 存储<br>重复资源只装一次"]
  C --> D["屏幕中心点 1s 回调<br>换算经纬度 + cityCode"]
  D --> E["2km 矩形圈选<br>范围内模型 diff 下发"]
  E --> F["高德 SDK 三角化<br>BeginDraw/EndDraw 之间"]
  F --> G["Unity 浮动锚点换算<br>墨卡托 - 锚点"]
  G --> H["Napa5 渲染<br>昼夜双模式"]

核心思路是四件事各自闭环:资源侧用 hash 和版本号做幂等;圈选侧用 cityCode + 距离矩形做最小下发集;坐标侧把 SDK 转换严格钉在渲染帧边界内;Unity 侧用浮动锚点规避 float 精度问题。

关键实现 ​

1. 屏幕中心点:百度和高德两条路 ​

百度链路是 Unity → floorcenter → Android 回传;高德不透出这个值,只能在 GblMapDataHandler.FetchOnce 里通过 GetAmapCameraInfo 主动轮询 GAMapCameraInfo.targetX/targetY/targetZ。所以信号层(ldScreenCenterSignal)和 Unity 侧(unityModelStateFlow)都要按 SDK 来源分支:中心点 1 秒回调一次,只在 LD 状态下轮询,中心点没变就跳过后续查询。

2. 城市圈选与资源去重 ​

拿到中心点经纬度后:先换算 cityCode(跨城市边界要取多个城市),按 cityCode 查 Nova 索引拿城市精模列表;再以中心点画 2km(可配)矩形,用 isPointInRectangle 过滤出范围内模型;最后按 id + versionName 做 diff,只把增量下发给 Unity,避免每帧全量调用把接口打爆。

资源本体带 MD5(原始)和 AES(加密后)双 hash,本地按 hash 分目录存储——同一资源被多个发布单引用时只安装一次,代价是删除前要检查引用计数。

3. 三角化坐标:钉在渲染帧边界里 ​

高德提供两个关键 C++ 接口(IMapView):

  • ConvertCoordToAnchorPoint:把经纬度列表转成三角化坐标(渲染可用);
  • SetUnrenderedRegion:告诉底图哪些区域不渲染(精模盖住的地方底图让位,避免 z-fighting)。

硬约束:转换必须在 BeginDraw 和 EndDraw 之间完成,并跟随每帧数据传给 Unity。错过这个窗口,转出来的坐标对应的是上一帧的相机状态,精模就会和底图错位漂移。

4. 浮动锚点:厘米级精度的来源 ​

墨卡托 X/Y 范围约 ±2003 万米(上海地区约 (13523660, -3632940)),float 在这个量级精度只剩米级。解法是让锚点每帧浮动:

csharp
// 锚点 = 地图中心 XY + 车辆高度 Z,每帧重算
Vector3d anchor = new Vector3d(MapCenter.x, MapCenter.y, CarPos.z);
// Unity 坐标 = 墨卡托 - 锚点(double 减完再转 float)
float x = (float)(inX - anchor.x);

跟车状态下锚点就是车的位置,车永远在 Unity 原点,世界围着车转——视觉上「车在往前开」,实际上是建筑在相对后退。用户拖动地图时锚点切到屏幕中心,车偏离原点,松手后回正动画把 MapCenter 插值回车辆位置。所有物体坐标保持在 ±几百米内,厘米级精度就回来了。

5. 比例尺:一条对数公式管所有缩放 ​

mermaid
flowchart TD
  A["相机高度 cameraHeight"] --> B["FScaleLevel = 24 - log2(h / 35.6947)"]
  B --> C["FScale = 1 / 2^(24 - Level)"]
  C --> D["可视宽度 ≈ h × 0.669"]
  D --> E{"瓦片数 = ⌈宽度/512⌉²"}
  E -->|"≤ 4 个(2×2)"| F["Unity 3D 车道级"]
  E -->|"需要 9 个"| G["切 Android 2D 地图"]

基准值 minCenterDist = 35.6947m,由 2560×1440 分辨率、FOV_Y 37° 和校正系数 0.009330691929342804 推出,实测值与理论值零误差。关键级别:

Level相机高度FScale语义
2435.69m1.0最近视角,元素 1:1
2371.39m0.5默认车道级导航视角
191142.23m0.0314 瓦片上限(百度/高德单次最多发 4 个)
18.51615.36m0.022Editor 下切 2D 的临界点(Release 为 17.5)

瓦片限 4 个不是拍脑袋:单瓦片 21–62MB,4 个瓦片要在 1 秒内完成下载、解析、建网格、传 GPU,再叠加 4G 带宽和内存里 16 个瓦片(可见 4 + 预载 8–12)的占用,9 个瓦片车机吃不消,所以 Level 18.5 以下直接切轻量 2D。

6. 地标昼夜双模式 ​

夜间地标不是把日间贴图调暗,而是单独一套四通道资源:Diffuse+transparent(tga)、AO(tga)、Emit 自发光(exr)、Lightmap+0Env(exr,增加采样)。每个地标按「实现效果 = 实现方式 + 所需资源」登记到资源表,上传 Nova 后用 adb 暗号命令在车端实时调效果。

指标 ​

  • 首批 3 城 11 地标完成视觉设计与实现,持续细节优化。
  • minCenterDist 实测 35.6947m,对数缩放公式实测误差为 0。
  • Level 19 可视宽度 764.1m,2×2 瓦片覆盖率有余量;Level 18.5 需 9 瓦片,触发切换。
  • 圈选半径 2km 可配,中心点未变化时不重复查询;资源 diff 按 id + versionName 三向比对。

复盘 ​

  1. 第三方 SDK 接入,先把「它不给你什么」列清楚(屏幕中心点、帧边界约束),再设计自己的信号分支,比事后打补丁便宜得多。
  2. 浮点精度问题不要想着升级 double 硬扛,浮动锚点一次减法就把问题消掉了——车不动、世界动,视觉完全等效。
  3. 比例尺、瓦片数、切换阈值要从相机参数一路推到公式,实测值和理论值对得上,后面所有 LOD 和降级决策才有底气。
  4. 昼夜双模式这类视觉需求要落到「每个地标几张图、什么格式」的资源表上,否则联调阶段一定漏。

罗子初 · AI 座舱专家 · 8年端侧研发

Lv.1