- 實現任務目標:
- 使用紋理貼圖,增強可視效果
- 應用坐標變換,實現場景中不同物體重建
- 采用雙緩沖技術,實現場景實時繪制
- 具有一定的鼠標、鍵盤交互功能
- 先放效果
鼠標的交互功能有:右鍵暫停轉動,左鍵繼續轉動,滾輪向前放大,向后縮小
- IDE:opengl實現需要庫函數。用的編譯環境是visual studio。附上一個很好的教程【1】:在vs2017下配置opengl。(vs2019也可以用)
- 一個很好的入門教程【2】:OpenGL入門教程(精)。講得很仔細,通俗易懂。前幾課用到的庫都沒有超過glut的范圍。
- 事實上,對於opengl的實現主要是對於各種庫函數的調用,所以對於各種庫函數的認知很重要。這里也給出一個很好的教程【3】:OpenGL庫函數匯總。
- ok,在看了上面的教程以后肯定對於opengl有了一定認識,尤其是第二個教程中講解得非常仔細。所以本文接下來的內容是建立在對那個教程的學習基礎之上,對一些我在實踐中遇到的問題作出補充。
- 下面就進入正文。
- 所包含的頭文件目錄
1 #include <GL/glut.h> 2 #include <stdlib.h> 3 #include <stdio.h>
- 最基本的功能是當然是創建自己的圖形並顯示出來,如上圖我創建的是日地月系統。需要的函數為display()和main()。
- 這其中很重要的一個知識點就是圖像的視圖變換/模型變換、投影變換和視口變換。有關這塊的內容個人覺得教程【2】中講得不夠清楚,可以參考一些別的教程。比如:OpenGL(六) gluLookAt和gluPerspective函數解析;Opengl---gluLookAt函數詳解。
- 這里要介紹一下opengl中的坐標軸。x軸水平向右為正,y軸豎直向上為正,z軸垂直屏幕向外為正。符合右手定則。
- 2020/5/15 13:06:37 對於圖像的各種變換做一個小的補充
- 視圖變換即設置/改變觀察點的位置,可以這么理解,相當於選擇一個位置和方向設置一台照相機。針對glLookAt()函數而言,它一共有九個參數,3對坐標值。第一對三維坐標是觀察點(照相機)在世界坐標中的位置,第二對三維坐標是被觀察點(物體)的位置。從第一對坐標到第二對坐標的向量其實就指定了照相機的方向。比如說人站在台階上,這是人在世界坐標的位置,然后人可以朝天空看,也可以朝地上看,可以朝北方看,也可以朝南方看。這個方向就是由兩對坐標所造成的向量來決定的。第三對坐標是人頭部的正向,可以指定人站着看,也可以倒立着看,類似於這樣。
- 模型變換則是改變物體本身的位置與方向。用到的函數有glTranslate,glRotate,glScale。這些都是對物體的坐標做變換的,相當於乘以一個變換矩陣。那么這里面有兩個需要注意的點。
- 1 變換的順序是逆向的。也就是說,如果我們寫的順序是先平移再旋轉,那么實際得到的結果應該是先旋轉再平移。所以我們可以用堆棧來實現。對於堆棧的概念不多作解釋了,如果不清楚可以去查數據結構。堆棧用到的函數是glPushMatrix()和glPopMatrix()。用法呢其實就是先聲明push,然后按照想要的順序寫好矩陣函數,最后Pop一下,就能得到想要的結果。
- 2 比如連續使用偏移函數,則第二個偏移的結果其實是在第一次作偏移的基礎上再做偏移的。那么如果我不想這樣算怎么辦呢?清空矩陣。用到的函數就是glLoadIdentity()。它的作用是把當前矩陣設置為單位矩陣。一般在開始做變換前都是需要調用一次這個函數的。
- 那么事實上,在OpenGL中,因為視圖變換和模型變換的效果是類似的,所以這兩個變換放在一個模式里面。在進行這兩種變換前,需要聲明glMatrixMode(GL_MODEVIEW)。看到這個'Matrix'是不是很眼熟呢?沒錯,上面講堆棧函數的時候用到了。相信你們也會有個疑問,如果直接調用堆棧函數,這個堆棧是在哪里的呢?這個堆棧段不需要自己聲明了,但它其實是屬於這個模式的堆棧段。因為在接下來要將的投影變換的模式下也有自己的堆棧段。所以我也是從這個角度理解為什么要分為這兩個模式的原因。
- 投影變換事實上時指定了一個可視空間。相當於你在外部架好了照相機,但你仍然可以在照相機的鏡頭里設置要不要放大看到的景象。在教程【2】里有有關於這個的圖。所以首先我們要聲明模式glMatrixMode(GL_PROJECTION),並單位化矩陣glLoadIdentity()。
- 在這個模式里有透視投影和正投影(我們做3D一般用的都是透視投影)。正投影的函數有glOrtho()和gluOrtho2D(),透視投影的函數有glFrustum()和gluPerspective()。我們最常用的當然就是gluPerspective()啦。
- gluPerspective()中的第一個參數是角度,它相當於人的眼皮要睜開多大,也就是2*仰角(仰角=俯角=1/2這個角度)。第二個參數是比例,應該是跟顯示的屏幕的寬高比例有關(但是這點我不是很確定,只是暫時這么理解,如果有更好的解釋,歡迎在評論區提出)。第三、四個參數則是表示截取的范圍,相當於兩堵牆,兩牆之間的東西能看,牆外的都忽略。這就是透視的意義。
- 那么最后就是視口變換,用到的函數是glViewport()。這個就不多做介紹了。
1 void display(void) 2 { 3 glEnable(GL_DEPTH_TEST); //3、5行代碼中跟DEPTH有關的函數是為了在同一個窗口內創建多個圖像而不會被后創建的圖像覆蓋。 4 glClearColor(0, 0, 0, 1); //設置“空”色。之前看到一個很好的解釋,白紙是白色的,所以白紙上的“空”色為白色。那么信封上的“空”色就是信封的顏色。 5 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //COLOR的那個參數是清除顏色緩存,設為“空”色 6 7 glMatrixMode(GL_PROJECTION); //投影變換 8 glLoadIdentity(); 9 gluPerspective(60.0, 1, 1.0, 100.0); 10 11 glMatrixMode(GL_MODELVIEW); //視圖變換/模型變換 12 glLoadIdentity(); //加載單位矩陣 13 gluLookAt(0.0, 0.0, 60.0, 0, 0, 0, 0.0, 1.0, 0); 15 //太陽 16 glColor3f(1.0, 0, 0); 17 glutSolidSphere(6, 20, 20); 18 //地球 19 glColor3f(0.0, 0, 1.0); 20 glTranslatef(-20.0, 0, 0); //偏移矩陣 21 glutSolidSphere(3, 20, 20); 22 //月球 23 glColor3f(1.0, 1.0, 0); 24 glTranslatef(-6.0, 0, 0); //這里的偏移量是在上面已經偏移的基礎上再進行偏移 25 glutSolidSphere(1, 20, 20); 26 27 glutSwapBuffers(); //雙緩沖函數用到,相關內容看上面的教程【2】里 28 }
- main()中的函數就不具體解釋了,應該都懂
1 int main(int argc, char** argv) 2 { 3 glutInit(&argc, argv); 4 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); 5 glutInitWindowSize(500, 500); 6 glutInitWindowPosition(100, 100); 7 glutCreateWindow("name"); 8 glutDisplayFunc(&display); 9 glutMainLoop(); 10 return 0; 11 }
- 現在在現有程序的基礎上加入動畫需要4步
- 1 加入全局變量
1 static GLfloat angle = 0.0f;
- 2 在display()里面加入旋轉的函數。由於效果是讓整個畫面都轉,這句話我選擇加在gluLookAt()后面。需要加入的語句已標紅。
1 void display(void) 2 { 3 …… …… 4 glMatrixMode(GL_MODELVIEW); 5 glLoadIdentity(); //加載單位矩陣 6 gluLookAt(place_x, 0.0, place_z, 0, 0, 0, 0.0, 1.0, 0); 7 glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋轉,改變的是x軸分量 8 9 glColor3f(1.0, 0, 0); 10 …… …… 11 }
- 3 編寫myIdle()函數
1 void myIdle(void) 2 { 3 angle += 1.8f; 4 if (angle >= 360.0f) 5 angle = 0.0f; 6 display(); 7 }
- 4 在主函數加入glutIdleFunc(&myIdle);可以加在剛剛的display語句下面。
1 int main(int argc, char** argv) 2 { 3 …… …… 4 glutDisplayFunc(&display); 5 glutIdleFunc(&myIdle); 6 …… …… 7 glutMainLoop(); 8 return 0; 9 }
- ok。接下來就要為我們的程序加上紋理了。首先在網上找了兩張星空的網圖。而且,為了方便起見,我把它們的格式改成了24位色的bmp圖片,尺寸為258*258。
- 至於怎么改格式:1 24位色可以對圖片另存為時在下拉菜單里選擇。2 修改尺寸可以用win自帶的圖片編輯器。
- 我的兩張照片分別命名為“wall.bmp”,"ground.bmp"。放在源程序的同一個子目錄里面
- 有關紋理貼圖的詳細內容繼續參考教程【2】。這里附上我寫的程序和說明。
- 一共分為3步。
- 1 搭建矩形框架【對我的程序來說相當於有一個支架,然后把按照點對點的方式紋理圖貼上去】
- 在這一步中先只寫上矩形各個點的坐標,為后面建立矩形做准備。

1 //全局變量 2 static const GLfloat vertex_list[][3] = { 3 - 15.0f, -20.0f, -10.0f, //事實上6、7兩個點是用不到的,作為完整性就一起寫了。貼圖只在背面和底面貼了圖,為了更好的演示效果。 4 40.0f, -20.0f, -10.0f, 5 40.0f, 20.0f, -10.0f, 6 -15.0f, 20.0f, -10.0f, 7 -15.0f, -20.0f, 10.0f, 8 40.0f, -20.0f, 10.0f, 9 -15.0f, 20.0f, 10.0f, 10 40.0f, 20.0f, 10.0f, 11 };
- 2 將紋理圖讀入。寫了一個讀文件的函數,還是參考之前的教程【2】,不多作解釋了。以及一個參考教程:OpenGL(十二) 紋理映射(貼圖)
1 //全局變量 2 #define BMP_Header_Length 54 3 //函數 4 // 函數power_of_two用於判斷一個整數是不是2的整數次冪 5 int power_of_two(int n) 6 { 7 if (n <= 0) 8 return 0; 9 return (n & (n - 1)) == 0; 10 } 11 /* 函數load_texture 12 * 讀取一個BMP文件作為紋理 13 * 如果失敗,返回0,如果成功,返回紋理編號 14 */ 15 GLuint load_texture(const char* file_name) 16 { 17 GLint width, height, total_bytes; 18 GLubyte* pixels = 0; 19 GLuint last_texture_ID = 0, texture_ID = 0; 20 21 // 打開文件,如果失敗,返回 22 FILE* pFile; 23 errno_t err; 24 err = fopen_s(&pFile, file_name, "rb"); //在vs中使用fopen_s()函數的示例。 25 if (!pFile) exit(0); 26 27 // 讀取文件中圖象的寬度和高度 28 fseek(pFile, 0x0012, SEEK_SET); 29 fread(&width, sizeof(width), 1, pFile); 30 fread(&height, sizeof(height), 1, pFile); 31 fseek(pFile, BMP_Header_Length, SEEK_SET); 32 33 // 計算每行像素所占字節數,並根據此數據計算總像素字節數 34 { 35 GLint line_bytes = width * 3; 36 while (line_bytes % 4 != 0) 37 ++line_bytes; 38 total_bytes = line_bytes * height; 39 } 40 41 // 根據總像素字節數分配內存 42 pixels = (GLubyte*)malloc(total_bytes); 43 if (pixels == 0) 44 { 45 fclose(pFile); 46 return 0; 47 } 48 49 // 讀取像素數據 50 if (fread(pixels, total_bytes, 1, pFile) <= 0) 51 { 52 free(pixels); 53 fclose(pFile); 54 return 0; 55 } 56 57 // 對就舊版本的兼容,如果圖象的寬度和高度不是的整數次方,則需要進行縮放 58 // 若圖像寬高超過了OpenGL規定的最大值,也縮放 59 { 60 GLint max; 61 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); 62 if (!power_of_two(width) 63 || !power_of_two(height) 64 || width > max 65 || height > max) 66 { 67 const GLint new_width = 256; 68 const GLint new_height = 256; // 規定縮放后新的大小為邊長的正方形 69 GLint new_line_bytes, new_total_bytes; 70 GLubyte* new_pixels = 0; 71 72 // 計算每行需要的字節數和總字節數 73 new_line_bytes = new_width * 3; 74 while (new_line_bytes % 4 != 0) 75 ++new_line_bytes; 76 new_total_bytes = new_line_bytes * new_height; 77 78 // 分配內存 79 new_pixels = (GLubyte*)malloc(new_total_bytes); 80 if (new_pixels == 0) 81 { 82 free(pixels); 83 fclose(pFile); 84 return 0; 85 } 86 87 // 進行像素縮放 88 gluScaleImage(GL_RGB, 89 width, height, GL_UNSIGNED_BYTE, pixels, 90 new_width, new_height, GL_UNSIGNED_BYTE, new_pixels); 91 92 // 釋放原來的像素數據,把pixels指向新的像素數據,並重新設置width和height 93 free(pixels); 94 pixels = new_pixels; 95 width = new_width; 96 height = new_height; 97 } 98 } 99 100 // 分配一個新的紋理編號 101 glGenTextures(1, &texture_ID); 102 if (texture_ID == 0) 103 { 104 free(pixels); 105 fclose(pFile); 106 return 0; 107 } 108 109 // 綁定新的紋理,載入紋理並設置紋理參數 110 // 在綁定前,先獲得原來綁定的紋理編號,以便在最后進行恢復 111 GLint lastTextureID = last_texture_ID; 112 glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID); 113 glBindTexture(GL_TEXTURE_2D, texture_ID); 114 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 115 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 116 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 117 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 118 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 119 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, 120 GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels); 121 glBindTexture(GL_TEXTURE_2D, lastTextureID); //恢復之前的紋理綁定 122 free(pixels); 123 return texture_ID; 124 }
- 3 在display()中打開狀態機->讀取紋理圖片->搭起矩形框架->貼圖->關閉狀態機。
- 這里踩過的坑就是關於狀態機的開閉問題。如果沒有關閉狀態機,顯示的圖像中之前畫的幾個球都是全黑的。這是因為紋理貼圖會干擾別的顏色。
- 其中用到的glTexCoord2f()函數可以參考百度的這個示例。
1 //全局變量 2 GLuint texGround; 3 GLuint texWall; 4 //函數補充 5 void display(void) 6 { 7 …… ……//之前內容的后面加入一下內容 8 glEnable(GL_TEXTURE_2D); //開啟狀態機 9 texGround = load_texture("ground.bmp"); 10 texWall = load_texture("wall.bmp"); 11 12 //繪制底面 13 glBindTexture(GL_TEXTURE_2D, texGround); 14 glBegin(GL_QUADS); 15 glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[4]); //點對點 16 glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[5]); 17 glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[1]); 18 glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[0]); 19 glEnd(); 20 //繪制立面 21 glBindTexture(GL_TEXTURE_2D, texWall); 22 glBegin(GL_QUADS); 23 glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[0]); 24 glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[1]); 25 glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[2]); 26 glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[3]); 27 glEnd(); 28 glDisable(GL_TEXTURE_2D);//關閉狀態機 29 glutSwapBuffers(); 30 }
- ok。那么到這里我們已經完成了紋理貼圖、雙緩沖繪制和場景重建的任務啦。接下來還有鼠標交互的任務。那么在這里先插入一個新的函數講解:reshape()。
- 關於reshape()的原理呢可以去查查資料。我說說我的理解吧。簡單來說呢就是在你顯示窗口時,如果你拉動邊框,窗口內的圖像不會隨着你拉動而改變。
- 附上一個簡單的圖片示例。
可以看到在右邊的圖中,我拉動了窗口的邊框,則圖像的形狀也改變了。
- reshape()就能在窗體大小被改變時,窗口大小不變,圖像比例也不變。
- 那么同樣的,完成這個功能需要2步。
- 1 寫一個reshape()函數
1 void reshape(int w, int h) 2 { 3 glViewport(0, 0, 500, 500); 4 glMatrixMode(GL_PROJECTION); 5 glLoadIdentity(); 6 gluPerspective(60.0, 1, 1, 100.0); 7 glMatrixMode(GL_MODELVIEW); 8 glLoadIdentity(); 9 gluLookAt(0, 0.0, 60.0, 0, 0, 0, 0.0, 1.0, 0); 10 }
- 2 在main函數中加入一句
1 int main(int argc, char** argv) 2 { 3 …… …… 4 glutDisplayFunc(&display); 5 glutReshapeFunc(&reshape); 6 glutIdleFunc(&myIdle); 7 …… …… 8 }
- ok。最后的最后,要完成鼠標的交互了。
- 我所設置的鼠標的功能包括:右鍵暫停、左鍵繼續;滾輪向上放大,滾輪向下縮小。
- 前兩個改變的是轉過的角度angle,后兩個則跟我們所建立的視圖模型,也就是之前用過glLookAt()函數的參數有關。
- 對於鼠標交互用到的函數及參量是void myMouse(int button, int state, int x, int y);關於這個更多的信息可以自行查找。
- 那么同樣的,完成這個需要2 / 3步。但是我分為兩個部分來講。首先是對於右鍵暫停和左鍵繼續的部分。
- 1 之前的顯示函數里已經有了一個angle變量用來控制角度,所以我們要做的就是停掉這個angle變量的自增,所以我們要停用myIdle函數。
1 void myMouse(int button, int state, int x, int y) 2 { 3 if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) 4 { 5 glutIdleFunc(&myIdle); 6 } 7 if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) 8 { 9 glutIdleFunc(NULL); 10 } 11 }
- 2 在主函數中加入語句。
1 int main(int argc, char** argv) 2 { 3 …… …… 4 glutCreateWindow("name"); 5 glutMouseFunc(&myMouse); 6 glutDisplayFunc(&display); 7 …… …… 8 }
- 對於縮放.
- 1 因為要涉及到之前顯示函數display()中的glLookAt()的改變,所以我們將其中的值設為全局變量。
1 //全局變量 2 static float place_z = 60.0f; 3 static float place_x = 0.0f; 4 //修改函數參數 5 void display(void) 6 { 7 …… …… 8 gluLookAt(place_x, 0.0, place_z, 0, 0, 0, 0.0, 1.0, 0); 9 glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋轉 10 …… …… 11 }
- 2 在之前的鼠標的函數中加入對滾輪的控制語句
1 //全局變量 2 #define GLUT_WHEEL_UP 3 3 #define GLUT_WHEEL_DOWN 4 4 //函數中 5 void myMouse(int button, int state, int x, int y) 6 { 7 …… …… 8 if (state == GLUT_UP && button == GLUT_WHEEL_UP) 9 { 10 glutReshapeFunc(NULL); 11 place_z -= 5.0; 12 display(); 13 } 14 if (state == GLUT_UP && button == GLUT_WHEEL_DOWN) 15 { 16 glutReshapeFunc(NULL); 17 place_z += 5.0; 18 display(); 19 } 20 }
- 這樣就ok啦。到這里就完成了一開始提出四個目標以及一個reshape()函數。效果就如最開始的gif動畫一樣。
- 這里還需要提到的一點是,動畫播放的速度在不同的cpu里是不一樣的,如果太快或太慢可以通過myIdle函數的angle自增的大小來控制。
- 為了避免混亂,最后附上完整的源代碼。
1 #include <GL/glut.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 5 static const GLfloat vertex_list[][3] = { 6 - 15.0f, -20.0f, -10.0f, 7 40.0f, -20.0f, -10.0f, 8 40.0f, 20.0f, -10.0f, 9 -15.0f, 20.0f, -10.0f, 10 -15.0f, -20.0f, 10.0f, 11 40.0f, -20.0f, 10.0f, 12 -15.0f, 20.0f, 10.0f, 13 40.0f, 20.0f, 10.0f, 14 }; 15 GLuint texGround; 16 GLuint texWall; 17 18 #define BMP_Header_Length 54 19 static GLfloat angle = 0.0f; 20 static float place_z = 60.0f; 21 static float place_x = 0.0f; 22 #define GLUT_WHEEL_UP 3 23 #define GLUT_WHEEL_DOWN 4 24 25 // 函數power_of_two用於判斷一個整數是不是2的整數次冪 26 int power_of_two(int n) 27 { 28 if (n <= 0) 29 return 0; 30 return (n & (n - 1)) == 0; 31 } 32 33 /* 函數load_texture 34 * 讀取一個BMP文件作為紋理 35 * 如果失敗,返回0,如果成功,返回紋理編號 36 */ 37 GLuint load_texture(const char* file_name) 38 { 39 GLint width, height, total_bytes; 40 GLubyte* pixels = 0; 41 GLuint last_texture_ID = 0, texture_ID = 0; 42 43 // 打開文件,如果失敗,返回 44 FILE* pFile; 45 errno_t err; 46 err = fopen_s(&pFile, file_name, "rb"); 47 if (!pFile) exit(0); 48 49 // 讀取文件中圖象的寬度和高度 50 fseek(pFile, 0x0012, SEEK_SET); 51 fread(&width, sizeof(width), 1, pFile); 52 fread(&height, sizeof(height), 1, pFile); 53 fseek(pFile, BMP_Header_Length, SEEK_SET); 54 55 // 計算每行像素所占字節數,並根據此數據計算總像素字節數 56 { 57 GLint line_bytes = width * 3; 58 while (line_bytes % 4 != 0) 59 ++line_bytes; 60 total_bytes = line_bytes * height; 61 } 62 63 // 根據總像素字節數分配內存 64 pixels = (GLubyte*)malloc(total_bytes); 65 if (pixels == 0) 66 { 67 fclose(pFile); 68 return 0; 69 } 70 71 // 讀取像素數據 72 if (fread(pixels, total_bytes, 1, pFile) <= 0) 73 { 74 free(pixels); 75 fclose(pFile); 76 return 0; 77 } 78 79 // 對就舊版本的兼容,如果圖象的寬度和高度不是的整數次方,則需要進行縮放 80 // 若圖像寬高超過了OpenGL規定的最大值,也縮放 81 { 82 GLint max; 83 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); 84 if (!power_of_two(width) 85 || !power_of_two(height) 86 || width > max 87 || height > max) 88 { 89 const GLint new_width = 256; 90 const GLint new_height = 256; // 規定縮放后新的大小為邊長的正方形 91 GLint new_line_bytes, new_total_bytes; 92 GLubyte* new_pixels = 0; 93 94 // 計算每行需要的字節數和總字節數 95 new_line_bytes = new_width * 3; 96 while (new_line_bytes % 4 != 0) 97 ++new_line_bytes; 98 new_total_bytes = new_line_bytes * new_height; 99 100 // 分配內存 101 new_pixels = (GLubyte*)malloc(new_total_bytes); 102 if (new_pixels == 0) 103 { 104 free(pixels); 105 fclose(pFile); 106 return 0; 107 } 108 109 // 進行像素縮放 110 gluScaleImage(GL_RGB, 111 width, height, GL_UNSIGNED_BYTE, pixels, 112 new_width, new_height, GL_UNSIGNED_BYTE, new_pixels); 113 114 // 釋放原來的像素數據,把pixels指向新的像素數據,並重新設置width和height 115 free(pixels); 116 pixels = new_pixels; 117 width = new_width; 118 height = new_height; 119 } 120 } 121 122 // 分配一個新的紋理編號 123 glGenTextures(1, &texture_ID); 124 if (texture_ID == 0) 125 { 126 free(pixels); 127 fclose(pFile); 128 return 0; 129 } 130 131 // 綁定新的紋理,載入紋理並設置紋理參數 132 // 在綁定前,先獲得原來綁定的紋理編號,以便在最后進行恢復 133 GLint lastTextureID = last_texture_ID; 134 glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID); 135 glBindTexture(GL_TEXTURE_2D, texture_ID); 136 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 137 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 138 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 139 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 140 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 141 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, 142 GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels); 143 glBindTexture(GL_TEXTURE_2D, lastTextureID); //恢復之前的紋理綁定 144 free(pixels); 145 return texture_ID; 146 } 147 void display(void) 148 { 149 glEnable(GL_DEPTH_TEST); 150 glClearColor(0, 0, 0, 1); 151 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 152 153 glMatrixMode(GL_PROJECTION); 154 glLoadIdentity(); 155 gluPerspective(60.0, 1, 1.0, 100.0); 156 157 glMatrixMode(GL_MODELVIEW); 158 glLoadIdentity(); //加載單位矩陣 159 gluLookAt(place_x, 0.0, place_z, 0, 0, 0, 0.0, 1.0, 0); 160 glRotatef(angle, 0.0f, 1.0f, 0.0f); //旋轉 161 162 glColor3f(1.0, 0, 0); 163 glutSolidSphere(6, 20, 20); 164 165 glColor3f(0.0, 0, 1.0); 166 glTranslatef(-20.0, 0, 0); 167 glutSolidSphere(3, 20, 20); 168 169 glColor3f(1.0, 1.0, 0); 170 glTranslatef(-6.0, 0, 0); 171 glutSolidSphere(1, 20, 20); 172 173 glEnable(GL_TEXTURE_2D); 174 texGround = load_texture("ground.bmp"); 175 texWall = load_texture("wall.bmp"); 176 177 //繪制底面 178 glBindTexture(GL_TEXTURE_2D, texGround); 179 glBegin(GL_QUADS); 180 glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[4]); 181 glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[5]); 182 glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[1]); 183 glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[0]); 184 glEnd(); 185 //繪制立面 186 glBindTexture(GL_TEXTURE_2D, texWall); 187 glBegin(GL_QUADS); 188 glTexCoord2f(0.0f, 0.0f); glVertex3fv(vertex_list[0]); 189 glTexCoord2f(1.0f, 0.0f); glVertex3fv(vertex_list[1]); 190 glTexCoord2f(1.0f, 1.0f); glVertex3fv(vertex_list[2]); 191 glTexCoord2f(0.0f, 1.0f); glVertex3fv(vertex_list[3]); 192 glEnd(); 193 glDisable(GL_TEXTURE_2D); 194 glutSwapBuffers(); 195 } 196 void myIdle(void) 197 { 198 angle += 1.8f; 199 if (angle >= 360.0f) 200 angle = 0.0f; 201 display(); 202 } 203 void myMouse(int button, int state, int x, int y) 204 { 205 if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) 206 { 207 glutIdleFunc(&myIdle); 208 } 209 if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) 210 { 211 glutIdleFunc(NULL); 212 } 213 if (state == GLUT_UP && button == GLUT_WHEEL_UP) 214 { 215 glutReshapeFunc(NULL); 216 place_z -= 5.0; 217 display(); 218 } 219 if (state == GLUT_UP && button == GLUT_WHEEL_DOWN) 220 { 221 glutReshapeFunc(NULL); 222 place_z += 5.0; 223 display(); 224 } 225 } 226 227 void reshape(int w, int h) 228 { 229 glViewport(0, 0, 500, 500); 230 glMatrixMode(GL_PROJECTION); 231 glLoadIdentity(); 232 gluPerspective(60.0, 1, 1, 100.0); 233 glMatrixMode(GL_MODELVIEW); 234 glLoadIdentity(); 235 gluLookAt(0, 0.0, 60.0, 0, 0, 0, 0.0, 1.0, 0); 236 } 237 int main(int argc, char** argv) 238 { 239 glutInit(&argc, argv); 240 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); 241 glutInitWindowSize(500, 500); 242 glutInitWindowPosition(100, 100); 243 glutCreateWindow("name"); 244 glutMouseFunc(&myMouse); 245 glutDisplayFunc(&display); 246 glutReshapeFunc(&reshape); 247 glutIdleFunc(&myIdle); 248 glutMainLoop(); 249 return 0; 250 }
