小怿学习日记
当前车载HMI由多个功能模块组成,如车控桌面、车辆设置、行泊车和地图导航等,这些模块都有自车模型显示。当不同模块之间切换时,直接画面切换会有跳跃感。
若能像影视作品中,镜头一直聚焦跟随着角色从一个场景到另一个场景,期间不做任何镜头画面切换,让自车模型部分画面保持一个连续变化,这就是一镜到底。一镜到底的切换方式,让车载HMI各个功能模块更像一个整体,交互更流畅,提升用户使用体验。
01
功能需求
一镜到底功能核心需求就是给功能模块切换时添加转场动画,并保证自车模型画面能衔接上。
车控桌面切换到行泊车,效果设计如下:
车控桌面切换地图、地图切换到设置,效果设计如下:
其它场景切换效果类似,保证自车模型画面变化连续不跳跃,场景元素过渡变化。
02
功能模块
常用功能模块关系如下:
作者参与的多款车型的智驾软件开发中,3D部分的场景都是Unity开发的,车控的桌面和车辆设置为一个Unity工程,智驾的行车和泊车为另一个Unity工程,由不同团队开发。最新版本的HMI将行车与泊车合并成一个场景,行泊车一体了,通过信号状态进行切换。
两个APK各自都包含Unity引擎,这意味着运行时会有两份引擎自身的开销。从性能开销的角度看,最完美的做法时将4个3D场景都做到一个Unity工程中,通过AIDL通信,将不包含Unity引擎的APK中的SurfaceView传递给包含引擎的APK进行画面渲染,这样可以大幅降低运行时的性能开销。
从场景应用角度看,车控的3D关注自车,还有不少特写镜头,对模型面数和画面质感要求高,可以选用画面效果更好的UE引擎。智驾的3D关注驾驶环境,一般都是远镜头,对模型要求较低,可以选用运行资源开销更少的COCOS引擎。只是不同引擎,数据通信的实现方式上会有差别。
03
转场类型
根据切换的功能模块场景归属关系不同,有不同的切换方式。
3.1 相同3D场景
3D场景画面是通过摄像机渲染出来的,同场景下只需要做个摄像机动画。如行车与泊车切换,设置内关注不同功能区域等。需要转场时,Android通知Unity即可,两者之通过JNI进行通信。
3.2 不同3D场景但相同APK
从Android原生的角度看,Unity的画面是输出到SurfaceView上的。Android层可以创建SurfaceView通过UnityPlayer.displayChanged(displayId, surface)接口传入给Unity渲染,Unity内部的Camera组件设置好对应的TargetDisplay即可。
转场时,将旧画面的SurfaceView叠在新Surface上,同步完镜头参数后按特定时序播放镜头动画,播放到某个时刻旧SurfaceView半透明淡出即刻。
3.3 不同APK
与上一种情况相比,多了一个进程之间的通信和目标apk首次启动拉起的过程等待问题。进程之间通信有不可避免的延迟,所以这类转场往往时其中一方APK做完整的转场动画,另一方直接硬切画面,或者通过AIDL将SurfaceView传递给另一个APK进行目标画面渲染,做SurfaceView渐变过渡。
04
转场流程
完整的转场逻辑如下
拉起目标场景:创建目标场景渲染用的surfaceview并传入3D引擎,或者启动目标场景所在APK,等待画面渲染完成通知,确保目标场景画面可以展示给用户了。
锁定视角同步相机参数:部分场景(如行泊车)的视角会随着信号变化,在转场动画过程中,需要暂时缓存信号值,待转场动画结束后再更到最新视角。摄像机动画需要起点和终点的参数,需要把参数值都同步给对方,不做动画的场景可以不需要。
播放前半段动画:旧场开始摄像机动画,并非所有转场都有。
播放前后段动画:新场开始摄像机动画,并非所有转场都有。
画面切换:旧场景画面的SurfaceView隐藏,新的SurfaceView显示给用户,同APK的可以做渐变过渡,不同APK的一般直接硬切。
转场完成处理:转场完成后对旧场景做一些清理,新场景解除视角锁定,按最新信号值刷新。
在项目实际生产过程中,发现转场动画全部由一边做效果最好,因为各做一半动画,在镜头运动过程中切换画面很容易出现停顿或跳跃等视觉效果不平滑的情况。
以前面展示的两个效果图为例说明:
车控桌面转智驾行车:
启动智驾apk但不在屏幕上显示给用户,待Unity初始化完毕,通知切换到行车模式
通知智驾锁定视角,并回传摄像机参数
车控收到智驾摄像机参数后,开始播放动画
动画播放完毕,隐藏车控画面,显示智驾画面
智驾解除视角锁定,若转场过程中有新的视角信号,则再次切换到新视角
车控桌面转地图导航:
地图应用是2D的,视角参数固定,切换过去前已经处于运行导航状态了
车控创建临时Surface传递给地图进程绘制地图内容,叠在车控桌面上层,但Alpha为0
车控桌面播放摄像机动画,开始转场
车控桌面动画快完毕时,绘制地图内容的临时Surface将Alpha渐变到1
动画结束,隐藏车控画面,显示地图画面
地图停止对传入的Surface渲染和解除引用,车控清理临时Surface
05
动画服务
实际车载HMI中,应用场景不止前面说的几个。若每种可互相切换的场景都建立通信和单独流程控制,那数量将会快速膨胀,不利于开发和维护。我们的项目中采用了动画服务方案,所有进程只与动画服务建立通信,不同3D场景的切换统一由动画服务控制和数据通信。
在动画服务中,每个3D场景都分配一个场景ID,需要切换的过程都分配一个动画ID,A->B与B->A对应不同动画ID。当需要转场时,通过向动画服务发起转场请求,并传递动画ID。
把转场的每个步骤定义成动画帧(跟渲染帧不是一个概念),通过向涉及转场的场景发送帧序号和数据,收到完成确认后继续下一帧的方式,实现整个动画流程的控制。各功能模块在接收到动画发服务发送的动画ID和和帧序号时,进行相应动作,完成后向动画服务回复完成确认,并通过确认接口传递需要的数据。
仍以车控桌面转智驾行车为例,进行说明:
|
动画服务 |
车控桌面 |
智驾行车 |
|
—— |
发起转场请求 |
—— |
|
拉起智驾 |
—— |
—— |
|
发送动画帧0 |
无操作,回复确认 |
锁定视角,回复确认和摄像机参数 |
|
发送动画帧1 |
用接收到的摄像机参收做动画 动画结束后回复确认 |
无操作,回复确认 |
|
切换车控与智驾显示 发送动画帧2 |
无操作,回复确认 |
解除视角锁定,回复确认 若锁定期间有新视角信号切换到新视角 |
|
转场完毕 |
—— |
—— |
反过来智驾行车转车控桌面时,转场动画依然由车控这边进行,动画行为如下:
|
动画服务 |
车控桌面 |
智驾行车 |
|
—— |
—— |
发起转场请求 |
|
拉起车控桌面 |
—— |
—— |
|
发送动画帧0 |
无操作,回复确认 |
锁定视角,回复确认和摄像机参数 |
|
发送动画帧1 |
用将收到的参数设置摄像机作为动画起点 待新画面输出后回复确认 |
无操作,回复确认 |
|
切换智驾与车控显示 发送动画帧2 |
播放摄像机动画回到车控默认视角 动画结束后回复确认 |
解除视角锁定,回复确认 |
|
转场完毕 |
—— |
—— |
Unity是按帧率周期输出画面的,在设置摄像机参数后,需要等画面输出后才能将SurfaceView显示给用户,否则会出现闪黑(新创建)或旧画面(重复使用)的情况。实际开发中发现,新创建的SurfaceView要200ms后才能稳定保证Unity输出的画面上屏,重复使用的SurfaceView则只需要等一帧的时间即可。
本篇简要介绍一镜到底的操作方法,如果大家对这方面内容感兴趣,想与我们探讨交流,欢迎随时联系我们(mkt@eptcom.com)。
END
往期 · 推荐

