剛開始學習opengl,作的第一個實驗,就是顯示圓柱體html
這個經過opengl庫中的api函數gluCylinder()就能夠顯示出來,可是極其蛋疼的是,徹底看不出它是一個圓柱啊api
雖然能夠經過reshape()來從新定視角,可是每次運行程序,只能顯示一個視角,多麻煩啊。ide
第一個想作的就是解決攝像機問題,讓咱們能夠經過鼠標鍵盤交互,實現360度旋轉和放大縮小。函數
opengl中的投影有兩種,一個是(平行投影),一個是(透視投影)學習
(透視投影)符合人們心理習慣,近大遠小spa
因此如下的說明都是基於(透視投影)的。 ps:其實我還不懂平行投影.net
1. 使用透視投影code
首先main函數中添加orm
glutReshapeFunc(reshape);
參數reshape()是函數名,接下來是reshape()函數htm
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個鍵來使用
再次提醒:每次座標系變換(不管是旋轉仍是平移),都要從新設置視景矩陣