引言

你是否注意到,这些年我们越来越少在演讲中看到演讲人用激光笔给观众指示所讲的内容。先说激光笔,激光笔的工作原理是射出一束激光,照射到幕布上并反射到观众的眼睛里,于是大家可以看到一个很亮的红色光点。但现在因为大尺寸屏幕越来越便宜,我们越来越少使用幕布这种传统投影显示设备了,毕竟屏幕的显示效果要更好。而屏幕为了保证良好的显示效果,往往都会在表面的玻璃上使用大量抗反射技术。这些抗反射技术的运用大大减弱了激光的反射,所以最终用户看到的红色光点就不那么显眼了,激光笔的效果大打折扣。

那么有没有可能用大家出门唯一愿意携带的手机来替代激光笔呢?我尝试了下面两种方案。

3D 模拟方案

简单来说就是用手机内置的姿态传感器和加速度传感器来构建出手机和显示屏在三维空间中的方位和姿态,再以此模拟计算如果从手机发出一束激光,会照射到屏幕上的哪个位置,并在屏幕上的相应位置绘制一个红点,这样来实现手机模拟激光笔的效果。

如上图所示,假设一块屏幕的两条邻边分别平行与 X 轴和 Z 轴。当手机从 A 点沿着屏幕的一条边移动到 B 点,再从 B 点沿着屏幕的另一条边移动到 C 点,我们就可以计算出屏幕在以 A 点为原点的三维空间中的具体位置。

计算方法其实很简单,比如当我们计算从 A 到 B 的过程时,只需要用手机上的加速度传感器取出时时刻刻手机在 x 轴方向上的加速度,再乘以时间,就可以得到手机时时刻刻在 x 轴方向的速度,速度再乘以时间就可以得出手机在 x 轴方向移动了多少距离。简单说就是对手机在 x 轴方向的加速度做了两次时间维度上的积分。

能够通过把手机从 A 点移动到 B 点和 C 点来计算出屏幕的位置,自然也可以在接下来计算出任意时刻手机相对屏幕的空间位置和姿态方向,并模拟计算从手机射出一束激光会照射到屏幕的哪个位置。

可是,实验过后我发现这个方案虽然在理论上是可行的,但因为累积误差的存在,实际并不可行。

我们拿手机从 A 点移动到 B 点这个最基础的场景来举例。当一个人拿着手机从 A 移动到 B 的时候,手机的加速度、速度和移动距离可以用下面三幅图来描述。

横轴 t 表示时间,纵轴 a、v、d 分别表示手机在 x 方向的加速度、速度和移动距离。

第一幅图中,加速度前半程是正的,后半程是负的,所以手机在 x 方向上先加速后减速,速度从 0 增长到最大,后又慢慢减为 0,而移动距离一开始因为速度比较慢,所以增长慢,中间速度达到最大值,移动距离也增长得最快,最后速度归 0,移动距离也不在增长。

可问题就出在加速度上。本来加速度正的部分的积分和负的部分的积分,也就是蓝色区域的面积和黄色区域的面积,是完全一致的,这样当运动过程结束时手机的速度就会恢复为 0,但实际情况并非如此。

首先,真实世界的物理量是连续的,加速度当然也是连续的,但在手机上通过操作系统提供的各种接口取到的加速度是不可能连续的,而是每 16ms 才能取一个值。于是在计算蓝色和黄色区域的面积的时候,只能通过计算若干个矩形的面积并求和来得到一个近似值。

其次,手机操作系统通过接口提供的加速度和真实的加速度必然是有差别的,任何测量都有误差,只有真实的值才会使得蓝色和黄色区域的面积完全相同。

所以,由于上述的两个原因,在手机从 A 点移动到 B 点后,我们经过测量加速度和计算速度,最后会发现手机到达 B 点后速度依然没有归 0,而速度没有归 0 意味着系统以为手机依然在匀速沿 x 轴移动,最终使得整个系统都不可用。

3D 模拟方案的根本问题在于我们需要对一个存在误差的量做时间上的积分,我们都知道误差并不可怕,几乎所有的测量量都有误差,比较可怕的是累积误差,它会让你的系统运行一段时间后完全失控,而 3D 模拟方案更糟糕的地方在于这个误差是在时间维度上累积的,而时间不会停止。

姿态模拟方案

在 3D 模拟方案失败后,我又想到一个简化版的方案,既然加速度因为有误差而无法使用,那能不能考虑简单一点,只用没有累积误差的方向传感器来实现控制呢?

假设手机的初始位置是正对屏幕的正中心,那么只要知道手机到屏幕的距离,以及手机绕 x 轴旋转的角度 beta,就可以算出红色光电会向上移动多少距离了。但我们没有距离传感器,前面也说了不能用加速度来计算距离,那我们干脆再进一步简化,不考虑距离,只考虑角度。

我们认为设定手机手机初始位置正对屏幕正中,绕 x 轴向上旋转 25 度时,红点从屏幕正中移动到屏幕顶部,绕 x 轴向下旋转 25 度时,红点移动到屏幕底部。手机绕 z 轴左右旋转 25 度时,红点分别移动到屏幕的最左边和最右边。这样,我们就可以只用一个简单的姿态传感器实现模拟激光笔的效果了。

实验表明,这种方案完全可行,而且代码逻辑简单。当然,这种方式假定了手机的初始姿态,其实也假定了手机到屏幕的距离(可以思考一下为啥也假定了手机到屏幕的距离^_^),用户在使用的时候可能会稍微感到有一点点不自然。同时,为了更好更方便的控制体验,还需要加一些让用户重置手机姿态的功能。

Demo

本文中所讲的姿态模拟方案,大家可以在 Laser Pen Demo 体验,相关代码在 Laser Pen - Github 的 example 目录下。 在 pc 浏览器中打开 demo 页面后稍等几秒,你应该会看到页面的头部生成了一个二维码。

当手机上的控制页面显示「Connected」的时候,你就可以尝试挥动手机来控制 pc 页面上的小红点啦。

关于这个 demo 还有两点说明。

首先,demo 中获取的手机姿态数据是通过 WebRTC 技术直接发到 pc 页面的,所以你完全不用担心有个人敏感信息泄露的问题。

其次,demo 的主要通信手段虽然是 WebRTC,但 WebRTC 链接建立的过程是需要一些消息推送手段的,所以 demo 也用到了 socket.io

最后,demo 用了一个免费的后端服务,这个服务一段时间没有人访问后会自动停止,下次再访问就得等将近两分钟服务才能启起来。所以如果你打开 pc 页面后发现二维码没有显示出来,可以先去喝杯水,再回来刷新一下页面试试。

最后

欢迎大家 star、pr、issue 我的项目 Laser Pen - Github