上一篇講到如何使用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); // 右乘模型的世界矩陣
-
- 攝像機初始朝向到設備初始朝向的旋轉矩陣
這樣,我們就把虛擬攝像機的朝向和設備的朝向綁定在了一起。