Cardboard Talk02 Accelerometer
操作系统:Windows8.1
显卡:Nivida GTX965M
开发工具:Android studio 3.0.0 | Cardboard 1.0
在深入讨论具体实现之前,有必要了解一下Android系统的 IMU 传感器与坐标系之间的关系及关系转换,才不至于在后面实现、应用中搞混坐标系。在此借机会翻译 NVDIA 的一篇文章 Tegra Android Accelerometer Whitepaper ,内容如有不当之处请指教。
Introduction
移动便携设备可以进行多方向的旋转。很多情景下,应用程序需要考虑通过何种方式影响他们,比如自定义用户UI时的 landscape 风景模式 或者 portriat 肖像模式时,或者解读原始的传感器数据时。应用程序在使用加速度计 accelerometer 获取原始数据必须要经过特殊处理,换句话说为了获得良好的用户体验,还要加入设备及操作系统的考虑因素。
虽然加速度计 accelerometer 是许多软件应用重要的输入设备,但是有一些开发者正在使用错误的方式来处理加速度计的数据。更重要的是,这些应用程序没有考虑到设备的方向,这会直接导致糟糕的用户体验。处理加速度计正确的方向是一件简单、容易的事情,我们将在接下来的章节中讨论。
Coordinate Space Glossary
Android系统会以绝对值的形式上报加速度计的值。最初上报的数值始终是物理传感器的数据,经过调整为 canonical 规范格式,以便所有设备以相同形式上报、处理数据。Android 不会根据设备方向将加速度计数据转换。应用程序必须自己执行转换过程。
为说明转换过程,本文档定义坐标空间概念以及各个坐标空间之间的转换关系。
Space | Description |
设备坐标空间 | 加速度计设备可以按照各种方式输出加速度值,并且不受到任何标准的限制。 |
规范坐标空间 |
为规范设备每帧输出的坐标数据,Android要求必须重新对设备坐标系 Device Raw 进行映射。所以,当设备朝向右移动时,正X轴增加,当设备朝上移动时,正Y轴增加,当设备朝向屏幕方向移动时,正Z轴增加,反之亦然。 有关 Canonical 加速度计各轴的布局结构请参阅 Accelerometer Canonical Axes 。 |
屏幕坐标空间 | Android窗体管理器的屏幕坐标系原点在左上角,最大坐标在右下角,即增加X是向右,增加Y是向下。Android的 Display 管理器会根据传感器读取的数据改变屏幕方向。屏幕坐标系总是相对于当前的旋转角度。 |
世界坐标空间 |
这个坐标空间是特定于 OpenGLES 应用程序的。应用程序开发人员可能需要修改代码以适应本文档中的假设条件,默认使用右手坐标系,up可以是任意的矢量。 |
下表显示了需要做的转换关系,并为本文定义了一个词汇表。OpenGL应用程序通常会使用 canonToWorld 。基于Android窗体系统的应用程序将使用 canonToScreen。混合开发的应用程序,例如基于Android窗体系统且应用OpenGL渲染内容的将需要两者。
需要注意的是,加速度计设备驱动程序会处理 Canonical Space,即处理 deviceToCanon 转换。本文档着重于 canonToScreen 和 canonToWorld 转换。
Accelerometer Canonical Axes
以下图示显示了基于给定设备和方向的加速度计值的变化。它们包括一个基于垂直 portrait 肖像布局的原生设备,一个旋转到横向的 portriat 肖像设备,以及一个横向 landscape 风景布局原生设备,规范化 Canonical x/y/z 加速度计 轴/值。
移动设备原生肖像模式 Orientation 0
移动设备原生肖像模式 Orientation 90
移动设备原生风景模式 Orientation 0
Working with Acclerometer Data
加速度计的值必须根据 Display 对象返回的方向进行旋转,才可能在屏幕上获取期望的结果。Android获取屏幕旋转朝向的API为 getOrientation() 或者 getRotation() ,他们都会返回同样的结果,但是前者不建议在继续使用。
这些函数返回的值对应 Android.view.Surface 类中带有 ROTATION_ 前缀的整数和常量。下面是调用其中一个函数的例子。其中 this 代表 Activity 类型对象。
WindowManager windowMgr =
(WindowManager)this.getSystemService(WINDOW_SERVICE);
int rotationIndex = windowMgr.getDefaultDisplay().getRotation();
返回常量为:
Constant Name | Index/Value |
ROTATION_0 | 0 |
ROTATION_90 | 1 |
ROTATION_180 | 2 |
ROTATION_270 | 3 |
应用程序可以使用旋转值构建转换矩阵,该矩阵将会转换 Android Canonical 加速度计数据到其他的坐标系空间。为了将标准的 Canonical 加速度计数值转换为屏幕或者世界坐标系下的数值。一个基于 rotationIndex 索引的 canonAccel 向量需要按照90度的增量旋转,其中 ROTATION_0 意味着不需要旋转。
为了进行 canonToScreen 转换,定义旋转方程为:
screenAccel[0] = canonAccel[0] * cos
- canonAccel[1] * sin
![]()
screenAccel[1] = -canonAccel[0] * sin- canonAccel[1] * cos
![]()
screenAccel[2] = canonAccel[2]
其中:
一个 canonToScreen 转换的函数实现如下:
static void canonicalToScreen(int displayRotation,
float[] canVec,
float[] screenVec)
{
struct AxisSwap
{
signed char negateX;
signed char negateY;
signed char xSrc;
signed char ySrc;
};
static const AxisSwap axisSwap[] = {
{ 1, -1, 0, 1 }, // ROTATION_0
{-1, -1, 1, 0 }, // ROTATION_90
{-1, 1, 0, 1 }, // ROTATION_180
{ 1, 1, 1, 0 } }; // ROTATION_270 const AxisSwap& as = axisSwap[displayRotation];
screenVec[0] = (float)as.negateX * canVec[ as.xSrc ];
screenVec[1] = (float)as.negateY * canVec[ as.ySrc ];
screenVec[2] = canVec[2];
}
对于 canonToWorld 转换,旋转按照如下公式进行:
screenAccel[0] = canonAccel[0] * cos
- canonAccel[1] * sin
![]()
screenAccel[1] = canonAccel[0] * sin+ canonAccel[1] * cos
![]()
screenAccel[2] = canonAccel[2]
轴对称的转换相关数据可以预置在静态数组中,如下列函数 canonicalToWorld(),将标准空间加速度向量转换成OpenGL风格的世界空间向量时,使用简单的整数查找数组避免昂贵的三角函数计算代价。
static void canonicalToWorld( int displayRotation,
const float* canVec,
float* worldVec)
{
struct AxisSwap
{
signed char negateX;
signed char negateY;
signed char xSrc;
signed char ySrc;
};
static const AxisSwap axisSwap[] = {
{ 1, 1, 0, 1 }, // ROTATION_0
{-1, 1, 1, 0 }, // ROTATION_90
{-1, -1, 0, 1 }, // ROTATION_180
{ 1, -1, 1, 0 } }; // ROTATION_270 const AxisSwap& as = axisSwap[displayRotation];
worldVec[0] = (float)as.negateX * canVec[ as.xSrc ];
worldVec[1] = (float)as.negateY * canVec[ as.ySrc ];
worldVec[2] = canVec[2];
}
下一个函数将根据对应的坐标轴计算旋转角度,该转换会参考加速度计上方向 localUp 。函数以向量的形式返回 旋转轴 rotAxis 和 对应的旋转角度 angles ,获得以上数据后就可以针对世界空间进行转换矩阵、四元数的创建。
void computeAxisAngle(const float* localUp, const float* worldVec,
float* rotAxis, float* ang)
{
const Vec3& lup = *(Vec3*)localUp;
Vec3 nTarget = normalize(*(Vec3*)worldVec);
*rotAxis = cross(lup, nTarget);
*rotAxis = normalize(*rotAxis);
*ang = -acosf(dot(lup, nTarget));
}
Power Conservation
为了节省设备电量,应用程序应该选择低级别的加速度计更新频率。可以选择的更新频率级别均定义在 android.hardware.sensormanager 中,下表显示了按照降序排列的加速度计更新频率级别。
Constant Name | Relative Speed |
SENSOR_DELAY_FASTEST | fastest |
SENSOR_DELAY_GAME | faster |
SENSOR_DELAY_NORMAL | slower |
SENSOR_DELAY_UI | slowest |
一个设置传感器更新频率的示例如下:
if (mSensorManager == null)
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
if (mSensorManager != null)
mSensorManager.registerListener(
this,
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SENSOR_DELAY_GAME );
额外需要注意的是 delay 值是抽象的,该值与具体的设备有关。因此同样的更新级别在不同手机表现会有所不同。保证设备更新频率唯一的方法就是在运行时测量返回的速率。
Supporting Older OS Versions
为了支持比较老的Android系统比如 Froyo / v2.2。可能需要使用比较旧的API功能 getOrientation()。下面演示动态绑定的例子,需要注意的是 getOrientation() 可能会在未来Android版本中消失。
WindowManager wm;
Method getRotation; wm = (WindowManager)this.getSystemService(WINDOW_SERVICE);
Class<Display> c = (Class<Display>)wm.getDefaultDisplay().getClass();
Method[] methods = c.getDeclaredMethods();
String rotFnName = new String("getRotation");
for( Method method : methods )
{
Log.d("Methods", method.getName());
if( method.getName().equals( rotFnName ))
{
getRotation = method;
break;
}
} int orientation;
Display display = wm.getDefaultDisplay(); if( getRotation != null )
{
try
{
Integer i = (Integer)getRotation.invoke(d);
orientation = i.intValue();
}
catch(Exception e) {}
}
else
{
orientation = display.getOrientation();
}
需要补充的是NDK并没有直接获取 Display 的方式,但是可以间接的传递 Context 对象,通过反射获取 WindowManager 及 Display 对象,从而调用 getOrientation() 或 getRotation() 。但是该方法性能比较堪忧,毕竟使用了反射。另一个方式是从上层将 getOrientation() 或者 getRotation() 的值传递到 native层使用。
Cardboard Talk02 Accelerometer的更多相关文章
- 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 ...
- [转]A Guide To using IMU (Accelerometer and Gyroscope Devices) in Embedded Applications.
原文地址http://www.starlino.com/imu_guide.html Introduction There’s now a FRENCH translation of this art ...
- 【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解决方案,提供了手 ...
随机推荐
- cx_oracle 安装和配置
前提条件: 已经成功安装python 已经成功安装oracle客户端 1.去官网上下载对应版本的cx_oracle http://cx-oracle.sourceforge.net/ 注意版本必须与p ...
- CF 920
t1 随便乱搞 t2 随便乱搞 然后wa了三发,QAQ t3 随便乱搞 t4 邻接表+堆 对进出进行一个统计 然后时间到了...
- Python3 列表List(十一)
list是一种有序可重复的集合,可以随时添加和删除其中的元素. 序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. ...
- iOS导航栏的正确隐藏方式
在项目中经常碰到首页顶部是无限轮播,需要靠最上面显示.有的设置导航栏为透明等一系列的方法,这个可以借助第三方.或者干脆简单粗暴的直接隐藏掉导航栏.可是push到下一个页面的时候是需要导航栏的,如何做了 ...
- 由于BOM头导致的Json解析出错
上周五改完一些BUG后,测试通过就安心在家过了个周末.结果周一回来一看,整个安卓APP所有的接口都挂掉了1.查找bug 首先想到的是客户端代码有问题,然后想起来上周五还能运行得好好的手机也是同样的错误 ...
- PHP迭代器的小坑
使用PHP迭代器的时候,需要主要到很多迭代器是对内部迭代器的封装,当外部迭代器移动的时候,实际上也是在移动内部迭代器. 示例一:命令行 &"C:\wamp64\bin\php\php ...
- Python流程控制-while循环-for循环
写重复代码 是可耻的行为 -------------- 完美的分割线 -------------- 摘录自:http://www.runoob.com/python/python-loops.htm ...
- OpenCV - Linux(Ubuntu 16.04)中安装OpenCV + OpenCV_Contrib
近两个月来接触了Linux系统,在老板的建议下翻了Ubuntu的牌子,我安装的版本是16.04,用习惯之后感觉蛮好的,比Windows要强.好啦,废话不说啦,下面开始说在Ubuntu中安装OpemCV ...
- [Luogu4899][IOI2018] werewolf 狼人
luogu sol \(\mbox{IOI2018}\)的出题人有没有看过\(\mbox{NOI2018}\)的题目呀... \(\mbox{Kruskal}\)重构树+二维数点. 题目相当于是问你从 ...
- TypeScript学习笔记(六) - 模块
本篇将介绍TypeScript里的模块,和使用方法. 在ECMAScript 2015标准里,JavaScript新增了模块的概念.TypeScript也沿用了这个概念. 一.模块的导入和导出 模块在 ...