通过OpenGL ES在iOS平台实践增强现实(二)
上一篇讲到如何使用OpenGL ES绘制一个3D场景,这一篇我们会配合使用iOS提供的CoreMotion框架把虚拟世界中的摄像机的位置朝向和设备实际的位置朝向绑定起来。本文还对防抖做了处理。
首先说明几个容易混淆的问题:
1. OpenGL ES的摄像机,位置固定在世界坐标系原点,Up方向和世界坐标系y轴重合,Right方向和世界坐标系x轴重合,Look方向和世界坐标系负z轴重合
2. 为了抽象一个可以缩放,旋转,移动的摄像机,我们可以在OpenGL ES的的矩阵操作中通过左乘这个摄像机的变换矩阵的逆矩阵来实现
3. CoreMotion框架中,可以从API中获取代表朝向的欧拉角或者四元数数据,因为欧拉角存在万向节死锁的问题,所以我们取四元数
4. 注意CoreMotion的初始朝向问题,本文采用CMAttitudeReferenceFrameXMagneticNorthZVertical(Right方向和南重合,Up方向指向垂直上方)作为初始朝向
- 初始化CoreMotion的代码
-
// 启用陀螺仪 motionManager = [[CMMotionManager alloc]init]; if (motionManager.deviceMotionAvailable) { motionManager.deviceMotionUpdateInterval = motionInterval; [motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical toQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) { if(motion){ CMRotationRate rotationRate = motion.rotationRate; double rotationX = rotationRate.x; double rotationY = rotationRate.y; double rotationZ = rotationRate.z; double value = rotationX * rotationX + rotationY * rotationY + rotationZ * rotationZ; // 防抖处理,阀值以下的朝向改变将被忽略 if (value > 0.01) { CMAttitude *attitude = motion.attitude; t = 0.0f; s = 0.0f; // 从当前朝向以固定加速度像目标朝向进行四元数插值 srcQuaternion = curQuaternion; desQuaternion = GLKQuaternionNormalize(GLKQuaternionMake(attitude.quaternion.x, attitude.quaternion.y, attitude.quaternion.z, -attitude.quaternion.w)); } } }]; }
-
- 为了防抖处理,需要将当前朝向平滑过渡到目标朝向
-
if (s <= 1) { t += 0.05; // 以固定初速度和加速度对原朝向和目标朝向进行插值 s = v0 *t + a * t * t / 2; curQuaternion = GLKQuaternionNormalize(GLKQuaternionSlerp(srcQuaternion, desQuaternion, s)); }
-
- 上文已经提到了虚拟摄像机的初始朝向,为了将设备的实际朝向和虚拟摄像机的朝向绑定到一起,我们先将摄像机的坐标轴转动到与CMAttitudeReferenceFrameXMagneticNorthZVertical代表的坐标轴重合,再用上文得到的curQuaternion转动到设备的实际朝向,最后求上述转动的逆即可
- 摄像机初始朝向到设备初始朝向的旋转矩阵
-
_worldTrasform[0] = 0.0; _worldTrasform[1] = 0.0; _worldTrasform[2] =1.0; _worldTrasform[3] = 0.0; _worldTrasform[4] = 1.0; _worldTrasform[5] = 0.0; _worldTrasform[6] = 0.0; _worldTrasform[7] = 0.0; _worldTrasform[8] = 0.0; _worldTrasform[9] = 1.0; _worldTrasform[10] = 0.0; _worldTrasform[11] = 0.0; _worldTrasform[12] = 0.0; _worldTrasform[13] = 0.0; _worldTrasform[14] = 0.0; _worldTrasform[15] = 1.0;
-
- 将四元数转换为矩阵
-
GLfloat x = curQuaternion.x; GLfloat y = curQuaternion.y; GLfloat z = curQuaternion.z; GLfloat w = curQuaternion.w; _worldTrasform[0] = 1-2*y*y-2*z*z; _worldTrasform[1] = 2*x*y-2*w*z; _worldTrasform[2] = 2*x*z+2*w*y; _worldTrasform[3] = 0.0; _worldTrasform[4] = 2*x*y+2*w*z; _worldTrasform[5] = 1-2*x*2-2*z*z; _worldTrasform[6] = 2*y*z-2*w*x; _worldTrasform[7] = 0.0; _worldTrasform[8] = 2*x*z-2*w*y; _worldTrasform[9] = 2*y*z+2*w*z; _worldTrasform[10] = 1-2*x*x-2*y*y; _worldTrasform[11] = 0.0; _worldTrasform[12] = _position.x; _worldTrasform[13] = _position.y; _worldTrasform[14] = _position.z; _worldTrasform[15] = 1.0;
-
- 将上面两个矩阵顺次相乘并根据正交矩阵的性质求逆矩阵并左乘当前带渲染实体的世界变换矩阵
-
glMatrixMode(GL_MODELVIEW_MATRIX); glLoadIdentity(); glLoadMatrixf(cameraMatrix); // 将模型矩阵设置为摄像机世界矩阵的逆矩阵 glMultMatrixf(transform); // 右乘模型的世界矩阵
-
- 摄像机初始朝向到设备初始朝向的旋转矩阵
这样,我们就把虚拟摄像机的朝向和设备的朝向绑定在了一起。