OpenGL學習進程(12)第九課:矩陣乘法實現3D變換


    本節是OpenGL學習的第九個課時,下面將詳細介紹OpenGL的多種3D變換和如何操作矩陣堆棧。

 

    (1)3D變換:

    OpenGL中繪制3D世界的空間變換包括:模型變換、視圖變換、投影變換和視口變換。

    現實世界是一個3維空間,如果我們要觀察一個物體,我們可以:

1.從不同的位置去觀察它。(視圖變換) 2.移動或者旋轉它,當然了,如果它只是計算機里面的物體,我們還可以放大或縮小它。(模型變換) 3.如果把物體畫下來,我們可以選擇:是否需要一種“近大遠小”的透視效果。另外,我們可能只希望看到物體的一部分,而不是全部(剪裁)。(投影變換) 4.我們可能希望把整個看到的圖形畫下來,但它只占據紙張的一部分,而不是全部。(視口變換)

    實現原理:

    OpenGL變換實際上是通過矩陣乘法來實現。無論是移動、旋轉還是縮放大小,都是通過在當前矩陣的基礎上乘以一個新的矩陣來達到目的。

    1)模型變換和視圖變換:

    從"相對移動"的觀點來看,改變觀察點的位置與方向和改變物體本身的位置與方向具有等效性。因此這里視圖變換和模型變換就一起講,甚至在OpenGL中實現這兩行總功能所用的函數都是相同的。

    如何進行模型或視圖視圖變換:

    1.1.1准備:

1.glMatrixMode(GL_MODELVIEW);//先設置當前操作的矩陣為“模型視圖矩陣”;
2.glLoadIdentity();//通常需要把此矩陣設為單位矩陣;

    函數glMatrixMode(GLenum mode):

    mode告訴計算機哪一個矩陣堆棧將是下面矩陣操作的目標,即將什么矩陣設置為當前矩陣,它的可選值有:

1.GL_MODELVIEW:表示接下來的矩陣操作都是針對模型視景矩陣堆棧,直到下一次調用這個函數並更改參數為止。   
2.GL_PROJECTION:表示接下來的矩陣操作都是針對投影矩陣堆棧,直到下一次調用這個函數並更改參數為止。
3.GL_TEXTURE:表示接下來的矩陣操作都是針對紋理矩陣堆棧,直到下一次調用這個函數並更改參數為止。

    注意:在設置好當前矩陣之后,任何可以做的操作只能是針對當前矩陣的。

    函數glLoadIdentity()

    將當前的用戶坐標系的原點移到了屏幕中心,類似於一個復位操作:(如果進行過空間變換后再次調用這個函數則視圖回到初始狀態)

1.X坐標軸從左至右,Y坐標軸從下至上,Z坐標軸從里至外。
2.OpenGL屏幕中心的坐標值是X和Y軸上的0.0f點。 
3.中心左面的坐標值是負值,右面是正值。移向屏幕頂端是正值,移向屏幕底端是負值。 移入屏幕深處是負值,移出屏幕則是正值。

    1.1.2變換:

    進行模型和視圖變換,主要涉及到這幾個函數:

模型變換函數三個:縮放glScalef(),移動glTranslatef(),旋轉glRotatef().//模型變換的目的是設置模型的位置和方向
視圖變換函數一個:觀察gluLookAt().

    函數void glScalef()

    glScalef(2.0f,3.0f,4.0f):將模型按x,y,z方向分別拉伸了2,3,4倍。

    glScalef(1.0f,1.0f,-1.0f):將模型關於z軸翻轉了180°(即關於xy軸所在平面對稱)。

    函數glTranslatef()

    glTranslatef(-1.5f,0.0f,-6.0f):將模型向x軸負方向平移1.5,z軸負方向平移6.0。

    函數glRotatef()

    glRotatef(45,0.0f,0.0f,0.0f):將模型沿x軸旋轉45°。

    函數gluLookAt()

void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,//相機在世界坐標的位置
               GLdouble centerx,GLdouble centery,GLdouble centerz,//相機鏡頭對准的物體在世界坐標的位置
               GLdouble upx,GLdouble upy,GLdouble upz);//相機向上的方向在世界坐標中的方向

    gluLookAt (0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0):相機在z軸上,歪45度(攝像機頂朝着(1.0,1.0,0.0)這個方向)看向原點。

    gluLookAt(0.0,0.0,5.0, 0.0,0.0,0.0, 0.0,1.0,0.0):相機在z軸上,攝像機頂朝向y軸(即上方),看向坐標原點。 

    示例:將3D物體變換后從不同位置觀察:(glViewport()和glOrtho()http://www.cnblogs.com/yxnchinahlj/archive/2010/10/30/1865298.html)

#include <GL/glut.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

void axis(double length)
{
    glColor3f(1.0f, 1.0f, 1.0f);
    glPushMatrix();
    glBegin(GL_LINES);
    glVertex3d(0.0, 0.0, 0.0);
    glVertex3d(0.0, 0.0, length);
    glEnd();
    //將當前操作點移到指定位置
    glTranslated(0.0, 0.0, length - 0.2);
    glColor3f(1.0, 0.0, 0.0);
    glutWireCone(0.04, 0.3, 8, 8);
    glPopMatrix();
}
void SetupRC()
{
    glClearColor(0.5,0.5,0.5,1.0);//設置背景色為灰色
}
void paint(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glPointSize(1.0f);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-3.0, 3.0, -3.0, 3.0, -3, 3);
    //gluLookAt(0, 0, 2, 0, 0, 0, 0, 1, 0);//正視圖
    gluLookAt(1, 1, 0.5, 0, 0, 0, 0, 0, 1);//斜視圖
    //gluLookAt(0, 2, 0, 0, 0, 0, 1, 0, 0);//俯視

    //畫坐標系
    axis(2.0);

    glPushMatrix();
    glRotated(90.0, 0, 1.0, 0);//繞y軸正方向旋轉90度
    axis(2.0);
    glPopMatrix();

    glPushMatrix();
    glRotated(-90.0, 1.0, 0.0, 0.0);//繞x軸負方向旋轉
    axis(2.0);
    glPopMatrix();

    glRotated(90, 0, 1, 0);
    glutWireTeapot(0.5);

    glFlush();
}
void changeSize(int width, int height)
{
    /*GLfloat mid = width > height ? height : width;
    glViewport(0, 0, mid, mid);*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (width <= height)
        glOrtho(-3, 3, -3 * (GLfloat)height/(GLfloat)width, 3 * (GLfloat)height/(GLfloat)width, -3, 3);
    else
        glOrtho(-3 * (GLfloat)width / (GLfloat)height, 3 * (GLfloat)width / (GLfloat)height, -3, 3, -3, 3);
}
int main(int argc,char *argv[])
{
    glutInit(&argc,argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(400,400);
    glutInitWindowPosition(200,200);
    glutCreateWindow("模型視圖變換");
    SetupRC();
    glutReshapeFunc(changeSize);
    glutDisplayFunc(paint);
    glutMainLoop();
}

    遇到的問題:

    這三句話是一起的,如果只用第三句會出現"每在窗體點擊一下,窗體中的物體就會在當前視圖狀態下發生改變"的情況。

glMatrixMode(GL_PROJECTION);
glLoadIdentity();//glLoadIdentity()函數設置為單位矩陣,阻止了連續變換產生的累積效果
glOrtho(-3.0, 3.0, -3.0, 3.0, -3, 3);

    防止當窗體變大時圖形發生變形有兩種解決方法:

GLfloat mid = width > height ? height : width;
glViewport(0, 0, mid, mid);
glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 if (width <= height)
    glOrtho(-3, 3, -3 * (GLfloat)height/(GLfloat)width, 3 *(GLfloat)height/(GLfloat)width, -3, 3);
 else
    glOrtho(-3 * (GLfloat)width / (GLfloat)height, 3 * (GLfloat)width / (GLfloat)height, -3, 3, -3, 3);

    2)投影變換:

    投影變換就是定義一個可視空間(視景體),可視空間以外的物體不會被繪制到屏幕上。投影矩陣指定了可視區域的大小和形狀,在未使用投影變換時,默認可見坐標范圍為(-1.0,1.0),使用后可以自定義在窗體顯示的空間坐標范圍。

    准備:

    要對當前矩陣進行投影變換,必須首先指定當前矩陣為投影矩陣並初始化單位矩陣:

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

    OpenGL支持兩種類型的投影變換,即透視投影和正投影:

    1.2.1正投影(Orthographic Projection):(示例如上)

    正投影通常用於2D繪圖,它的最大一個特點是無論物體距離相機多遠,投影后的物體大小尺寸不變。這種投影通常用在建築藍圖繪制和計算機輔助設計等方面,這些行業要求投影后的物體尺寸及相互間的角度不變,以便施工或制造時物體比例大小正確。

    此種模式下,不需要設定照相機位置、方向以及視點的位置,也就是說可以不需要gluLookAt函數。(此時默認是正投影)

    OpenGL正射投影函數共有兩個:

    void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far)

    它創建一個平行視景體。實際上這個函數的操作是創建一個正射投影矩陣,並且用這個矩陣乘以當前矩陣。其中近裁剪平面是一個矩形,矩形左下角點三維空間坐標是(left,bottom,-near),右上角點是(right,top,-near);遠裁剪平面也是一個矩形,左下角點空間坐標是(left,bottom,-far),右上角點是(right,top,-far)。所有的near和far值同時為正或同時為負。如果沒有其他變換,正射投影的方向平行於Z軸,且視點朝向Z負軸。這意味着物體在視點前面時far和near都為負值,物體在視點后面時far和near都為正值。

    void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)

    它是一個特殊的正射投影函數,主要用於二維圖像到二維屏幕上的投影。它的near和far缺省值分別為-1.0和1.0,所有二維物體的Z坐標都為0.0。因此它的裁剪面是一個左下角點為(left,bottom)、右上角點為(right,top)的矩形。

    實例如下:

#include <GL/glut.h>
#include <stdlib.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

void display(void)
{
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glRectf(-0.9, 0.9, 0.9, -0.9);
    glFlush();
}
void init(void)
{
    glClearColor(0.5, 0.5, 0.5, 0.5);
    glShadeModel(GL_FLAT);
}
void reshape(int w, int h)
{
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //仍然是原來1:1的比例,所以起不到任何作用
    gluOrtho2D(-(GLdouble)h / h, (GLdouble)h / h, -(GLdouble)h / h, (GLdouble)h / h);
}
void changeSize(int w, int h)
{  /*相當於按窗體的顯示區域動態定義裁剪區域的大小
   然后填充到顯示區域時圖像比例並不發生變化。*/
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    float mid = w > h ? (w*1.0) / h : (h*1.0) / w;
    if (w<h)
        gluOrtho2D(-1.0, 1.0, -mid, mid);
    else
        gluOrtho2D(-mid, mid, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(400, 400);
    glutCreateWindow("2D正投影變換");
    init();
    glutDisplayFunc(display);
    //glutReshapeFunc(reshape);此語句中的reshape函數實際上起不到任何作用
    glutReshapeFunc(changeSize);
    glutMainLoop();
    return 0;
}

    1.2.2透視投影:

    透視投影符合人們心理習慣:即離視點近的物體大,離視點遠的物體小,遠到極點即為消失,成為滅點。它的視景體類似於一個頂部和底部都被切除掉的棱椎,也就是棱台。這個投影通常用於動畫、視覺仿真以及其它許多具有真實性反映的方面。

    運用透視投影時需要用gluLookAt設定照相機位置、照相機方向(一般設置為(0,1,0))、以及視點位置。

    透視投影函數也有兩個:

    void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far);

    它創建一個透視視景體。其操作是創建一個透視投影矩陣,並且用這個矩陣乘以當前矩陣。這個函數的參數只定義近裁剪平面的左下角點和右上角點的三維空間坐標,即(left,bottom,-near)和(right,top,-near);最后一個參數far是遠裁剪平面的Z負值,其左下角點和右上角點空間坐標由函數根據透視投影原理自動生成。near和far表示離視點的遠近,它們總為正值。

    void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);

    它也創建一個對稱透視視景體,但它的參數定義於前面的不同,如圖所示。其操作是創建一個對稱的透視投影矩陣,並且用這個矩陣乘以當前矩陣。參數fovy定義視野在X-Z平面的角度,范圍是[0.0,180.0];參數aspect是投影平面寬度與高度的比率;參數zNear和Far分別是遠近裁剪面到眼睛的距離,它們總為正值。

  

     透視變換的實例:

#include <GL/glut.h>
#include <math.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
//旋轉的步進值
static float fMoonRot = 0.0f;
static float fEarthRot = 0.0f;

void SetupRC()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    //打開深度測試,一般運用在繪制3D場景中
    glEnable(GL_DEPTH_TEST);
}

// 繪制場景(顯示回調函數)
void RenderScene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 保存矩陣狀態(模型視圖矩陣)
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // 平移坐標系,注意是相對於視覺坐標的位置
    glTranslatef(0.0f, 0.0f, -300.0f);

    // 繪制紅色的太陽
    glColor3f(1.0f, 0.0f, 0.0f);//等價於glColor3ub(255, 0, 0);
    glutSolidSphere(25.0f, 30, 30);

    // 旋轉坐標系(此坐標系的中心是太陽)
    glRotatef(fEarthRot, 0.0f, 1.0f, 0.0f);
    // 繪制藍色的地球
    glColor3ub(0, 0, 255);
    // 平移坐標系
    glTranslatef(100.0f, 0.0f, 0.0f);//表明太陽與地球的距離是100
    // 設置地球的旋轉步進
    fEarthRot += 5.0f;
    if (fEarthRot > 360.0f) {
        fEarthRot = 0.0f;
    }
    glutSolidSphere(15.0f, 30, 30);

    // 繪制月球
    glColor3ub(200, 200, 255);
    // 旋轉坐標系(此坐標系的中心是地球)
    glRotatef(fMoonRot, 0.0f, 1.0f, 0.0f);
    // 平移坐標系
    glTranslatef(30.0f, 0.0f, 0.0f);
    // 設置月亮的旋轉步進
    fMoonRot += 20.0f;
    if (fMoonRot > 360.0f) {
        fMoonRot = 0.0f;
    }
    glutSolidSphere(6.0f, 30, 30);
    glPopMatrix();

    glutSwapBuffers();
}


void ChangeSize(GLsizei w, GLsizei h)//typdef int GLsizei 
{
    GLfloat fAspect;
    // 防止被0除
    if (0 == h) {
        h = 1;
    }
    glViewport(0, 0, w, h);
    fAspect = (GLfloat)w / (GLfloat)h;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // 定義平截頭體, 45度視野,近、遠平面為1.0和425.0
    //gluPerspective(45.0f, fAspect, 100.0, 500);能達到相同的效果
    glFrustum(-100,100,-100,100,200,500);
}

// 計時器函數
void TimerFunc(int value)
{
    //每100毫秒調用一次函數
    glutPostRedisplay();
    glutTimerFunc(100, TimerFunc, 1);
}

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition(200,200);
    glutInitWindowSize(600, 450);
    glutCreateWindow("透視測試");
    glutDisplayFunc(RenderScene);
    glutReshapeFunc(ChangeSize);
    // 設置計時器函數
    glutTimerFunc(100, TimerFunc, 1);
    SetupRC();
    glutMainLoop();

    return 0;
}

    遇到的問題:

    這里顯示的是正面的透視視圖,視平線與z軸重合,而我想要觀察傾斜的俯視圖,用學習進程(9)的方法(使用glulookat函數)並沒有達到效果,而是用了下面的方法:

void ChangeSize(GLsizei w, GLsizei h)//typdef int GLsizei 
{
    GLfloat fAspect;
    // 防止被0除
    if (0 == h) {
        h = 1;
    }
    glViewport(0, 0, w, h);
    fAspect = (GLfloat)w / (GLfloat)h;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // 定義平截頭體, 45度視野,近、遠平面為1.0和425.0
 gluLookAt(0,0.5,1,0,0,0,0,-1,0);
    gluPerspective(45.0f, fAspect, 100.0, 500);
}

    在學習進程(9)中用glulookat()函數改換視點時,是這樣做的:(在本例中並沒有任何效果)

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(0.0, 6.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

    兩個程序的另一個不同點在於:繪制場景時,之前的程序每繪制一次物體就會有一次進出棧的操作,只有一個相對坐標系;而當前程序一次完成,每進行一次坐標系平移或旋轉就換一個相對坐標系,效果是累積的。它解決了月亮繞地球轉時不需考慮太陽的依賴關系。(詳解見后文)

    glulookat()函數和透視變換函數的組合用法依然不清晰。

    3)視口變換:(示例:http://www.cnblogs.com/MenAngel/p/5630475.html)

    當一切工作已經就緒,只需要把像素繪制到屏幕上了。這時候還剩最后一個問題:應該把像素繪制到窗口的哪個區域呢?通常情況下,默認是完整的填充整個窗口,但我們完全可以只填充一半。(即:把整個圖象填充到一半的窗口內)

    Viewport(int x, int y, int width, int height)

    OpenGL使用此函數來定義視口區域,它代表了即將繪制的圖形顯示在2D窗體的位置和大小。

 

    (2)操作矩陣堆棧:

    OpenGL中對圖形的變換是基於矩陣操作的,有可能需要先保存某個矩陣,過一段時間再恢復它:

glPushMatrix()
glPopMatrix()

    OpenGL規定堆棧的容量至少可以容納32個矩陣。

    為什么要使用矩陣堆棧:

    一般我們將需要執行的縮放、平移等操作放在glPushMatrix和glPopMatrix之間。glPushMatrix()和glPopMatrix()的配對使用可以消除上一次的變換對本次變換的影響,使本次變換是以世界坐標系的原點為參考點進行。

    矩陣堆棧對復雜模型運動過程中的多個變換操作之間的聯系與獨立十分有利。因為所有矩陣操作函數如glLoadMatrix()、glMultMatrix()、glLoadIdentity()等只處理當前矩陣或堆棧頂部矩陣,這樣堆棧中下面的其它矩陣就不受影響。

    原理詳解:

1.OpenGL中模型視圖矩陣變換是一個馬爾科夫過程:上一次的變換結果對本次變換有影響,上次模型視圖變換后物體在世界坐標系下的位置是本次模型變換的起點。默認時本次變換和上次變換不獨立。
2.OpenGL物體建模實際上是分兩步走的。第一步,在世界坐標系的原點位置繪制出該物體;第二步,通過模型視圖變換矩陣對世界坐標系原點處的物體進行仿射變換,將該物體移動到世界坐標系的目標位置處。
3.將模型視圖變換放在glPushMatrix和glPopMatrix之間可以使本次變換和上次變換獨立。
4.凡是使用glPushMatrix()和glPopMatrix()的程序一般可以判定是采用世界坐標系建模。既世界坐標系固定,模型視圖矩陣移動物體。

     示例:

#include<GL/glut.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

static int spin = 0;
void init()
{
    glClearColor(0.3,0.3,0.3,1.0);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);
}
void display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    GLfloat position[] = { 0.0, 0.0, 1.5, 1.0 };
    //OpenGL中的模型視圖變換矩陣全是右乘當前變換矩陣
    glPushMatrix(); //1.保存矩陣狀態
    //先畫燈
    glTranslatef(0.0, 0.0, -5.0);
    glPushMatrix();//2.由於先后畫的物體使用同一個世界坐標系,因此這里入棧消除本次操作對下次操作的影響 
    //將0號光源和矩形線框繪制在一起
    glRotated((GLdouble)spin, 1.0, 0.0, 0.0);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glTranslated(0.0, 0.0, 1.5);
    glDisable(GL_LIGHTING);
    glColor3f(1.0, 0.0, 0.0);
    glLineWidth(2.0f);
    glutWireCube(0.1);
    glEnable(GL_LIGHTING);
    //再畫被照物體
    glPopMatrix(); 
    //直接繪制在世界坐標的(0,0,0)位置
    glutSolidSphere(0.5, 40, 40);

    glPopMatrix(); //取出矩陣狀態,返回到單位矩陣
    glFlush();
}
void reshape(int w, int h)
{
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
 //gluLookAt(1.0, 0, 0, 0, 0, 0, 0, 1, 10);這句話取消注釋后是左視圖,俯瞰旋轉圖。
    gluPerspective(40.0, (GLfloat)w / (GLfloat)h, 3, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

}
void mouse(int button, int state, int x, int y)
{
    switch (button)
    {
    case GLUT_LEFT_BUTTON://鼠標左鍵點擊事件
        if (state == GLUT_DOWN)
        {
            spin = (spin + 30) % 360;
            glutPostRedisplay();//場景重新繪制
        }
        break;
    default:
        break;
    }
}
int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(500, 500);
    glutCreateWindow("矩陣堆棧操作");
    init();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMainLoop();
    return 0;
}

 

 

    (3)空間變換繪制實例:(空間變換矩陣堆棧綜合應用,僅作了解)

#include <GL/glut.h>
#include <glaux.h>
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
void myinit(void);
void drawPlane(void);
void CALLBACK elbowAdd(void);
void CALLBACK elbowSubtract(void);
void CALLBACK shoulderAdd(void);
void CALLBACK shoulderSubtract(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);

static int shoulder = 0, elbow = 0;
void CALLBACK elbowAdd(void)
{
    elbow = (elbow + 5) % 360;
}
void CALLBACK elbowSubtract(void)
{
    elbow = (elbow - 5) % 360;
}
void CALLBACK shoulderAdd(void)
{
    shoulder = (shoulder + 5) % 360;
}
void CALLBACK shoulderSubtract(void)
{
    shoulder = (shoulder - 5) % 360;
}
void CALLBACK display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(0.0, 1.0, 1.0);
    glPushMatrix(); // 將此句注釋掉后可以發現上一次的變換結果對當前變換有影響,加上了glPushMatrix的目的是讓各次變換相互獨立。
    glTranslatef(-0.5, 0.0, 0.0); // 將旋轉后的Wirebox向左移動0.5個單位
    glRotatef((GLfloat)shoulder, 0.0, 0.0, 1.0); //看到shoulder是相對於0的絕對角度,不是基於上一次位置的相對角度。
    glTranslatef(1.0, 0.0, 0.0); //Step 1將WireBox向右移動一個單位
    // void auxWireBox(GLdouble width,GLdouble height,GLdouble depth)
    auxWireBox(2.0, 0.2, 0.5); //這個WireBox以x=1為中心,width=2從該中心開始向兩邊各延伸1個單位。
    glTranslatef(1.0, 0.0, 0.0);
    glRotatef((GLfloat)elbow, 0.0, 0.0, 1.0);
    glTranslatef(0.8, 0.0, 0.0);
    auxWireBox(1.6, 0.2, 0.5);
    glPopMatrix(); // 可以看出glPushMatrix和glPopMatrix之間的變換效果被消除。清除上一次對modelview矩陣的修改。
    glFlush();
}
void myinit(void)
{
    glShadeModel(GL_FLAT);
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(65.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -5.0);
}
void main(void)
{
    auxInitDisplayMode(AUX_SINGLE | AUX_RGBA);
    auxInitPosition(200,200, 600, 400);
    auxInitWindow("機械手臂");
    myinit();
    auxKeyFunc(AUX_LEFT, shoulderSubtract);
    auxKeyFunc(AUX_RIGHT, shoulderAdd);
    auxKeyFunc(AUX_UP, elbowAdd);
    auxKeyFunc(AUX_DOWN, elbowSubtract);
    auxReshapeFunc(myReshape);
    auxMainLoop(display);
}

 

    (4)相關知識:

    1)CALLBACK標志:(#define CALLBACK _stdcall)

    用CALLBACK標志一個函數為持續回調函數。

    void CALLBACK changeSize(void)等價於void changeSize()。

    2)auxInitPosition(int,int,int,int):

    有初始化窗體的位置和大小兩重功能:

auxInitPosition(200,200, 600, 400);
等價於
glutInitWindowPosition(200, 200);
glutInitWindowSize(600, 400);

    3)auxKeyFunc();

    向鍵盤按鍵上綁定函數指針,每一次按鍵操作都會引發此函數的一次回調。按鍵包括:

AUX_LEFT
AUX_RIGHT
AUX_UP
AUX_DOWN


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM