CARLA & Unreal Engine: 0.9.15 vs 0.10, Part 1
CARLA 的传感器分三类,关键是它们的数据生成路径不同:
| 类别 | 传感器 | 数据来源 |
|---|---|---|
| 视觉类 | RGB 相机、语义分割(semantic segmentation)、深度相机 | 引擎渲染管线:GPU 光栅化出图 |
| 测距类 | LiDAR 点云、毫米波雷达 | 射线投射(raycast):对场景几何发光线,与渲染画质几乎无关 |
| 定位类 | GNSS(全球导航卫星系统)经纬度、IMU(惯性测量单元)姿态 | 仿真状态直接读出,数学变换 |
这个区分是本项目方法论的根基:换渲染引擎时,三类传感器受的影响完全不同。RGB 相机的每个像素都经过 UE 的光照、材质、后处理,UE4 换 UE5 等于整个输入分布搬家;而 LiDAR 打的是几何不是画面,GNSS/IMU 干脆不经过渲染。所以研究”渲染保真度的影响”时,主模型 SimLingo 选纯视觉(单前视 RGB + GNSS + IMU)是有意的——渲染变量不被几何传感器稀释。第二模型 AutoMoT 的动作分支吃 LiDAR BEV 特征,结论里必须注明这一点(它本身就是”渲染敏感度更低”的对照组)。
一个容易误解的点:语义分割图、深度图看着像”真值(ground truth)“,其实同样是引擎渲染出来的——分割图是给每个物体打上标签后渲染的纯色图,深度图来自深度缓冲。它们随引擎一起换,只是标签语义不变、像素级外观变。
一帧 RGB 画面从生成到模型手里要走:GPU 渲染到纹理 → 显存回读 → 序列化 → TCP 发送到客户端 → 反序列化成 numpy 数组。这条链路决定了吞吐上限(本项目实测 UE4 侧 110.1 帧/秒、UE5 侧 40.4 帧/秒,720p 零丢帧),也解释了为什么”渲染更真实”会直接变成”数据更慢”。
CARLA Leaderboard 系的驾驶分(Driving Score, DS)是完成度乘以罚分:
其中 是第 条路线的完成比例(route completion), 是罚分系数。每发生一次违章,就把对应的系数乘进去:
是第 类违章的罚分系数, 是该类违章次数。Leaderboard 默认系数量级:撞行人 0.50、撞车 0.60、撞静态物 0.65、闯红灯 0.30、闯停车标志 0.20。总分取所有路线 DS 的平均。
这个结构的后果:DS 对碰撞极度敏感——一次撞车就把该路线成绩打到六折,撞行人直接腰斩。所以 DS 的方差天然很大,单次评测的个位数分差通常在噪声带内(本项目实测 UE4 基线 DS 的跨 seed 噪声达 12.91 分),比较两个模型必须看噪声带,不能盯小数点。
Bench2Drive 的价值在可比性:SimLingo 论文口径 DS 85.07±0.95(Table 2,3 seeds),是榜单史上最高分。只有先在同一张考卷上把官方成绩复现到合理误差内,UE4 侧的分数才能当”公制刻度”用——基线不可信,后面的 UE4/UE5 对比就是空中楼阁。
CARLA 把仿真拆成两个独立进程:
CarlaUE4-Win64-Shipping.exe),负责渲染、物理、交通、传感器仿真。它独占 GPU,是最重的一端。中间走 RPC(远程过程调用),承载在 TCP 上。CARLA 默认一个 server 占三个连续端口:RPC 控制口、传感器数据流口、备用口;TrafficManager(交通流管理器)另占一个口(默认 8000)。
import carla
client = carla.Client(host_ip, 2021) # 2021 = 本项目 UE5 server 的 RPC 口
client.set_timeout(20.0) # 秒;超时不响应即抛异常
world = client.get_world() # 一句 RPC 拿回世界句柄
因为通信就是 TCP,客户端和服务端天然可以在不同机器、不同操作系统。本项目的形态:两台 server 都跑 Windows 原生(绕开 WSL2 的 Vulkan 渲染风险),所有 Python 跑在 WSL2(训练生态 flash-attn/triton 只在 Linux 正常)。WSL2 访问 Windows 宿主不能写死 localhost,要动态取宿主 IP:
ip route show | awk '/default/{print $3}' # WSL2 里取 Windows 宿主 IP
端口分配是踩过坑之后拍板的:UE5 用 2021/2022/2023,UE4 用 2031/2032/2033——最初的约定 2000–2002/2010–2012 被 Windows 的 winNAT 端口保留段(实测 1921–2020)吞掉,server bind 直接崩(WSAEACCES,退出码 0xe06d7363)。这个保留段会随大重启漂移,启动失败要先查 netsh int ipv4 show excludedportrange tcp。
模型与仿真器解耦:客户端拿到的是渲染好的图像字节流,模型完全不知道、也不需要知道画面是 UE4 还是 UE5 画的。正因如此,同一份 agent 代码可以不改一行就连两个引擎——这是”双仿真器并行”实验设计在架构上的可行性基础。
**异步模式(asynchronous)**是默认:server 按自己的节奏自由跑,客户端随时来取最新一帧。客户端慢了就丢帧——传感器回调拿到的永远是”当前”画面,中间帧直接丢弃。适合看演示,不适合做实验。
同步模式(synchronous)下 server 不再自由推进:它走一步,然后停下来等客户端确认,客户端处理完调一次 world.tick(),server 才走下一步。
settings = world.get_settings()
settings.synchronous_mode = True
settings.fixed_delta_seconds = 0.05 # 每个 tick 推进 0.05 秒仿真时间 = 20 Hz
world.apply_settings(settings)
while True:
frame = world.tick() # 握手:推进一步并阻塞到本帧数据到齐
control = model(image, gnss, imu) # 模型推理
vehicle.apply_control(control) # 下发下一步动作
闭环评测里,丢帧不是”少看几眼”而是动作-观测错位:模型基于第 帧算出的油门,被执行到第 帧的世界上,控制律整个失效,而且每次跑错位得不一样——结果不可复现。同步模式把这个耦合钉死:第 帧画面必然对应第 步世界状态,控制必然作用于第 步。数据严格对齐、不丢帧、可精确重放,这是实验科学的最低要求。
fixed_delta_seconds 决定仿真时间的粒度。本项目统一 0.05 秒/tick(20 Hz)且两侧同口径——它既是被控对象的采样周期,也是”实时倍率”的换算基准:同步模式下 server 每秒能推 个 tick,实时倍率就是 秒仿真时间/秒墙钟。实测 UE4 侧 97.6 tick/s ≈ 4.9× 实时,UE5 侧 42.3 tick/s ≈ 2.1× 实时。注意这个 FPS 是”世界推进速度”而非显示帧率,它直接决定评测和采数的墙钟成本。
渲染方程(rendering equation, Kajiya 1986)描述表面上一点 朝方向 的出射亮度:
难点在 :它不仅来自太阳、路灯这些光源,还来自其他表面反射过来的光——而 本身又满足同一个方程。这就是”光弹射”:阳光打到路面,弹起来照亮桥洞底。全局光照(Global Illumination, GI)就是要解这个递归方程的间接光照项。
实时代数解算不起,UE4 时代的主流做法是烘焙(lightmap baking):离线把间接光照预先算好,存成光照贴图,运行时直接查表。快,但有两个硬伤——光照必须是静态的(太阳不能动、车门开了光不会变),而且近似带来阴影偏硬、暗部死黑、色彩断层。
UE5 的 Lumen 是全动态实时 GI:不预计算,每帧现算光线弹射。工程上靠几招把成本压到实时:对场景维护符号距离场(Signed Distance Field, SDF)做软件光线求交(有 RT 硬件时可走硬件光追)、把表面辐射亮度缓存进 Surface Cache 复用、再用时域滤波(temporal filtering)降噪。效果上:阴影随光连续变化、暗部有反弹光、色彩有层次——这是 UE5 画面”真实感”的主要来源。
亮度分布和色彩统计整体搬家:UE4 烘焙画面的暗部直方图贴着零,Lumen 画面的暗部被反弹光填起来;同一位置不同时刻阴影方向长度都在变。对在 UE4 画面上训练的视觉模型,这等于输入特征的一阶、二阶统计量全部平移——域差距(domain gap)的第一大来源。
GPU 每帧能画的三角形有限,UE4 时代的解法是做 LOD(Level of Detail,细节层级):美术为同一个模型手工做高、中、低多档精度,引擎按距离切换。代价是双重的——美术成本,以及远处墙面发糊、轮廓”跳变”(popping,切档瞬间形状突变)这种穿帮。
Nanite 是虚拟化微多边形几何(virtualized micropolygon geometry),思路和虚拟纹理(virtual texturing)同构:纹理不需要整张驻留显存,按页调入;Nanite 把网格预先切成一簇一簇三角形簇(cluster,约 128 个三角形一簇),运行时按屏幕占比按需流式调入所需精度的簇,亿级三角形的原始资产直接用,不再需要手工 LOD。
每帧的关键步骤:
画面上:建筑立面有真实的雕刻起伏而非法线贴图的视差错觉,涂鸦、店招锐利可读,植被密度肉眼可见地增加。对视觉模型:纹理梯度更丰富、边缘特征更多——但都是训练分布里没见过的特征。卷积/ViT 特征提取器对高频纹理统计很敏感,这是域差距的第二大来源。
UE4 时代的物理引擎是 NVIDIA PhysX;UE5 换成 Epic 自研的 Chaos。对车辆仿真,物理引擎决定的是同一脚油门下去之后的一切:轮胎-地面摩擦模型、悬挂响应、质量分布如何转化为加速度和转向几何。两套引擎的求解器、积分步进、接触模型都不同,即使参数逐一对应,车辆动力学响应仍有细微差别——Bench2Drive 时代所有模型练出来的”手感”都是 PhysX 手感。
UE4 资产不能带进 UE5:Lumen 需要材质按新光照模型重做,Nanite 需要网格按虚拟化几何的格式重导。所以 CARLA 0.10 里的 Town10 不是 0.9 侧 Town10HD 的”搬运”,而是为 Lumen/Nanite 整个重做的重制版——路网有血缘、几何近似,但材质、光照、细节全是新的。
渲染(Lumen/Nanite)、物理(Chaos)、地图(重制资产)三层同时换,UE4/UE5 的差异是复合差异,不是单变量实验里那种干净的一个旋钮。本项目的口径声明由此而来:两侧差异保留渲染和物理两项,地图取语义对齐(同名路线、几何近似)而非逐像素对齐——后面所有结论都只能在这个口径下成立,任何”UE5 画面帮助/伤害了模型”的表述都默认包含物理与资产差异在内。
下表整理自本项目调研报告(docs/01-research-report.md §2.1,2026-08 核实):
| CARLA 0.9.x(UE 4.26) | CARLA 0.10.x(UE 5.5) | |
|---|---|---|
| 最新版 | 0.9.16(2025-09),活跃维护 | 0.10.0(2024-12),至今无 0.10.1,技术预览 |
| 地图 | Town01–15 全套 | 仅 Town10 重制版 + 一张矿场图 |
| 天气 | 全可调(域随机化必需) | 锁死白天 |
| 传感器 | 完整 | 主力已迁移,官方标注有黑屏 bug |
| ScenarioRunner / Leaderboard | 支持 | 完全不兼容(scenario_runner#1164) |
| Bench2Drive / 各 E2E 方案 | 全部支持(锁 0.9.15) | 无一支持 |
| 性能 | Epic 画质流畅(本机实测 97.6 FPS) | 官方内测峰值约 25 FPS(本机实测 42.3) |
| Python 包 | carla | carla-ue5-api |
carla(0.9.15,客户端 wheel 有 cp37/cp38/cp39/cp310)和 carla-ue5-api(0.10.0,PyPI 只有 manylinux 的 cp311/cp312)永远不能装在同一环境——这是本项目硬性规则第一条。两个包都注册 carla 这个 import 名,同装即互相覆盖。本项目的解法:按引擎拆 conda 环境(mysim-ue5 装 py3.11 + carla-ue5-api,mysim-simlingo 装 py3.10 + carla 0.9.15 + torch 2.7.1+cu128),环境拓扑即实验边界。
左列的每一项(地图、天气、生态)都是”成熟”的同义词;右列几乎每一项都是缺口。这个不对称正是研究设计的来源:0.9.15 负责提供可比的锚点(生态、考卷、官方分数),0.10 负责提供被研究的自变量(新渲染、新物理)。缺一列,实验都不成立。
从 0.9 到 0.10,“把现有方案迁过去”这条路被三道独立的坎堵死:
调研报告的原话结论是:“0.9 生态的方案不能平滑迁移到 0.10,不要对未来迁移做承诺”。
不做迁移,做并行:0.9.15 原地不动当基线(Bench2Drive 复现、官方分数锚点),0.10 另起炉灶(自建闭环基建)。两台 server 同机共存,客户端按引擎拆 conda 环境,两边跑同一个模型、同语义路线、同一套评分。
这正好把生态断层翻转成对照结构:
能冻结的是模型权重、路线语义、评分公式;冻不住的是地图逐像素对齐(Town10 两侧是重制关系,只能语义对齐)和物理手感。所以本项目的对比结论永远带一个口径声明:差异 = 渲染 + 物理 + 重制资产的复合体。这不是缺陷而是事实——真实世界里”引擎升级”本来就是这三件事一起发生的。