1. 程式人生 > >二維圖形學的變換-平移、旋轉、縮放 OpenGL

二維圖形學的變換-平移、旋轉、縮放 OpenGL

這裡實現的是多點畫多邊形,然後把這個多邊形進行二維的變換。

首先,多點畫多邊形,為了方便起見,我直接呼叫了Opengl的庫函式。其次,就是如何進行多邊形的二維變換。在這裡我有兩種方法。第一種是直接根據數學三角等公式推斷得到結果。第二種方法是用矩陣相乘的方法。

先講第一種

平移

假如我要平移a到b的位置:


那麼操作就是:把這個多邊形的n個頂點從一個位置移動到另外一個位置,然後重新生成平移後的多邊形。(頂點知道了,那麼就可以直接重新繪製多邊形了)

大致程式碼就是:

         Void translate2D(int x,int y){//x,y表示平移xy的距離

                   For(int i=0;i<n;i++){

頂點[i].x=頂點[i].x+x;

頂點[i].y=頂點[i].y+y;

}

                   DrawPolygon();//這裡表示重新繪製多邊形

}

旋轉(可以繞任意中心點旋轉)

旋轉原理:我們可以通過應用標準的三角等式得到旋轉後的座標。


根據上圖,利用三角形的數學公式可以直接得出旋轉後的公式


虛擬碼如下:

Void rotate2D(int x,int y,double theta){//x,y為旋轉的中心,theta為旋轉的角度

         For(inti=0;i<n;i++){

頂點[i].x=x+(頂點[i].x-x)*cos(theta)-(

頂點[i].y-y)*sin(theta);

頂點[i].y=y+(頂點[i].x-x)*sin(theta)+(頂點[i].y-y)*cos(theta);

}

DrawPolygon();//這裡表示重新繪製多邊形

}

二維縮放(基於一個固定點縮放)

簡單縮放可以直接通過將縮放係數sx,sy與對應x,y座標相乘:x’=x*sx,y’=y*sy

當然,我們需要在一個固定點進行縮放,那麼就需要我們選擇一個在縮放變換後不改變位置的點,來控制縮放後物件的位置。


得到的公式則是:


其中sx,sy屬於縮放係數。0~1表示縮小,>1表示放大

虛擬碼:

Void scale2D(int x,int y,float sx,float sy){//x,y

表示固定座標,sx,sy為縮放係數

         For(inti=0;i<n;i++){

頂點[i].x=頂點[i].x*sx+x*(1-sx);

頂點[i].y=頂點[i].y*sy+y*(1-sy);

}

         DrawPolygon();//這裡表示重新繪製多邊形

}

第二種方法(矩陣相乘):

       二維平移矩陣:

      

       二維旋轉矩陣:繞座標系原點的而為旋轉變換方程可以表示成矩陣形式


x’,y’表示變換後的座標。

如果要基於某個基準點旋轉,則:

1.平移物件使基準點位置移動到座標原點

2.繞座標原點旋轉

3.平移物件使基準點回到原始位置

矩陣公式則是:


二維縮放矩陣:(也是基於原點)


如何要基於某個固定點進行縮放,那麼就需要二維複合變換矩陣。

基於某個固定點縮放原理:其實就是先平移然後再縮放,最後再返回到原點。

於是有:


上述矩陣就是基於某個固定點的縮放。

必須注意的是:複合矩陣求值的順序一定不能交換,除非某些情況譬如連續兩次旋轉或者連續兩次平移等等。

為什麼需要用複合矩陣相乘?而不直接一次一次的變換得到結果呢?

因為直接進行復合矩陣的計算會減少乘法和加法次數。具體詳解就不在此敘述。

來看最終完整版程式碼:

#include<gl/glut.h>
#include<iostream>
#include<cmath>
#include<vector>
#pragma comment(linker,"/subsystem:\"Windows\"/entry:\"mainCRTStartup\"")
#define PI 3.14159265358
//q,w,e,r分別為平移,繪製多邊形,旋轉,縮放
#define TRANSLATE 0
#define DRAWPOLYGON 1
#define ROTATE 2
#define SCALE 3
int tran_x,tran_y;
int _xtmp,_ytmp;//作為縮放變數用
int mode=DRAWPOLYGON;//預設為繪製模式
using namespace std;
const int winwidth=800;
const int winheight=640;
struct position{
    double x;
    double y;
};
typedef GLfloat Matrix3x3[3][3];
Matrix3x3 matComposite;//複合矩陣
vector<position> xy;
position tmp;
void DrawPolygon();
void dragmouse(int x,int y);
void mymouse(int button,int state,int x,int y);
void myKeyboard(unsignedchar key,int x,int y);
void myKeyboardUp(unsignedchar key,int x,int y);
//設定為單位矩陣
voidmatrix3x3SetIdentity(Matrix3x3mat){
    GLint row,col;
    for(row=0;row<3;row++){
        for(col=0;col<3;col++)
            mat[row][col]=(row==col);
    }
}
void init(){
    glClearColor(1.0,1.0,1.0,1.0);//設定繪製視窗顏色為白色
    glClear(GL_COLOR_BUFFER_BIT);//清除視窗顯示內容
    /*設定為投影型別模式和其他觀察引數*/
    glPointSize(3.0f);
    glColor3f(1.0,0.0,0.0);//設定顏色為紅
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0,winwidth,winheight,0);
    matrix3x3SetIdentity(matComposite);
}
 
//矩陣相乘
void matrixpreMultiply(Matrix3x3m1,Matrix3x3m2){
    GLint row,col;
    Matrix3x3 tmp;
    for(row=0;row<3;row++){
        for(col=0;col<3;col++){
            tmp[row][col]=m1[row][0]*m2[0][col]+m1[row][1]*
                m2[1][col]+m1[row][2]*m2[2][col];
        }
    }
    for(row=0;row<3;row++){
        for(col=0;col<3;col++){
            m2[row][col]=tmp[row][col];
        }
    }
}
//平移
void translate2D(GLfloattx,GLfloatty){
    Matrix3x3 matTransl;
    matrix3x3SetIdentity(matTransl);//設定為單位矩陣
    matTransl[0][2]=tx;
    matTransl[1][2]=ty;
    matrixpreMultiply(matTransl,matComposite);
 
}
//旋轉
void rotate2D(intx,inty,floattheta){
    theta=theta/180*PI;
    Matrix3x3 matRot;
    matrix3x3SetIdentity(matRot);
    matRot[0][0]=cos(theta);
    matRot[0][1]=-sin(theta);
    matRot[0][2]=x*(1-cos(theta))+y*sin(theta);
    matRot[1][0]=sin(theta);
    matRot[1][1]=cos(theta);
    matRot[1][2]=y*(1-cos(theta))-x*sin(theta);
    matrixpreMultiply(matRot,matComposite);
}
 
//縮放
void scale2D(GLfloatsx,GLfloatsy,intx,inty){
    Matrix3x3 matScale;
    matrix3x3SetIdentity(matScale);
    matScale[0][0]=sx;
    matScale[0][2]=(1-sx)*x;
    matScale[1][1]=sy;
    matScale[1][2]=(1-sy)*y;
    matrixpreMultiply(matScale,matComposite);
}
//複合矩陣
void transformVerts2D(){
    GLfloat tmp;
    for(int i=0;i<xy.size();i++){
        tmp=matComposite[0][0]*xy[i].x+matComposite[0][1]*xy[i].y+matComposite[0][2];
        xy[i].y=matComposite[1][0]*xy[i].x+matComposite[1][1]*xy[i].y+matComposite[1][2];
        xy[i].x=tmp;
    }
    DrawPolygon();
    matrix3x3SetIdentity(matComposite);
}
 
 
//繪製多邊形
void DrawPolygon(){
//  glEnable(GL_POLYGON_STIPPLE);
    glPolygonMode(GL_BACK,GL_LINE);//設定反面為線性模式
    glPolygonMode(GL_FRONT,GL_LINE);//設定正面為線性模式
    glClear(GL_COLOR_BUFFER_BIT);//清除視窗顯示內容
    glBegin(GL_POLYGON);
    for(unsignedint i=0;i<xy.size();i++){
        glVertex2f(xy[i].x,xy[i].y);
    }
    glEnd();
    glFlush();
}
 
//滑鼠拖動
void dragmouse(intx,inty){
    float ssx=1,ssy=1;
    switch (mode)
    {
        //q,w,e,r
        case TRANSLATE:
            translate2D(x-tran_x,y-tran_y);
            transformVerts2D();
            tran_x=x;
            tran_y=y;
            break;
        case DRAWPOLYGON:      
           
            break;
        case ROTATE:
            if(x<=_xtmp&&y>=_ytmp)
                rotate2D(tran_x,tran_y,-8);
            else
                rotate2D(tran_x,tran_y,8);
            transformVerts2D();
            _xtmp=x;
            _ytmp=y;
            break;
        case SCALE:
 
 
            /*不等比例縮放*/
            if(x>_xtmp){
                ssx+=0.01f;
            }
            else if(x<_xtmp&&ssx>0){
                ssx-=0.01f;
            }
            if(y<_ytmp){
                ssy+=0.01f;
            }
            else if(y>_ytmp&&ssy>0){
                ssy-=0.01f;
            }
 
            /*等比例縮放
            if(x<=_xtmp&&y>=_ytmp){
                ssx-=0.01f;
                ssy-=0.01f;
            }else{
                ssx+=0.01f;
                ssy+=0.01f;
            }*/
 
 
 
 
 
            scale2D(ssx,ssy,tran_x,tran_y);
            transformVerts2D();
            _xtmp=x;
            _ytmp=y;
            break;
        default:
            break;
    }
 
}
 
//滑鼠監聽
void mymouse(intbutton,intstate,intx,inty){
 
    if(button==GLUT_LEFT_BUTTON &&state==GLUT_DOWN){
        switch (mode)
        {
        //q,w,e,r
        case TRANSLATE:
            tran_x=x;
            tran_y=y;
 
            break;
        case DRAWPOLYGON:      
            tmp.x=x;
            tmp.y=y;
            xy.push_back(tmp);
            DrawPolygon();
            break;
        case ROTATE:
            tran_x=x;
            tran_y=y;
            _xtmp=x;
            _ytmp=y;
            break;
        case SCALE:
            tran_x=x;
            tran_y=y;
            break;
        default:
            break;
        }
 
    }
 
 
 
}
//鍵盤監聽
void myKeyboard(unsignedcharkey,intx,inty){
    //清空刪除
    if(key=='a'){
        glClear(GL_COLOR_BUFFER_BIT);//清除視窗顯示內容
        glFlush();
        xy.clear();
    }
 
 
}
 
void myKeyboardUp(unsignedcharkey,intx,inty){
    switch (key)
    {
    case 'q':mode=TRANSLATE;
        break;
    case 'w':mode=DRAWPOLYGON;
        break;
    case 'e':mode=ROTATE;
        break;
    case 'r':mode=SCALE;
        break;
    default:
        break;
    }
}
void myDisplay(){
     glFlush();
}
void mymenu(intid){
    if(id==0)
        mode=0;
    else if(id==1)
        mode=1;
    else if(id==2)
        mode=2;
    else if(id==3)
        mode=3;
}
 
int main(intargc,char**argv){
    glutInit(&argc,argv);//初始化
    glutInitDisplayMode(GLUT_SINGLE |GLUT_RGB);//設定繪製模式
    glutInitWindowPosition(500,300);
    glutInitWindowSize(winwidth,winheight);
    glutCreateWindow("二維圖形的變換");//建立視窗
 
    int id=glutCreateMenu(mymenu);
   
    glutAddMenuEntry("平移",0);
    glutAddMenuEntry("繪製多邊形",1);
    glutAddMenuEntry("旋轉",2);
    glutAddMenuEntry("縮放",3);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
 
    init();
    glutDisplayFunc(myDisplay);
    glutMouseFunc(mymouse);//滑鼠監聽回撥函式
    glutMotionFunc(dragmouse);//滑鼠拖動
   
    glutKeyboardFunc(myKeyboard);//鍵盤監聽
    glutKeyboardUpFunc(myKeyboardUp);//鍵盤彈起狀態
    glutMainLoop();
}

上述預設是繪製模式,如果需要平移,按一下鍵盤Q,然後按住滑鼠左鍵拖動就可以平移,旋轉,縮放也是一樣的道理。

最終結果自己執行去吧~O(∩_∩)O哈哈~

以上程式碼我用的是VS2012+OpenGL環境執行。可以正確執行。