openvr_survivor第一期开发活动:复位与定位追踪
来源:互联网 发布:c语言goto怎么用 编辑:程序博客网 时间:2024/06/05 08:47
开发简介
- 开发主题:VR复位和定位追踪.
- 参与人员:helen,ice,bikasuo.(VR开发者QQ群:538874606)
- 项目代码
- gitter讨论链接
分享一下我们对于这方面问题的一些看法,文档可能存在一些错误和不足,希望能够得到大家的批评和建议,希望与大家一起将这部分内容做的更加完善,持续更新.可以随意复制使用,方便的话注明一下出处,不胜感谢!
复位
- 复位介绍
玩VR游戏的人经常能发现这样的现象,你坐在一个固定的位置上,开始游戏时,你可能是正对着游戏的正前方:
激情几分钟后,在回到这个画面时,面对相同的方向,实际显示的画面已经偏离了原来的方向,例如:
这是一个典型的陀螺仪漂移的情况,为了消除这异常情况,需要进行一次复位操作,使面面重新回到最初的方向上;或则当你在玩VR游戏时,移动头部调整到画面为正方向上时,你面对的方向是北,但是你想朝着方向为南的地方玩,这时需要在南方向上触发一个复位,将画面正方向”拉”过来;或则添加一些特殊功能,例如某些定位方案只有180度范围的定位,扩展定位方案从180—>360度,需要做”一键转身的功能”.目前市面上的各种VR设备都会提供复位的操作. 那么我们就来聊聊VR的复位功能.
复位包括,姿态(rotation)/位置(position)的复位.
姿态的复位:将当前姿态设置为新的原点,这个原点是指在应用的正面方向(多数情况为Yaw为0度的方向,这里暂时也只讨论Yaw为0度的正方向情况,openvr_survivor支持任意Yaw角度复位),**姿态的复位目前多数平台都只对Yaw方向执行复位操作,Pitch和Roll参与复位操作,因为磁力计能确保Yaw方向正确,而没有其他传感器能作为Pitch和Roll的准确位置参考,复位流程如下:
位置的复位:将当前位置设置为新的原点,这个原点是指位置为(0,0,0)的位置,这个比较好理解,在steamVR里是地上绿色圈圈的中心.目前位置定位的方案大多都是绝对坐标,没有累计漂移的问题,一般不需要复位位置. - 如何实现复位功能
实现复位的操作,可以在平台层\应用层\硬件驱动层做这个操作,不论在哪层实现,实现起来的方法都一样,都是对被追踪物体的四元素进行处理.这里,关于复位功能的讨论,个人认为是在平台层实现复位的方法,然后向硬件驱动层和应用层分别提供接口来统一管理比较好,但steamVR目前的情况是没有统一,比较混乱另外复位的算法在几大平台都只提供接口,而不开放源代码,后来自己把复位功能实现,觉得这些并不复杂(花点时间大家基本都能实现),VR目前这行业处于技术爬坡,并且不挣钱的时候,没必要把这些功能封闭起来,浪费大部分开发者的时间,所以创建开源openvr_survivor项目,把一些VR基础技术实现,并分享,和更多的开发者一起推动VR的发展,额,想的有点多~.回归正题,复位功能实现的思路,之前在openvr的issue里讨论过.
按下复位按键或APP里的复位按钮(在触发前最好保持静止状态,这样yaw的角度会比较稳定),第一步先获取当前yaw角度值:
然后在每次上报物体四元素之前,对yaw做偏移:
对应的主要代码:
//get yaw offsetm_dRecenterYawOffset = simple_math::GetYawDegree(m_OriginRotation)//DoOrientationRecenterinline vr::HmdQuaternion_t DoOrientationRecenter(const vr::HmdQuaternion_t quaternion_origin,const double yaw_offset){ double yaw_degree_new,yaw_degree_origin; vr::HmdVector3d_t degree; vr::HmdQuaternion_t quaternion_dest; //get origin yaw from quaternion degree = simple_math::QuaternionToEulerDegree(quaternion_origin); yaw_degree_origin = degree.v[0]; LOG_EVERY_N(INFO,5 * 60) << "DoOrientationRecenter[0]:quat(" << quaternion_origin.w << "," << quaternion_origin.x << "," << quaternion_origin.y << "," << quaternion_origin.z << "),degree(" << degree.v[0] << "," << degree.v[1] << "," << degree.v[2] << "),yaw_offset=" << yaw_offset; //recenter yaw ,get yaw_degree_new yaw_degree_new = yaw_degree_origin - yaw_offset; yaw_degree_origin = degree.v[0]; LOG_EVERY_N(INFO,5 * 60) << "DoOrientationRecenter[0]:quat(" << quaternion_origin.w << "," << quaternion_origin.x << "," << quaternion_origin.y << "," << quaternion_origin.z << "),degree(" << degree.v[0] << "," << degree.v[1] << "," << degree.v[2] << "),yaw_offset=" << yaw_offset; //recenter yaw ,get yaw_degree_new yaw_degree_new = yaw_degree_origin - yaw_offset; if(yaw_degree_new > 180){ yaw_degree_new= -360 + yaw_degree_new; }else if(yaw_degree_new < -180){ yaw_degree_new = 360 + yaw_degree_new; } //transform degree to quaternion degree.v[0] = yaw_degree_new; quaternion_dest = simple_math::DegreeEulerToQuaternion(degree); LOG_EVERY_N(INFO,5 * 60) << "DoOrientationRecenter[1]:quat(" << quaternion_dest.w << "," << quaternion_dest.x << "," << quaternion_dest.y << "," << quaternion_dest.z << "),degree(" << degree.v[0] << "," << degree.v[1] << "," << degree.v[2] << "),yaw_offset=" << yaw_offset; return quaternion_dest;}
详细见openvr_survivor的提交:
commit 72ec5ee7a0d22aceb79991a185763982bc09aed6Author: HelenXR <helenhololens@gmail.com>Date: Fri Jul 14 15:57:16 2017 +0800
实际验证可行,这个复位方法比较容易理解,当然这个复位转换应该有一个更快的操作方法:
quaternion(new) = quaternion(ori) * Rotation matrix.
后续有时间会用GLM库来实现这个转换.
定位追踪
之前学习整理过一个关于定位追踪的资料,详见VR定位追踪,这里就直接进入主题.我们主要是选择了国内目前比较成熟的定位追踪方案,一个是NOLO,另外一个是Ximmerse.两种方案技术路径不同,NOLO是激光+声波,Ximmerse是类似PSVR的可见光定位,它们提供的是相同的功能:6DOF的头部追踪和6DOF的手柄.有了定位追踪,带来了更好的VR体验.
NOLO
NOLO提供的是一种激光定位+声波定位的方案,类似HTC VIVE,它包含一个基站,一个头盔定位器(6DOF),两个手柄控制器(6DOF).激光定位原理(激光定位原理,之前翻译过Hypereal开源的激光定位文档,想了解的,点击这里)上单基站相对于双基站的情况,定位精度会低一些(双基站可以达到0.5mm),单基站能达到2mm的精度,在精度上问题不大,最大的缺点是不能实现360度追踪,只能达到180度,还是蛮遗憾的(文章最后提供一个单基站扩展为360的方案”一键转身”功能).
NOLO定位参数:
1. 定位基站
尺寸:81*41*71mm
功耗:400 ma(2000mAh)
续航:4小时
充电:USB充电(1.5小时)
支持USB数据传输
2. 头盔定位器
尺寸:105*30*53mm
功耗:100 ma
续航:手机供电
支持USB数据传输
3. 交互手柄
尺寸:146*40*40mm
功耗:120 ma(1000mAh)
续航:USB充电(1.5小时)
支持USB数据传输
支持滑动触摸和点触摸
支持可调节震动
4. 定位参数
定位范围:FOV100°5.3m(以基站为原点)
定位精度:<2mm
定位刷新率:60hz
定位延时: <20ms
基于NOLO SDK开发,对应修改代码,见如下提交:
commit ba2a6c68e88480b60ce244894f6ba25dec541118Author: HelenXR <helenhololens@gmail.com>Date: Fri Aug 25 18:42:29 2017 +0800 add six dof module:nolo.
Ximmerse
Ximmerse提供的是一种类似PSVR的可见光追踪的方案.它包含一个双目摄像头,一个头盔定位器(6DOF),两个手柄控制器(6DOF).定位精度2mm,缺点也是只有180度范围定位,相比NOLO刷新率高一些(ximmerse:90,NOLO:60),使用起来更加流畅一些,但定位距离和范围没有NOLO的大,同样存在只有180度追踪范围的尴尬情况.
Ximmerse定位参数:
基于Ximmerse SDK开发,对应修改代码,见如下提交:
commit 3416a43d25e48b2acdf0120cb8d530e656606be0Author: HelenXR <helenhololens@gmail.com>Date: Mon Aug 21 16:25:04 2017 +0800 add six dof module:ximmerse.
3个功能代码说明
在处理两种定位方案过程中,基于SDK开发还是比较顺利的,这里有2个有点意思的数学问题
TouchPad区域识别
在定位方案SDK中都可以通过接口获取到TouchPad的x,y坐标,TouchPad的是一个圆盘(半径为1.0),如下图所示:分为up,down,left,right区域,每次手触摸在触摸板上时,都可以读取到一组x,y的值,如何通过x,y判断按下的区域是哪一个?
区域”UP”:黑色直线(y=x)左侧与蓝色直线(y=-x)右侧,以及半径为1的圆相交的区域.
黑色直线左侧:y>x
蓝色直线右侧:y>-x
xy在圆之内:因触摸板上报的x,y坐标值一定会在圆盘之内,不需要处理.
up区域:-y < x < y
同理可以得出其他三个区域:
down区域:y < x < -y
left区域:x < y < -x
right区域:-x < y < x
对应代码:
vr::EVRButtonId CHandControllerDevice::GetDPadButton(float float_x,float float_y){ if(float_x > 1.0 || float_x < -1.0 ||float_y > 1.0 || float_y < -1.0){ LOG(WARNING) << "GetDPadButton[" << m_cControllerRole << "]: error postion(" << float_x << "," << float_y << ")"; return vr::k_EButton_Max; } int x = float_x * 10000.0f,y = float_y * 10000.0f; //UP:-y<x<y if(x > -y && x < y){ LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:k_EButton_DPad_Up"; return vr::k_EButton_DPad_Up; } //DOWN:y<x<-y if(x > y && x < -y){ LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:k_EButton_DPad_Down"; return vr::k_EButton_DPad_Down; } //LEFT:x<y<-x if(y > x && y < -x){ LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:k_EButton_DPad_Left"; return vr::k_EButton_DPad_Left; } //RIGHT:-x<y<x if(y > -x && y < x){ LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:k_EButton_DPad_Right"; return vr::k_EButton_DPad_Right; } LOG_EVERY_N(INFO,1 * 90) << "GetDPadButton[" << m_cControllerRole << "]:unknown region(" << x << "," << y << "),float(" << float_x << "," << "" << float_y <<")"; return vr::k_EButton_Max;}
- 一键转身
因为NOLO和Ximmerse的定位方案都只有180度,当你面对基站/双目摄像头时,可以操作前方180度的区域,无法操作到背后的区域,一键转身,实现对背后区域的操作,项目里以双击menu/back按键来实现一键转身.一键转身时需要考虑头部信息(6DOF)和手柄信息(6DOF)的转换.
头部位置信息包括姿态(rotation)和位置(position),当一键转身后,头部的姿态需要旋转180度,位置点绕自身旋转180度.
头部姿态旋转按公式q(dest) = q(rotate) * q(ori) 即可.
头部位置旋转麻烦一些:头部位置点,看做一个三维矢量,然后将矢量移到原点,之后进行旋转180度操作,旋转后的矢量移回最初的位置.
手柄信息的处理与头部信息处理基本一致,要注意,手柄位置信息需要绕头部信息进行旋转操作.
代码如下:
vr::HmdQuaternion_t quaternion_rotate = HmdQuaternion_Init( 0, 0, 1, 0 ); vr::HmdQuaternion_t quaternion_origin = HmdQuaternion_Init( m_Pose.qRotation.w,m_Pose.qRotation.x,m_Pose.qRotation.y,m_Pose.qRotation.z); //rotate rotation m_Pose.qRotation = glm_adapter::QuaternionMultiplyQuaternion(quaternion_rotate,m_Pose.qRotation); //rotate position glm_adapter::PointAroundPointRotate(quaternion_rotate,m_Pose.vecPosition,m_dHmdPositionWhenTurnAround,m_Pose.vecPosition);//其中PointAroundPointRotate代码如下: void PointAroundPointRotate(const vr::HmdQuaternion_t quaternion_rotate, const double point_origin[3], const double point_center[3], double point_dest[3]) { glm::tquat<double> tquat_rotate(quaternion_rotate.w, quaternion_rotate.x, quaternion_rotate.y, quaternion_rotate.z); glm::tvec3<double> tvec3_origin(point_origin[0], point_origin[1], point_origin[2]), tvec3_center(point_center[0], point_center[1], point_center[2]),tvec3_dest; tvec3_dest = tquat_rotate * (tvec3_origin - tvec3_center) + tvec3_center; point_dest[0] = tvec3_dest.x; point_dest[1] = tvec3_dest.y; point_dest[2] = tvec3_dest.z; }
- 360度方案扩展
因为NOLO和Ximmerse的定位方案都只有180度,当你面对基站/双目摄像头时,可以操作前方180度的区域,无法操作到背后的区域,虽然通过一键转身功能可以将视角强制转到后面,但这样带来的用户体验和沉静感都或多或少
会受到影响,而如果把NOLO或Ximmerse的基站/双目摄像头放在头顶上,那么360度的操作将变得可行。因为考虑到基站/双目摄像头的最佳识别范围是在1.5米-2.5米,或2米-4.5米,那么考虑到我们手离地的位置为0.7米
那么0.7+2米是目前最佳的放置高度,然后将原先的-z轴变为现在的y轴,原先的y轴变为现在的z轴。
具体的代码如下:(考虑到用户是否需要用这个功能,配置中可以修改m_bTopCamera开启,m_bCameraHeight放置高度)
if (m_bTopCamera) {
glm::vec4 vecPosition = glm::vec4((double)hmdPos[0], (double)-hmdPos[2], (double)hmdPos[1], 1.0f);
glm::mat4 rts = glm::mat4(1.0);
rts = glm::translate(rts, glm::vec3(0, m_bCameraHeight, 0));
vecPosition = rts * vecPosition;
m_Pose.vecPosition[0] = vecPosition.x;
m_Pose.vecPosition[1] = vecPosition.y;
m_Pose.vecPosition[2] = vecPosition.z;
}
对应提交:
commit 7bf5f86d408c871fdb1936edce1d0000747180b6
最后
感谢ice,bikasuo,我们一起用空闲时间完成开发活动,这是一个开始,我们希望更多的人可以加入到我们当中来,VR还处于很初期的阶段,我们通过努力,也许可以给VR的发展带来一些好的东西,分享的过程也是一个很好的互相学习的过程,openvr_survivor下一期活动再会:-).
参考资料:
- OSVR reset yaw
- libovr
- openvr_issues_1
- openvr_issues_2
- reset_camera
- openvr_advanced_settings
- nolo_windows_sdk
- openvr_survivor第一期开发活动:复位与定位追踪
- openvr_survivor第二期开发活动:VR畸变
- 【每日听力活动训练】第一期 0320
- 菜鸟互助会活动记录第一期
- ios 开发之crash定位追踪
- 复位与复位电路
- Dragon 第一期开发日志
- Dragon:第一期开发记录
- SaoUnits开发日志-第一期
- 2010年1月第一期图书拍拍活动预告
- CSDN博客“评论王”活动第一期获奖名单!
- FineReport报表爱好者论坛金币兑换活动第一期粉墨登场
- linux运维技能交换分享活动[第一期]
- 前端俱乐部-先锋计划【第一期】活动总结
- 【第一期】Mini2440开发板软件开发
- EJB开发第一期---EJB开发配置
- 同步复位与异步复位
- 纪念乔布斯,推出四期iPad、iphone赠书活动【第一期】
- 自定义拦截器
- 将文本保存为TXT文档
- WPF中Treeview使用HierarchicalDataTemplate模板之后内容不充满
- C++之创建自定义类型的数组---补充(17)《Effective C++》
- Dev 皮肤控件使用的几种方式
- openvr_survivor第一期开发活动:复位与定位追踪
- 工具:前端参考书籍目录
- 用两个栈实现队列
- 深度学习模型压缩与加速算法之SqueezeNet和ShuffleNet
- python01
- SVN版本冲突问题
- Integer包装类数值比较时值得注意的地方
- Android Api demo系列(14) (App>Fragment>FragmentContextMenu)
- 计算机网络学习预备知识