作者:憨豆酒(YinDou),聯繫我[email protected],熟悉圖形學,圖像處理領域,本章的源代碼可在此倉庫中找到: https://github.com/douysu/person-summary 如果大家發現錯誤以及不合理之處,還希望多多指出。
OpenGL/ES攝像機漫遊
說在前面:
之前的學習當中只是實現了簡單的XZ平面的攝像機旋轉,對於三維空間的旋轉還需要學習一些新的知識,主要涉及兩方面的知識:1、歐拉角的概念2、三維空間的數學計算。
本節內容主要參考:
LearnOpenGL的攝像機一章中的歐拉角的概念。
https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/#_1
什麼是攝像機漫遊?
已下gif實現的即爲三維空間的漫遊。
什麼是歐拉角?(借鑑於LearnOpenGL)
歐拉角使可以表示三維空間中任何旋轉的三個值,一共有三種歐拉角,俯仰角,偏航角,和滾轉角。下面的圖片展示了它們的含義:
- 俯仰角(仰視角):繞X軸旋轉的角,在OpenGL/ES中通常表示往上看或者往下看的角,將攝像機抽象成人的頭代表的也就是人的仰視角。
- 偏航角(左右角):繞Y旋轉的角,在OpenGL/ES中通常表示往左或者往右看的角,代表人的頭部左右旋轉。
- 滾轉角:繞Z軸旋轉的角,在OpenGL/ES中通常表示可以360度旋轉的角,無法代表人的頭部,因爲人的頭部是無法實現旋轉360度的,通常情況下不會使用使用滾轉角,除非有特許需求。
一個攝像機漫遊例子
在介紹漫遊之前,我們還需要了解設置攝像機的方法,在OpenGL/ES中,爲了方便和簡化攝像機矩陣,我們會設置攝像機相關的3個因素:攝像機位置,目標位置(眼睛的觀察點),和與人頭部保持一個方向的向量(通常叫它UP向量),當然,這些位置向量都是在世界座標系考慮的。通過這三個因素去生成攝像機矩陣(關於生成攝像機矩陣的相關內容,可以參考LearnOpenGL中的章節),下圖比較直觀的表示了攝像機的三個因素。
攝像機三維旋轉部分
先給出一段限制俯仰角和偏航角的方法,在這裏傳入的兩個參數分別是y方向的偏移距離(手指豎着划動的距離)和x方向的偏移距離(手指橫着划動的距離)。在這裏,我限制了俯仰角爲090度。偏航角爲0360度。
/*
* @param yjSpan y方向的偏移距離
* @param cxSpan x方向的偏移距離
* */
void CameraUtil::calCamera(float yjSpan, float cxSpan) {
//限制俯仰角度
yj = yj + yjSpan;
if(yj>=90){
yj=90;
}if(yj<=0){
yj=0;
}
//限制偏航角角度
degree = degree + cxSpan;
if (degree >= 360) {
degree = degree - 360;
}
else if (degree <= 0) {
degree = degree + 360;
}
calCamera();//重新計算攝像機姿態的方法
}
我繪製了相關三維圖像方便理解。首先是動態計算攝像機的位置cx,cy,cz,圖像如下。爲了方便理解,我將目標點放到了原點。攝像機是在以目標點爲中心,半徑爲R的球面上運動的,我繪製的只是某一個狀態,不過這也足夠了。接着就是對位置的分解了,我相信這難不倒大家
這就是動態計算攝像機位置的代碼部分,其中涉及到角度轉弧度的知識,我便不再介紹了。這裏便是通過編程實現上面的簡單算法了。
//計算當前觀察角度下攝像機的位置(基於俯仰角重新計算 X軸爲旋轉軸)
cy = float(sin(yj*3.1415926535898 / 180)*CAMERA_R + ty);
float cxz = float(cos(yj*3.1415926535898 / 180)*CAMERA_R);
//(基於偏航角重新計算攝像機位置 Y軸爲旋轉軸)
cx = float(sin(degree*3.1415926535898 / 180)*cxz + tx);
cz = float(cos(degree*3.1415926535898 / 180)*cxz + tz);
然後就是動態計算UP向量的部分了,這與計算攝像機位置的部分大同小異,我就不再進行分解了,幾何圖如下。
計算UP向量的代碼。
//計算當前攝像機的UP向量
float upY = float(cos(yj*3.1415926535898 / 180));
float upXZ = float(sin(yj*3.1415926535898 / 180));
float upX = float(-upXZ*sin(degree*3.1415926535898 / 180));
float upZ = float(-upXZ*cos(degree*3.1415926535898 / 180));
攝像機三維移動部分
通過以上我繪製的幾何圖相信已經很好理解整個過程了,下面是攝像機前後所有移動的相關幾何圖。
通過上面的幾何分解就可得到移動的距離了,非常簡單、我直接給出攝像機前後左右的移動代碼。
/*
* @param goBack 前進或者後退的距離
* @param leftRight 左右移動的距離
* */
void CameraUtil::cameraGo(float goBack, float leftRight){
//計算X方向位移
float xStep = float(-goBack*sin(degree*3.1415926535898 / 180) - leftRight*sin((degree + 90)*3.1415926535898 / 180));
//計算Z方向位移
float zStep = float(-goBack*cos(degree*3.1415926535898 / 180) - leftRight*cos((degree + 90)*3.1415926535898 / 180));
tx = tx + xStep;
tz = tz + zStep;
calCamera();
}
最後是應用的代碼啦,屏幕上X,Y方向的偏移量的大小與俯仰角和偏航角的大小是成正比的,這裏這是計算了X,Y兩個放向的偏移量,並傳給了攝像機類。如下
int x=AMotionEvent_getRawX(event,0);
int y=AMotionEvent_getRawY(event,0);
int32_t id = AMotionEvent_getAction(event);
switch(id){
case AMOTION_EVENT_ACTION_DOWN://按下事件
xPre=x;
yPre=y;
isClick = true;
break;
case AMOTION_EVENT_ACTION_MOVE://滑動事件
xDis=x-xPre;
yDis=y-yPre;
if (myabs(xDis)>5 || myabs(yDis)>5){//判斷觸控點位移是否超過閾值
isClick = false;
}
if (!isClick){
CameraUtil::calCamera(yDis*180.0f / 600, xDis*180.0f / 600);
}
xPre=x;
yPre=y;
break;
case AMOTION_EVENT_ACTION_UP://擡起事件
#define MOVE_SPAN 10
if (isClick){
if (x < MyVulkanManager::screenWidth / 4){//左移
CameraUtil::cameraGo(0, MOVE_SPAN);
}
else if (x > MyVulkanManager::screenWidth * 3 / 4){//右移
CameraUtil::cameraGo(0, -MOVE_SPAN);
}
else if (y < MyVulkanManager::screenHeight / 2){//前移
CameraUtil::cameraGo(MOVE_SPAN, 0);
}
else{
CameraUtil::cameraGo(-MOVE_SPAN, 0);//後移
}
}
break;
}
寫在最後:
本小節主要基於OpenGL ES介紹了實現攝像機的三維漫遊,如果本節內容有錯誤和不合理之處,還請朋友們多多指出,我會虛心接受每一個建議。