标签:
刚开始学习opengl,做的第一个实验,就是显示圆柱体
这个通过opengl库中的api函数gluCylinder()就可以显示出来,但是极其蛋疼的是,完全看不出它是一个圆柱啊
虽然可以通过reshape()来重新定视角,但是每次运行程序,只能显示一个视角,多麻烦啊。
第一个想做的就是解决摄像机问题,让我们可以通过鼠标键盘交互,实现360度旋转和放大缩小。
opengl中的投影有两种,一个是(平行投影),一个是(透视投影)
(透视投影)符合人们心理习惯,近大远小
所以以下的说明都是基于(透视投影)的。 ps:其实我还不懂平行投影
1. 使用透视投影
首先main函数中添加
glutReshapeFunc(reshape);
参数reshape()是函数名,接下来是reshape()函数
void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(5.0, 5.0, 5.0, 0.0, 0.0, 0.0, 0, 1, 0); }
而gluLookAt()函数,其实你可以理解成摄像机,
前3个参数是你的眼睛,中间3个参数是你看着的地方,最后3个参数是你的相机的正上方
具体说明可以参考这位大佬的文章
http://blog.csdn.net/wangqinghao/article/details/14002077
个人理解,gluLookAt()定义了一个视景矩阵,把(世界坐标系)转换成(摄像机坐标系),
然后由(摄像机坐标系)来解释(世界坐标系)中物体的坐标。
具体转换说明,可以参考
以下网址
http://www.360doc.com/content/14/1028/10/19175681_420515511.shtml
或者我写的数学基础知识03
2.矩阵替换
opengl中支持使用编写的uvn视景矩阵。
创建一个Point类(点),创建一个Vector类(向量),创建一个Camera类(摄像机)
根据gluLookAt()的9个参数,我们同样可以计算出对应的uvn坐标系
事先说明,这个摄像机是以世界坐标系(0, 0, 0)为中心来360度移动的,所以如果变换了中心,之后的函数都要做相对应的修改。
void setCamera(float eyex, float eyey, float eyez, float centerx, float centery, float centerz, float upx, float upy, float upz) { eye.set(eyex, eyey, eyez); center.set(centerx, centery, centerz); up.setV(upx, upy, upz); n.setV(center, eye); n.normalize(); u = n.cross(up); u.normalize(); v = u.cross(n); v.normalize(); R = eye.getDist(); setModeViewMatrix(); }
center这个点必须是(0, 0, 0);
如果不是(0,0,0),后面旋转函数要做相对应的修改
参数说明:
eye是世界坐标系的点,坐标系变换涉及到了平移,所以必须保存下来。
R是视点中心到摄像机的距离,
函数说明:
normalize()是规范化函数,即令向量的模变为1
cross()是叉乘函数,即求出两个不平行向量决定的平面的(法向量)。
最终目的是使uvn两两垂直。
然后是矩阵设置
void setModeViewMatrix() { Vector pointV(eye.x, eye.y, eye.z); M[0] = u.x; M[1] = v.x; M[2] = -n.x; M[3] = 0; M[4] = u.y; M[5] = v.y; M[6] = -n.y; M[7] = 0; M[8] = u.z; M[9] = v.z; M[10] = -n.z; M[11] = 0; M[12] = -pointV.dot(u); M[13] = -pointV.dot(v); M[14] = pointV.dot(n); M[15] = 1; glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // gluLookAt(5.0, 5.0, 5.0, 0.0, 0.0, 0.0, 0, 1, 0); // gluLookAt(eye.x, eye.y, eye.z, 0.0, 0.0, 0.0, up.x, up.y, up.z); glMultMatrixf(M); //这句话就是把矩阵M设为视景矩阵 }
函数说明:
dot()是点乘函数
这里的矩阵为什么出现负号,和
数学基础知识03
不一样,因为向量是有方向的,方向有所改变,所以,一定要注意喔。
好了,然后我们可以看看效果,可以发现和gluLookAt()没有明显的差异
3.摄像机旋转
首先要确定有多少种旋转方式,
根据查找的资料,可以分为roll,yaw,pitch,具体说明,可以参考大佬的文章
http://blog.csdn.net/lovehota/article/details/17374303
http://blog.csdn.net/hobbit1988/article/details/7956838
和上面的大佬们的鼠标交互代码实现不一样,本人没有使用slide
以下是我修改后的yaw()函数和pitch()函数
void yaw(float angle) { float cs = cos(angle*PI / 180); float sn = sin(angle*PI / 180); Vector t(n); Vector s(u); n.setV(cs*t.x - sn*s.x, cs*t.y - sn*s.y, cs*t.z - sn*s.z); u.setV(sn*t.x + cs*s.x, sn*t.y + cs*s.y, sn*t.z + cs*s.z); eye.set(-R*n.x, -R*n.y, -R*n.z); setModeViewMatrix(); }
void pitch(float angle) { float cs = cos(angle*PI / 180); float sn = sin(angle*PI / 180); Vector t(v); Vector s(n); v.setV(cs*t.x - sn*s.x, cs*t.y - sn*s.y, cs*t.z - sn*s.z); n.setV(sn*t.x + cs*s.x, sn*t.y + cs*s.y, sn*t.z + cs*s.z); eye.set(-R*n.x, -R*n.y, -R*n.z); setModeViewMatrix(); }
这两个函数,都添加了修改摄像机的点再世界坐标系下的坐标
举个例子,你把摄像机往上移动,如果你的眼睛不跟上的摄像机,你能看见东西吗?
鼠标交互函数
glutMouseFunc(onMouse);
glutMotionFunc(onMouseMove);
具体函数实现
void onMouse(int button, int state, int x, int y) { if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN ) { LeftMouseOn = true; x11 = x; y11 = y; } else if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) { RightMouseOn = true; x11 = x; y11 = y; } else { LeftMouseOn = false; RightMouseOn = false; } }
变量说明:
x11,y11是鼠标按下去的时候,从屏幕获得的点坐标
然后是鼠标移动的相关函数
void onMouseMove(int x, int y) { int dx = x - x11; int dy = y - y11; // int cnt = 10000; if (LeftMouseOn == true) { RotateX(dx); RotateY(dy); } else if (RightMouseOn == true) { RotateRoll(dx); } x11 = x; y11 = y; //x11 = x21; //y11 = y21; }
重点说明:
只要鼠标在移动,都要修改当前鼠标的坐标,
举个例子:你从起点出发,向右跑出去了,但是如果你突然向左,你算是向左吗,不会,相对于起点,你还是向右的。
函数说明:
RotateX()是水平方向旋转
RotateY()是竖直方向旋转
这里的水平和竖直指的是屏幕的水平和竖直,不是内部物体的
RotateRoll()是摄像机自身的旋转,n不变,旋转u和v,即roll
void RotateX(float x_move) { float part_theta = 30; float theta = x_move*part_theta*3.14/180; cam.yaw(theta); } void RotateY(float y_move) { float part_theta = 30; float theta = y_move*part_theta*3.14 / 180; cam.pitch(theta); /* theta = theta / cnt; for (; cnt != 0; cnt--) { cam.pitch(theta); }*/ } void RotateRoll(float x_move) { float part_theta = 30; float theta = x_move*part_theta*3.14 / 180; cam.roll(theta); }
数据说明:
part_theta是(旋转角度/每1单位移动距离)
4.摄像机放大缩小(伪)
放大缩小的原理,我是用摄像机和视点距离的远近变化来理解的。
所以我实现的这个函数,与其说摄像机放大缩小,还不如说是你拿着摄像机向视点走过去。(因为近大远小嘛)
首先是键盘交互函数
glutKeyboardFunc(keyboard);
然后是键盘操作函数
void keyboard(unsigned char key, int x, int y) { if (key == 109){ cam.bsChange(1); } else if (key == 110){ cam.bsChange(-1); } }
void bsChange(int d) { if (d > 0){ R--; eye.set(-R*n.x, -R*n.y, -R*n.z); } else{ R++; eye.set(-R*n.x, -R*n.y, -R*n.z); } setModeViewMatrix(); }
109和110就是某两个按键的码,大家可以通过printf找出另外2个键来使用
再次提醒:每次坐标系变换(无论是旋转还是平移),都要重新设置视景矩阵
标签:
原文地址:http://www.cnblogs.com/keyncoin/p/5492099.html