【OpenGL编程】摄像机漫游
#OpenGL/ES摄像机漫游
##说在前面:
之前的学习当中只是实现了简单的XZ平面的摄像机旋转,对于三维空间的旋转还需要学习一些新的知识,主要涉及两方面的知识:1、欧拉角的概念2、三维空间的数学计算。本节的代码都可在我的github上进行下载。https://github.com/ModestBean/MySample/tree/master/CameraRoam如果本节内容有错误和不合理之处,还请朋友们多多指出,我会虚心接受每一个建议。
本节内容主要参考:
LearnOpenGL的摄像机一章中的欧拉角的概念。
https://learnopengl-cn.github.io/01 Getting started/09 Camera/#_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介绍了实现摄像机的三维漫游,如果本节内容有错误和不合理之处,还请朋友们多多指出,我会虚心接受每一个建议。
下一篇: zookeeper安装容易出现的一些问题