天天看點

OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)

  • 理論基礎

    1,OpenGL渲染3D物體到螢幕上的過程其實類似我們平時用照相機拍照的過程,這個步驟大緻如下:一,把照相機固定在三腳架并讓它對準場景(視圖變換)二,把場景中的物體調整擺放好(模型變換)三,選擇照相機的鏡頭,并調整放大倍數(投影變換)四,确定最終照片的大小(視口變換)。其中視圖變換必須要在模型變換之前,其它可以在任何時候。

    2,視圖變換:設定錄影機的位置,gluLookAt(錄影機位置,鏡頭瞄準,上方向),其中上方向的了解就類似是一個放好的瓶子,一般指定垂直向上是瓶蓋的方向。預設錄影機位于原點,指向z軸的負方向,朝上向量為(0, 1, 0).

    3,模型變換:設定物體的位置和方向,包括:旋轉,移動,縮放,而且要注意組合使用他們的順序,像先平移再旋轉與先旋轉再平移它們的表現是不一樣的。注意,可以不必通過移動錄影機(視圖變換)來觀察物體,而是移動這個物體(模型變換),這兩個方式都能實作同樣地效果,有時使用其中一種變換比使用另一種變換要友善得多。這也是把視圖變換和模型變換都統一為模型視圖矩陣的原因。

    4,投影變換:設定視景體,将3維坐标投影為2維螢幕坐标,位于視景體之外的東西将被裁剪掉。它分為兩種投影:正投影和透視投影,其中正投影就是平行投影,不管遠近物體看上去都一樣。而透視投影則是近大遠小的真實效果。為什麼會是這樣?可以這麼了解:投影時會是有個正方體或是平截頭體(就是指視景體),它們的每個切面其實就相當于我們的螢幕,對應正投影的正方體(長方體)當然是不管距離錄影機遠處還是近處,切面都是一樣大,是以物體觀察沒有變化。而對于透視投影的平截頭體,近處的切面小因而物體占據的切面百分比面積要大,即同樣的物體近處填充螢幕的面積要大,是以就形成了近大遠小的真實效果。

    正投影:glOrtho(left, right, bottom, top, near, far)或二維圖像的投影gluOrtho2D(left, right, bottom, top)

    透視投影:glFrustum(left, right, bottom, top, near, far),或者工具封裝的接口gluPerspective(angle, w/h, near, far)

    對應的示意圖如下:

    OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)
    OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)
    OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)

    5,視口變換:其實就是指定最終圖像顯示在螢幕那個區域,一般我們是設定和螢幕一樣大。(glViewport (0, 0, (GLsizei) w, (GLsizei) h))

    6,glPushMatrix() 和 glPopMatrix()了解:首先我們要知道,對于矩陣的操作都是對于矩陣棧的棧頂來操作的。目前矩陣即為矩陣棧的棧頂元素,而對目前矩陣進行平移、旋轉等的變換操作也同樣是對棧頂矩陣的修改。是以我們在變換之前調用giPushMatrix()的話,就會把目前狀态壓入第二層,不過此時棧頂的矩陣也與第二層的相同。 當經過一系列的變換後,棧頂矩陣被修改,此時調用glPopMatrix()時,棧頂矩陣被彈出,且又會恢複為原來的狀态。即儲存原來狀态與恢複原來狀态,它與glLoadIdentity ();這個的差別是,這個直接恢複為最初始狀态(0),而它是儲存了目前狀态。示意圖如下:

    OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)
    7,逆變換:我們是經過各種變換将3D空間投影到2D螢幕上的,然而我們滑鼠點選2D螢幕它對應的原來3D空間的哪裡了?這就需要逆變換還原。(gluUnProject())
  • 代碼示例

    1,繪制一個長方體

#include "GLTools.h"
#include "GLShaderManager.h"
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#endif

void init(void)
{
    glClearColor (, , , );
    glShadeModel (GL_FLAT);
}

void display(void)
{
    glClear (GL_COLOR_BUFFER_BIT);
    glColor3f (, , );
    glLoadIdentity ();//矩陣操作之前一般把目前矩陣設定為機關矩陣
    //視圖變換
    gluLookAt (, , , , , , , , );//設定錄影機位置
    //模型變換
    glScalef (, , ); //設定物體的位置和方向,包括:旋轉,移動,縮放
    glutWireCube ();  //繪制立方體
    glFlush ();
}

//當視窗初次建立或改變時會被調用
void reshape (int w, int h)
{
    //視口變換
    glViewport (, , (GLsizei) w, (GLsizei) h);//最終在螢幕上顯示的大小
    glMatrixMode (GL_PROJECTION);//設定目前矩陣為投影矩形
    glLoadIdentity ();
    //投影變換
    //glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
    /*設定視景體,将3維坐标投影到2維螢幕坐标*/
    gluPerspective(, , , );
    glMatrixMode (GL_MODELVIEW);//設定目前矩陣為模型視圖矩陣
}


int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize (, );
    glutInitWindowPosition (, );
    glutCreateWindow (argv[]);
    init ();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();
    return ;
}
           
OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)

2,繪制一個裁剪掉了3/4的線框圓(除了視景體的6個裁剪平面外,還可以另外再指定最多可達6個的其他裁剪平面,對視景體施加進一步的限制)

#include <GL/glut.h>

void init(void) 
{
   glClearColor (, , , );
   glShadeModel (GL_FLAT);
}

void display(void)
{
   //其它裁剪面方程式Ax + By + Cz + D = 0系數
   GLdouble eqn[] = {, , , };
   GLdouble eqn2[] = {, , , };

   glClear(GL_COLOR_BUFFER_BIT);

   glColor3f (, , );
   glPushMatrix();
   glTranslatef (, , -);//Z軸負方向平移5個機關

   /* 裁剪掉y < 0 的部分 */
   glClipPlane (GL_CLIP_PLANE0, eqn);
   glEnable (GL_CLIP_PLANE0);
   /* 裁剪掉x < 0 的部分 */
   glClipPlane (GL_CLIP_PLANE1, eqn2);
   glEnable (GL_CLIP_PLANE1);
   /*逆時針方向繞着從原點到(1.0,0.0,0.0)的直線旋轉90°*/
   glRotatef (, , , );
   glutWireSphere(, , );//繪制圓
   glPopMatrix();

   glFlush ();
}

void reshape (int w, int h)
{
   glViewport (, , (GLsizei) w, (GLsizei) h); 
   glMatrixMode (GL_PROJECTION);
   glLoadIdentity ();
   gluPerspective(, (GLfloat) w/(GLfloat) h, , );
   glMatrixMode (GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case :
         exit();
         break;
   }
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (, ); 
   glutInitWindowPosition (, );
   glutCreateWindow (argv[]);
   init ();
   glutDisplayFunc(display); 
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return ;
}
           
OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)

3,模拟行星系統

#include "GLTools.h"
#include "GLShaderManager.h"
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#endif

static int day = ;
void display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST); //開啟深度測試

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(, , , );
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(, -, , , , , , , );

    //太陽
    glColor3f(, , );
    glutSolidSphere(, , );
    //地球
    glColor3f(, , );
    /*正确地順序是要先平移再旋轉,代碼的寫法要相反*/
    glRotatef(day , , , -);
    glTranslatef(, , );
    glutSolidSphere(, , );
    //月亮
    glColor3f(, , );
    glRotatef(day / * - day , , , -);
    glTranslatef(, , );
    glutSolidSphere(, , );

    glutSwapBuffers();
}

void play()
{
    day++;
    if (day >= )
        day = ;
    display();
    glutPostRedisplay();
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize (, );
    glutInitWindowPosition (, );
    glutCreateWindow (argv[]);

    glutDisplayFunc(display);
    glutIdleFunc(play);
    glutMainLoop();
    return ;

}
           
OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)

4,模拟機器人手臂

#include "GLTools.h"
#include "GLShaderManager.h"
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#endif

static int shoulder = , elbow = ;

void init(void)
{
    glClearColor (, , , );
    glShadeModel (GL_FLAT);
}
/*把握一點,opengl是一個狀态機,所有的操作都是在改變狀态而已*/
void display(void)
{
    glClear (GL_COLOR_BUFFER_BIT);
    glPushMatrix();
        /*這裡為什麼不是先畫長方體再執行這裡的移動旋轉,也是由于opengl是一個狀态
        機,先畫了,再執行這些變換就已經沒作用了*/
        glTranslatef (-, , );
        glRotatef ((GLfloat) shoulder, , , );
        glTranslatef (, , );
        /*push時記住目前的狀态,pop就恢複,如果不這樣,那麼像這裡進行了縮放操作
        的狀态就會一直影響以後所有的繪制*/
        glPushMatrix();
            glScalef (, , );
            glutWireCube ();
        glPopMatrix();

        glTranslatef (, , );
        glRotatef ((GLfloat) elbow, , , );
        glTranslatef (, , );
        glPushMatrix();
            glScalef (, , );
            glutWireCube ();
        glPopMatrix();

    glPopMatrix();
    glutSwapBuffers();
}

void reshape (int w, int h)
{
    glViewport (, , (GLsizei) w, (GLsizei) h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective(, (GLfloat) w/(GLfloat) h, , );
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (, , -);
}

void keyboard (unsigned char key, int x, int y)
{
    switch (key) {
        case 's':
            shoulder = (shoulder + ) % ;
            glutPostRedisplay();
            break;
        case 'S':
            shoulder = (shoulder - ) % ;
            glutPostRedisplay();
            break;
        case 'e':
            elbow = (elbow + ) % ;
            glutPostRedisplay();
            break;
        case 'E':
            elbow = (elbow - ) % ;
            glutPostRedisplay();
            break;
        case :
            exit();
            break;
        default:
            break;
    }
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize (, );
    glutInitWindowPosition (, );
    glutCreateWindow (argv[]);
    init ();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutMainLoop();
    return ;

}
           
OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)

5,逆變換執行個體

#include "GLTools.h"
#include "GLShaderManager.h"
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#endif

void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glFlush();
}


void reshape(int w, int h)
{
    glViewport (, , (GLsizei) w, (GLsizei) h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective (, (GLfloat) w/(GLfloat) h, , );
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
    GLint viewport[];
    GLdouble mvmatrix[], projmatrix[];
    GLint realy;  //opengl表示的y坐标
    GLdouble wx, wy, wz;  //逆變換後的3D坐标

    switch (button)
    {
        case GLUT_LEFT_BUTTON:
            if (state == GLUT_DOWN)
            {
                /*得到對應變換狀态值并儲存到對應變量中*/
                glGetIntegerv (GL_VIEWPORT, viewport);  
                glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix);
                glGetDoublev (GL_PROJECTION_MATRIX, projmatrix);
                /*滑鼠點是視窗左上角是原點而opengl是左下角原點*/
                realy = viewport[] - (GLint) y - ;
                printf ("Coordinates at cursor are (%4d, %4d)\n", x, 
                realy);
                //逆變換
                gluUnProject ((GLdouble) x, (GLdouble) realy, ,
                              mvmatrix, projmatrix, viewport, &wx, 
                              &wy, &wz);
                printf ("World coords at z=0.0 are (%f, %f, %f)\n",
                        wx, wy, wz);
                gluUnProject ((GLdouble) x, (GLdouble) realy, ,
                              mvmatrix, projmatrix, viewport, &wx, 
                              &wy, &wz);
                printf ("World coords at z=1.0 are (%f, %f, %f)\n",
                        wx, wy, wz);
            }
            break;
        case GLUT_RIGHT_BUTTON:
            if (state == GLUT_DOWN)
                exit();
            break;
        default:
            break;
    }
}


int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize (, );
    glutInitWindowPosition (, );
    glutCreateWindow (argv[]);
    glutDisplayFunc(display); 
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMainLoop();
    return ;

}
           
OpenGL--3D世界(視圖變換,模型變換,投影變換,視口變換)

繼續閱讀