OpenGL裏面的旋轉縮放與平移其實就是座標系的相關變換,不過由於初學者(像我)很容易被OpenGL裏面各種座標系搞暈而難以理解,現在將我的理解記錄如下。PS: 由於學校課程要求,使用的是過時的固定管線。
爲了清楚地理解這三種變換,我們只關心兩個座標系:建模座標系和世界座標系。眼座標之類的使用默認值。文末有一段程序代碼,繪製了一個正方體,並實現了這三種變換。
世界座標系(WC):你現在正對繪圖窗口,右方是世界座標系的x軸,上方是y軸,從屏幕指向你的是z軸。
模型座標系(MC):使用glVertex()
之類的函數定義圖元時用的座標系,初始時MC和WC是重合的。
下文中的圖,用藍色表示MC,黑色表示WC
旋轉
旋轉時世界座標系不變,但是建模座標系會旋轉,所以旋轉之後MC和WC是不重合的。
旋轉需要選定一個向量以及一個角度,相應的函數爲glRotatef()
,注意:這個向量指的是建模座標系下的一個向量,建模座標系繞着這個向量旋轉。
例如:繞y軸旋轉30度,向量爲(0, 1, 0),角度爲30。角度可正可負,opengl裏面的是右手座標系,所以旋轉的正方向就是右手大拇指朝向旋轉向量所指方向時,四個手指所指方向。下圖爲繞(0, 1, 0)旋轉30度。
這樣其實也不難理解,但是當時被自己寫的代碼坑到了。
文末的代碼中,旋轉是在一對glPushMatrix()
和 glPopMatrix()
中的,所以你在屏幕上看到的圖形是由最開始的MC旋轉得到的,而不是由上次旋轉後的MC再次旋轉得到的。
在我寫的程序中顯示的圖形是由繞x, y, z軸依次旋轉而得到的(當然也可以不繞着座標軸),按下xyz鍵會改變繞xyz軸旋轉的角度r_x, r_y, r_z,然後依次地繞xyz軸旋轉而得到最終顯示的圖形。
現在有一種情形,繞xyz軸旋轉的角度爲r_x, r_y, r_z且均不爲0,圖形已經繪製好了,我現在按着y鍵,使r_y一直增加,我看到的是圖形繞着y軸轉動了一定角度嗎?
不是。由於是依次繞着xyz軸旋轉,r_y增加,所以受影響的只是基於最開始的MC繪製時,繞y軸旋轉的角度,但是繞y軸之後還有繞z軸轉動啊,最終的到的圖形與之前旋轉了r_x,r_y,r_z時相比,並不是繞y軸旋轉。如果想直觀觀察到,可以編譯運行下面的代碼,先胡亂按xyz,然後按着y鍵不放。
平移
平移時MC相對與WC的位置不變,但是MC裏面所有的座標都將在原來的基礎上加上偏移量(dx, dy, dz)。使用的函數爲glTranslatef()
,平移相對來說比較容易理解,不詳細敘述了。
縮放
先說說現實中的縮放,如果你想要一個物體變小,有兩個方法:
- 增加你和物體之間的距離
- 對物體施加魔法,改變它的實際尺寸
同理,OpenGL裏面你如果想要對一個圖形進行縮放,可以用glOrtho()
之類的函數調整投影區域,也可以使用glScalef()
等將圖形的各個座標縮小或放大。
#include <iostream>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <math.h>
#include <unistd.h>
/*---------------全局變量---------------------*/
//--------------------------------------------
const double PI = 3.14159;
//旋轉角度
GLfloat r_xyz[3] = {0.0f};
//平移偏移量
GLfloat dx=0, dy=0, dz=0;
//縮放比例
double radio = 1.0d;
int cx = 200, cy = 200; //中心座標窗口
//窗口大小
int w = 400;
int h = 400;
//標記是否着色,默認着色
int mode = 1;
/*------------------函數聲明--------------------*/
//---------------------------------------------
void onDisplay();
void onReshape(int , int);
void Drawing();
void myRotate();
void rgb(int r, int g, int b);
void specialKeys(int key, int x, int y);
void rgb(int r, int g, int b)
{
glColor3f(r/255.0f, g/255.0f, b/255.0f);
}
void onDisplay()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
Drawing();
glutSwapBuffers();
}
void onReshape(int width, int height)
{
GLfloat aspect = (GLfloat) w/(GLfloat)h;
GLfloat nRange = 400;
w = width;
h = height;
int min = w > h ? h : w;
glViewport(0, 0, min, min);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-nRange, nRange, -nRange , nRange, -nRange, nRange);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glutSwapBuffers();
glutPostRedisplay();
}
void myRotate()
{
int vec[3][3] = {
{1, 0, 0},
{0, 1, 0},
{0, 0, 1}
};
for (int i = 0 ; i <= 2 ; i ++)
{
glRotatef(r_xyz[i], vec[i][0], vec[i][1], vec[i][2]);
}
}
void Drawing()
{
glPushMatrix();
glLoadIdentity();
//旋轉
myRotate();
glScalef(radio, radio, radio);
glTranslatef(dx, dy, dz);
if (mode == 2)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glLineWidth(3);
}
else
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
//頂點向量
//下面表示一個正方體
int A[] = {-100, -100, 100};
int a[] = {-100, -100, -100};
int B[] = {100, -100, 100};
int b[] = {100, -100, -100};
int C[] = {100, 100, 100};
int c[] = {100, 100, -100};
int D[] = {-100, 100, 100};
int d[] = {-100, 100, -100};
glBegin(GL_QUADS);
//front tomato
rgb(234, 67, 53);
glVertex3iv(A);
glVertex3iv(B);
glVertex3iv(C);
glVertex3iv(D);
//fruit salad
rgb(52, 168, 83);
glVertex3iv(D);
glVertex3iv(C);
glVertex3iv(c);
glVertex3iv(d);
//gray
rgb(120, 120, 120);
glVertex3iv(d);
glVertex3iv(c);
glVertex3iv(b);
glVertex3iv(a);
//white
rgb(255, 255, 255);
glVertex3iv(a);
glVertex3iv(b);
glVertex3iv(B);
glVertex3iv(A);
//yellow
rgb(251, 188, 5);
glVertex3iv(a);
glVertex3iv(A);
glVertex3iv(D);
glVertex3iv(d);
//blue
rgb(66, 132, 243);
glVertex3iv(B);
glVertex3iv(C);
glVertex3iv(c);
glVertex3iv(b);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnd();
glPopMatrix();
glutSwapBuffers();
}
void keyFunc(unsigned char key, int x, int y)
{
switch(key)
{
case 'z': r_xyz[2] += 2; break;
case 'Z': r_xyz[2] -= 2; break;
case 'y': r_xyz[1] += 2; break;
case 'Y': r_xyz[1] -= 2; break;
case 'x': r_xyz[0] += 2; break;
case 'X': r_xyz[0] -= 2; break;
case '-': if (radio > 0)radio -= 0.01; break;
case '+': radio += 0.01; break;
case 'q': exit(0);
case '1': mode = 1;break;
case '2': mode = 2; break;
}
glutPostRedisplay();
}
void specialKeys(int key, int x, int y)
{
switch(key)
{
case GLUT_KEY_UP: dy += 4;break;
case GLUT_KEY_DOWN: dy -= 4; break;
case GLUT_KEY_LEFT: dx -= 4; break;
case GLUT_KEY_RIGHT: dx += 4; break;
}
glutPostRedisplay();
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowPosition(10, 10);
glutInitWindowSize(400, 400);
glutCreateWindow("this is not what I wanted");
glutDisplayFunc(onDisplay);
glutReshapeFunc(onReshape);
glutKeyboardFunc(keyFunc);
glutSpecialFunc(specialKeys);
glutMainLoop();
}