我在 2017 年参与了这个项目,是我第一次接触 3D 和 VR/AR 相关技术,VR/AR 技术正处于 Gartner 曲线中泡沫破裂前夕,虽然我主要负责网络通信部分,但是在我心目中埋下了一颗种子,所以写下此文回溯其中部分内容,以供借鉴。

VR

整体架构

3D Device Train

工业装备都是比较大型的,由多个机械部件构成,操作和维修都需要相关知识和对其结构的了解,所以通过将工业装备构建成 3D 模型,培训讲师可以通过平板作为主控端,在其上操作 3D 模型并讲解,然后培训学员可以通过 PC 大屏、其他平板、以及 HoloLens 作为受控端,同步观看 3D 模型相关操作。

上图中的每一个组成部分,会分别讲解一下其中的关键技术要点和相关参考资料。

CMS

这部分比较简单,就是有一个后台管理系统可以上传针对某一个客户的培训素材资源,主要是 3D 场景、图片和视频,以及给客户端提供培训素材资源下载的接口。

客户端

客户端主要功能之一就是从 CMS 下载 3D 场景、图片和视频,然后在客户端展示,图片和视频没什么可说的,所以主要讲解 3D 场景的渲染,我们采用的是 Unity 引擎进行渲染,Unity 引擎中有几种不同渲染管线(Render Pipeline),比如内置渲染管线、URP、HDRP,渲染管线你可以理解为要将一个 3D 场景显示到屏幕上所需要经过的一系列操作步骤,所以一个 3D 场景在客户端上能通过 Unity 正确地渲染,前置条件是在 PC 上通过 Unity 编辑器将原始 3D 模型通过选择的渲染管线进行了转换,然后再将 3D 场景导出为文件,再上传到 CMS,我们所创建的客户端 Unity 工程也要选择对应的渲染管线,才能在客户端上正确渲染。

Unity 加载 3D 场景的方法:

public class SceneTest : MonoBehaviour
{
    void Start()
    {
        SceneManager.LoadScene(1);
        SceneManager.LoadScene("New Scene");
    }

    IEnumerator ChangeScene(int sceneIndex)
    {
        AsyncOperation operation = SceneManager.LoadSceneAsync(sceneIndex, LoadSceneMode.Additive);
        yield return operation;
    }
}

iOS 客户端,首先创建 Unity 工程,然后导出 iOS 工程,再用 Xcode 打开此工程,可以直接运行,此 iOS 工程已有 Unity 引擎在 iPhone 手机上运行的静态库,好几十兆,Unity 渲染输出是封装在一个 ViewController 中,把它作为 RootViewController 的子 ViewController,就可以在其上面用 iOS 添加 iOS 视图和逻辑处理代码,iOS 从 CMS 下载 3D 场景到沙箱,告知 Unity 引擎此 3D 场景的文件地址并加载渲染,iOS 是使用 Objective-C 编程语言,Unity 是使用 C# 编程语言,所以这里就涉及到 iOS 和 Unity 互相调用,可参考如下代码,更多细节可以自行搜索:

Unity 调用 iOS,首先需要在 Unity 中定义相应的方法如下面的FooPluginFunction,然后在 iOS 中通过 C 语言来实现此方法:

extern "C" {
    void FooPluginFunction(char* str) {
        NSString *text = [[NSString alloc] initWithCString:str encoding:NSUTF8StringEncoding];
        [[NSNotificationCenter defaultCenter] postNotificationName:FooViewControllerNotification object:nil userInfo:@{FooViewControllerUserInfo: text}];
    }
}

iOS 调用 Unity,首先 Unity 中有定义对应某 GameObject 的某方法:

UnitySendMessage("Unity", "MethodName", text.UTF8String);

Android 客户端,一样的套路,首先创建 Unity 工程,然后导出 Android 工程,再用 Android Studio 打开此工程,可以直接运行,此 Android 工程已有 Unity 引擎在 Android 手机上运行的 JAR 包,好几十兆,Unity 渲染输出是封装在一个视图中,不是 Activity,在一个 Activity 中把这个视图添加进去,就可以在其上面添加其他 Android 视图和逻辑处理代码,Android 从 CMS 下载 3D 场景到沙箱,告知 Unity 引擎此 3D 场景的文件地址并加载渲染,Android 是使用 Java 编程语言,Unity 是使用 C# 编程语言,所以这里就涉及到 Android 和 Unity 互相调用,也是可以的,请自行搜索。

PC 大屏,运行的是 Windows,还是一样创建 Unity 工程,直接导出可执行的 exe 程序,所以 3D 场景下载和加载都是 Unity 完成的,用 C# 写就好了,Windows 也可以用 C# 写,所以挺合适的。

HoloLens MR 一体机,运行的是 Windows Core OS,还是一样创建 Unity 工程,然后装一堆 HoloLens 开发需要的插件,直接导出可执行的程序,同样使用 C# 实现 3D 场景下载和加载。

网络通信

3D Device Train

如前所述要实现客户端之间同步 3D 场景的操作,就需要组网通信,这里面分两个场景来看。

首先,每一个客户端都是 MQTT 客户端,启动时就同 MQTT 服务端建立连接,每一个客户端都会在本地记录其他 MQTT 客户端,有新的 MQTT 客户端加入,MQTT 服务端会发广播,当然刚开始需要从业务服务器拿历史 MQTT 客户端列表。

其次,在一个局域网内,如图中 iPad 和 PC 都在路由器 A 中,安卓平板和 HoloLens 都在路由器 B 中,我们可以让每一个客户端都是发送 UDP 广播,在同一个局域网内的其他客户端就会收到这个 UDP 广播,从而就可以在本地记录同一个局域网内的其他客户端的 IP 地址和 TCP 端口号。

到这里,在每一个客户端上,我们就有了其他客户端的连接方式 MQTT 或者 TCP。

这里先说一下,在 Unity 操作 3D 场景,主要就是两种,一种是播放帧动画,就是已经在 Unity 编辑器或者 3D 建模软件内置好的动画,通过代码调用播放动画,比如打开车门,所以只需要把播放什么动画的命令传递到其他客户端就可以了,另一种就是直接操作 3D 场景,你应该知道无非就是移动、旋转、缩放,这时候就需要把其变化的世界坐标,不断地传递到其他客户端。

比如在 iPad 发起飞屏,可以选择单次连接、连续单向、连续多向。

单次连接,就是通过 UDP 发送一次命令,只适合于播放帧动画。

连续单向,就是 iPad 作为主控端,其他客户端都是受控端,如果有其他客户端可通过 TCP 连接,那就建立 TCP 连接,主控端操作 3D 场景,通过 MQTT 或 TCP 同步命令到其他客户端,其他客户端不能操作 3D 场景,只能看。

连续多向,就是 iPad 作为主控端,其他客户端都是受控端,主控端和受控端都可以操作 3D 场景,比较容易引发混乱。

客户端开源网络库:

  • CocoaAsyncSocket
  • MQTT-Client-Framework
  • Apache MINA
  • Eclipse Paho

尾声

文章中虽然忽略了很多实现的细节,但是也阐述清楚了主体思路,最后,针对项目中一些问题,阐述一下可尝试的优化方向。

1. 提供本地 TCP 服务器

每个客户端上开启 TCP 端口监听是相当不稳当,因为用户随时可能切到其他应用上,导致 TCP 连接断掉,编码时需要考虑很多异常流程,因为用户基本上都是在一个固定室内进行培训,提供 TCP 服务器或者 TCP 服务程序安装到用户固定在室内的电脑上是一个可行的方案,这样每个客户端都充当 TCP 客户端,这样会简化很多编码工作,同时可靠性要高很多。 想更深入一步,可以搜索边缘计算。

2. 直播客户端的 3D 场景操作

前面有说连续单向,就是一个客户端作为主控端,其他客户端都是受控端,通过网络连接同步命令到其他客户端,再复原 3D 场景的操作,且只能看,当网络不稳定时,会导致同步命令的到达不是均衡的,就会导致用户看到 3D 场景的操作有时很慢有时很快,可以采取在主控端录屏推流到流媒体服务器,然后其他客户端再拉流,可以使用户看到 3D 场景的操作更流畅,缺点就是无法实现多方操作 3D 场景。

想更深入一步,可以搜索云游戏。

3. 客户端都用 Unity 开发

如果手机应用不是在已有原生技术开发的应用上添加 3D 场景的功能,完全可以使用 Untiy 开发多个客户端,减少技术栈,复用代码,减少开发成本。

想更深入一步,可以搜索 Unity。

4. 主控端的转换

前面说了飞屏,在连续单向时,主控端操作 3D 场景,其他客户端只能看,连续多向,主控端和受控端都可以操作 3D 场景,比较容易引发混乱,更好的一种方式是同一时间,只有一个客户端是主控端,可以操作 3D 场景,且可以将主控权转换给其他客户端,解决前面的问题。