剛開始學習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個鍵來使用
再次提醒:每次坐標系變換(無論是旋轉還是平移),都要重新設置視景矩陣