Cardboard Talk01 HeadTracker
操作系统:Windows8.1
显卡:Nivida GTX965M
开发工具:Android studio 3.0.0 | Cardboard 1.0
使用 Google 的 Cardboard开发VR应用,会用到其中的几个功能,其中一个便是头部跟踪,即HeadTracker。接下来几个章节将会逐一分析 Cardboard 头部跟踪的具体设计和实现。考虑新版本的SDK已经不再提供源代码的支持,故采用比较老的版本对源代码进行分析说明。
Overview Of HeadTracker
有关头部跟踪的代码均在 com.google.vrtoolkit.cardboard.sensors 包下。SDK通过 HeadTracker 对外提供服务,该类内部通过 DeviceSensorLooper 完成对Android系统的 SensorManager 的绑定、监听工作。通过 OrientationEKF 完成旋转角度的转换工作,其中涉及到应用扩展卡尔曼滤波及罗德里格旋转公式,该部分会在后面章节用到的时候逐一介绍。首先通过一张图,看一下 HeadTracker 与 DeviceSensorLooper 的关系结构。
从类结构来看 HeadTracker 实现了Android系统的 SensorEventListener接口,该接口提供了必要的功能定义,用于监听系统对应的传感器的回调。换句话说 HeadTracker 将会作为监听器间接的或者直接的用以注册到系统的 SensorManager中,获取源源不断的传感器数据。下面分别介绍几个主要的 PublicAPI 。
1. createFromContext(Context) 根据传递的Context创建 HeadTracker Instance:
public static HeadTracker createFromContext(final Context context) {
final SensorManager sensorManager = (SensorManager)context.getSystemService("sensor");
final Display display = ((WindowManager)context.getSystemService("window")).getDefaultDisplay();
return new HeadTracker(new DeviceSensorLooper(sensorManager), new SystemClock(), display);
}
该函数获取系统的 SensorManager,如之前所述用于注册监听器获取传感器数据。接下来通过 WindowManager 获取默认的 Display对象,用于判断当前设备屏幕的旋转朝向。接着创建 DeviceSensorLooper 作为一个中间层内部启用了一个线程维护 SensorEventListener与 SensorManager的交互操作及外部控制逻辑,并将之前获取的 sesorManager 作为构造参数使用。最后实例化的是 SystomClock,该实例用于获得系统精确的 nano time 参与计算旋转角度。
2. onSensorChanged(SensorEvent) 将获取的传感器数据缓存,并传递给 OrientationEKF 进行处理以获得更准确的旋转数据。值得注意的是头部跟踪相关的传感器类型为应加速度计 Acc 和陀螺仪 Gyro。
public void onSensorChanged(final SensorEvent event) {
if (event.sensor.getType() == 1) {
this.mLatestAcc.set(event.values[0], event.values[1], event.values[2]);
this.mTracker.processAcc(this.mLatestAcc, event.timestamp);
}
else if (event.sensor.getType() == 4) {
this.mLatestGyroEventClockTimeNs = this.mClock.nanoTime();
this.mLatestGyro.set(event.values[0], event.values[1], event.values[2]);
Vector3d.sub(this.mLatestGyro, this.mGyroBias, this.mLatestGyro);
this.mTracker.processGyro(this.mLatestGyro, event.timestamp);
}
}
3. startTracking() 开启头部跟踪功能。
public void startTracking() {
if (this.mTracking) {
return;
}
this.mTracker.reset();
this.mSensorEventProvider.registerListener((SensorEventListener)this);
this.mSensorEventProvider.start();
this.mTracking = true;
}
重置 OrientationEKF 对象,向 SensorEventProvider 接口的实现类 DeviceSensorLooper 对象注册 HeadTracker 实例本身。除此之外开启 DeviceSensorLooper 线程,最后设置上下文 mIsRunning 为 true 。
4. stopTracking() 关闭头部跟踪功能。
@Override
public void stop() {
if (!this.mIsRunning) {
return;
}
this.mSensorManager.unregisterListener(this.mSensorEventListener);
this.mSensorEventListener = null;
this.mSensorLooper.quit();
this.mSensorLooper = null;
this.mIsRunning = false;
}
从传感器管理器移除所有的监听器,停止监听。并结束传感器线程,设置上下文 mIsRunning 为 false。
5. setGyroBias(float[]) 设置陀螺仪Gyro的偏好数据,对于头部跟踪的最小原型,该函数是非必要的。
public void setGyroBias(final float[] gyroBias) {
if (gyroBias == null) {
this.mGyroBias.setZero();
return;
}
if (gyroBias.length != 3) {
throw new IllegalArgumentException("Gyro bias should be an array of 3 values");
}
this.mGyroBias.set(gyroBias[0], gyroBias[1], gyroBias[2]);
}
6. setNeckModelEnabled(boolean) 开启颈部模型偏移数据。
public void setNeckModelEnabled(final boolean enabled) {
this.mNeckModelEnabled = enabled;
}
关于该函数没什么好说的,该功能默认是关闭的,如果开启需要留意类中定义了颈部模型的offset补偿的参数:
private static final float DEFAULT_NECK_HORIZONTAL_OFFSET = 0.08f;
private static final float DEFAULT_NECK_VERTICAL_OFFSET = 0.075f;
private static final boolean DEFAULT_NECK_MODEL_ENABLED = false;
Details of DeviceSensorLooper
在介绍 getLastHeadView(float[], int) 函数之前,先看一下 DeviceSensorLooper 的设计与实现,以便更好的理解后续的逻辑。该类的初始化阶段定义了感兴趣的传感器类型:
static {
INPUT_SENSORS = new int[] { 1, 4 };
}
1. constructor(SensorManager) 现在看一下构造函数的定义实现:保存 HeadTracker 传递进来的系统 SensorManager 实例引用,用于注册监听器所使用。
public DeviceSensorLooper(final SensorManager sensorManager) {
super();
this.mRegisteredListeners = new ArrayList<SensorEventListener>();
this.mSensorManager = sensorManager;
}
2. start() 开启独立工作线程,监听传感器传递的数据,并向注册的监听器回调获取到的数据,可以看到在内部类中引用了监听器集合 mRegisteredListeners,会遍历所有的监听器分发数据。
@Override
public void start() {
if (this.mIsRunning) {
return;
}
this.mSensorEventListener = (SensorEventListener)new SensorEventListener() {
public void onSensorChanged(final SensorEvent event) {
for (final SensorEventListener listener : DeviceSensorLooper.this.mRegisteredListeners) {
synchronized (listener) {
listener.onSensorChanged(event);
}
}
} public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
for (final SensorEventListener listener : DeviceSensorLooper.this.mRegisteredListeners) {
synchronized (listener) {
listener.onAccuracyChanged(sensor, accuracy);
}
}
}
};
final HandlerThread sensorThread = new HandlerThread("sensor") {
protected void onLooperPrepared() {
final Handler handler = new Handler(Looper.myLooper());
for (final int sensorType : DeviceSensorLooper.INPUT_SENSORS) {
final Sensor sensor = DeviceSensorLooper.this.mSensorManager.getDefaultSensor(sensorType);
DeviceSensorLooper.this.mSensorManager.registerListener(DeviceSensorLooper.this.mSensorEventListener, sensor, 0, handler);
}
}
};
sensorThread.start();
this.mSensorLooper = sensorThread.getLooper();
this.mIsRunning = true;
}
函数的后半部分,会实例化线程的Handler,并在Handler初始化的时候获取感兴趣的传感器对象,并最终注册代理 SensorEventListener 对象。完成后启动线程,并修改标志位为已运行状态。
2. stop() 停传感器工作线程,移除代理中间监听器对象,退出线程并修改标志位为停止运行状态。
@Override
public void stop() {
if (!this.mIsRunning) {
return;
}
this.mSensorManager.unregisterListener(this.mSensorEventListener);
this.mSensorEventListener = null;
this.mSensorLooper.quit();
this.mSensorLooper = null;
this.mIsRunning = false;
}
3. registerListener(SensorEventListener) 向外部提供接口,用以注册外部Sensor监听器对象。
@Override
public void registerListener(final SensorEventListener listener) {
synchronized (this.mRegisteredListeners) {
this.mRegisteredListeners.add(listener);
}
}
4. unregisterListener(SensorEventListener) 向外部提供接口,用以注销外部Sensor监听器对象。
@Override
public void unregisterListener(final SensorEventListener listener) {
synchronized (this.mRegisteredListeners) {
this.mRegisteredListeners.remove(listener);
}
}
Brief summary
Cardboard SDK 为获取系统的传感器数据,定义了 DeviceSensorLooper 对象维护系统内部 SensorManager 与外部 SensorEventListener 监听器的联系,其内部开启了一个独立的worker工作线程专门负责传感器数据的获取与分发。而 HeadTracker 通过注册自身到 DeviceSensorLooper 中最终获得了有效的传感器原始数据,Acc 与 Gyro。
目前位置,拿到传感器原始数据后,如何计算精准的旋转矩阵并未提及。该部分将会在接下来的章节逐一讨论。
Cardboard Talk01 HeadTracker的更多相关文章
- Cardboard开发教程:使用Unity制作Cardboard全景图片浏览器
这两年,虚拟现实(VR)领域很火,很多人认为这将会是下一个手机般改变人们生活的技术.目前全球最领先的还是Facebook旗下的Oculus,HTC VIVE,以及最流行的Cardboard.国内多家厂 ...
- google vr开源 cardboard
https://developers.google.com/cardboard/android/ 待续
- 承接cardboard外包,unity3d外包(北京动软— 谷歌CARDBOARD真强大)
手把手教你玩转googlecardboard[不知道在这里发可以不?] 谷歌Google I/O开发者大会于北京时间6月26日0点在美国旧金山举行,谷歌发布了Android L手机系统:Android ...
- 【Cardboard】 体验 - Google Cardboard DIY及完成后简单体验
体验 - Google Cardboard DIY及完成后简单体验 今年的Google I/O最让我感兴趣的除了Material Design以外就是这个Google Cardboard了.据说是Go ...
- Google Cardboard
Google Cardboard是谷歌的一个虚拟现实开源项目,旨在使用户可以以一种简单.有趣且廉价的方式体验虚拟现实.用户只需要在Android手机上安装一个Google Cardboard应用,并将 ...
- 纸板上的虚拟现实和代码中的Cardboard
虚拟现实技术 未来视角? Google Cardboard试玩与比較 阅读下面文字请请先戳 戳我戳我 2014年的Google I/O大会,一向以Geek自称的Google拿出了一个叫做Cardboa ...
- 基于Daydream technical preview GVR13开发Daydream,Cardboard的Android应用
本文用Unity的Daydream Preview GVR13版本开发同时兼容Daydream和Cardboard的Android应用,Android Studio版本为2.2.3. 下载最新Dayd ...
- Unity For Android Cardboard App ( 1 ):基础入门
作者: ericzwhuang 前言 目前Google官方推出的VR设备有DayDream(2016年推出)和Cardboard(2014年推出)两种. Daydream是消费级VR解决方案,提供了手 ...
- Google Cardboard的九轴融合算法——基于李群的扩展卡尔曼滤波
Google Cardboard的九轴融合算法 --基于李群的扩展卡尔曼滤波 极品巧克力 前言 九轴融合算法是指通过融合IMU中的加速度计(三轴).陀螺仪(三轴).磁场计(三轴),来获取物体姿态的方法 ...
随机推荐
- 208. Implement Trie (Prefix Tree) -- 键树
Implement a trie with insert, search, and startsWith methods. Note:You may assume that all inputs ar ...
- OC 数据持久化(数据本地化)- 本地存储
// // ViewController.m // IOS_0113_本地存储 // // Created by ma c on 16/1/13. // Copyright (c) 2016年 博文科 ...
- @pathVariable的作用(二十二)
spring mvc中的@PathVariable是用来获得请求url中的动态参数的,十分方便,复习下: @Controller public class TestController { @Requ ...
- html5 实现简单的上传
简单记录下今早做H5上传中一些代码还有坑 一.展示 因为前端上传文件是必须通过form表单的,不能使用ajax,这样的话一个移动页面放入一个type为file的input真心不怎么好看,如下图,很挫有 ...
- localhost能连接websocket 127.0.0.1 不能连接问题?
最近开发中遇到一个问题,就是有的浏览器电脑能连接websocket , 有的不能 , 有的能用localhost连接,有的能用127.0.0.1连接,这个问题很奇怪 提供一个很好测试websocket ...
- java实现MsOffice文档向pdf文档转化
本篇文档实现功能,将word和ppt文档的文件转化成pdf格式的文档 应用到jacob 第一步:下载压缩包 (1)jacob官网下载jacob压缩包 (2)网址:http://sourceforge. ...
- CH1809 匹配统计
题意 描述 阿轩在纸上写了两个字符串,分别记为A和B.利用在数据结构与算法课上学到的知识,他很容易地求出了"字符串A从任意位置开始的后缀子串"与"字符串B"匹配 ...
- vue-cli 本地开发mock数据使用方法
vue-cli 中可以通过配置 proxyTable 解决开发环境的跨域问题,具体可以参考这篇文章: Vue-cli proxyTable 解决开发环境的跨域问题 如果后端接口尚未开发完成,前端开发一 ...
- 使用C#的两种方式OracleClient组件和OleDB组件连接ORACLE数据库
一.使用OracleClient组件连接Oracle .Net框架的System.Data.OracleClient.dll组件(ADO.Net组件),为连接和使用Oracle数据库提供了很大的方便. ...
- Jmeter二次开发之代码环境搭建(QQ交流群:577439379)
一.创建项目 1. 分别下载apache3.1 binaries和source两个压缩包,前者为release版本,后者为jmeter最新的源码,下载地址:http://jmeter.apache.o ...