學了半學期的圖形學,除了幾個用python或是matlab比較方便的實驗外,用的大多數是opengl,在這總結一下紋理貼圖實驗中opengl的用法。
1、編譯器連接靜態庫
有用到glaux.h的程序,在加入相應的.h、.lib文件后,需要加入兩行代碼強行連接靜態庫:
#pragma comment(lib, "glaux")
#pragma comment(lib, "legacy_stdio_definitions")
另外關於glaux.h,我想吐槽的是在csdn賣下載的人是有多想賺錢?……這里我把找到的glaux.h的下載鏈接貼出來,需要自取:
鏈接:https://pan.baidu.com/s/1-P44eWXlehmd9jPYuXNiHw 提取碼:hbi6
2、畫一個簡單立方體
最終目的是要把貼圖映射到一個立方體上,首先我們需要構建一個不存在任何紋理的立方體。首先構建繪制函數:
void Draw(void){ glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); glTranslatef(0.0f, 0.0f, -5.0f); glBegin(GL_QUADS); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glVertex3f(1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(1.0f, -1.0f, -1.0f); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glEnd(); glFlush(); }
首先使用大家都喜歡的(必須用的)清屏函數glClear將窗口清除成當前幀的顏色,不用的話滿屏漆黑。glLoadIdentity()被用於重置觀察模型矩陣(感覺這幾個函數挺固定搭配的?);glTranslatef()用於移動模型,三個參數分別對應x,y,z軸線模型的移動,這里向負方向移動了5f。不挪 或者 向正方向挪,只會造成我們的視點根本無法觀察到模型,負方向5f的位置相對大小比較合適。
模板一樣的東西套完以后,可以注意到glBegin()和glEnd()之間有一萬個(不是)glVertex3f的堆疊,用於確定立方體的點。glBegin的參數QUADS表示接下來要繪制以四個點為一組的四邊形,其他參數的含義如下:
GL_POINTS:把每個頂點作為一個點進行處理,頂點n定義了點n,繪制N個點。
GL_LINES: 把每個頂點作為一個獨立的線段,頂點2n-1和2n之間定義了n條線段,繪制N/2條線段
GL_LINE_STRIP:繪制從第一個頂點到最后一個頂點依次相連的一組線段,第n和n+1個頂點定義了線段n,繪制n-1條線段。
GL_LINE_LOOP: 繪制從第一個頂點到最后一個頂點依次相連的一組線段,然后最后一個頂點和第一個頂點相連,第n和n+1個頂點定義了線段n,繪制n條線段。
GL_TRIANGLES: 把每個頂點作為一個獨立的三角形,頂點3n-2,3n-1和3n定義了第n個三角形,繪制了N/3個三角形。
GL_TRIANGLE_STPIP:繪制一組相連的三角形,對於奇數n,頂點n,n+1,和n+2定義了第n個三角形;對於偶數n,頂點n+1,n和n+2定義了第n個三角形,繪制N-2個三角 形。
GL_QUAD_STRIP:繪制一組相連的四邊形。每個四邊形是由一對頂點及其后給定的一對頂點共同確定的。頂點2n-1,2n,2n+2和2n+1定義了第n個四邊形,繪制了N/2-1個 四邊形。
GL_POLYGON:繪制了一個凸多邊形。頂點1到n定義了這個多邊形。
而glVertex3f()的三個函數對應x,y,z軸的坐標,共4*6個glVertex3f()函數調用,對應立方體6個面的4個點。最后使用glFlush()清除緩存。
再構建一個display函數用於展示,將繪制函數置於display函數中:
void display(void){ glClear(GL_COLOR_BUFFER_BIT); Draw(); glutSwapBuffers(); }
清屏函數還是熟悉的味道,只是多了glutSwapBuffers()用於交換緩沖區和顯示圖形。
接下來是一個實現繪圖之后轉換矩陣模式的函數,用於主函數中的回調函數glutReshapeFunc()中:
void reshape(GLsizei w, GLsizei h){ glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); gluPerspective(60, (GLfloat)w / h, 0, 100);
glMatrixMode(GL_MODELVIEW);
}
繪圖以后先將矩陣模式調為投影模式,在投影模式下使用gluPerspective調整投影模型(不同模式下能執行的操作不一樣),四個參數分別代表視野范圍(值越大,視野范圍越寬闊),裁剪面的寬w高h比(影響到視野的截面有多大),近裁剪面到眼睛的距離,遠裁剪面到眼睛的距離(都不能設置設置為負值)。設置完之后把矩陣設置為視圖模式。
最后是main()函數:
int main(int argc, char* argv[]){ glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); glutInitWindowSize(600, 600); glutInitWindowPosition(100, 100); glutCreateWindow("大立方體?"); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMainLoop(); return 0; }
main函數的固定搭配glutInit(),之后設置顯示模式為RGB單緩存區,分別設置窗口大小和位置之后,使用glutDisplayFunc()調用display(),glutReshapeFunc調用reshape,所得圖形是這樣:
呃…這不是個正方形嘛,其實只是我們的視點正對着立方體 而已。
接下來給它貼上紋理,首先加入三軸的旋轉變量與一個紋理數組:
GLfloat xrot = 0;
GLfloat yrot = 0;
GLfloat zrot = 0;
GLuint texture[1];
讀入貼圖文件:
AUX_RGBImageRec* LoadBMP(const char Filename[7]){ FILE* File = NULL; if (!Filename){ return NULL; } File = fopen(Filename, "r"); if (File) { fclose(File); return auxDIBImageLoadA(Filename); } return NULL; }
聽說是固定搭配?
有了讀取位圖的函數,我們需要將它轉換成紋理:
int LoadGLTextures(GLuint* texture, const char bmp_file_name[7], int texture_id){ int re=1; AUX_RGBImageRec* TextureImage[1]; memset(TextureImage, 0, sizeof(void*) * 1); if (TextureImage[0] = LoadBMP(bmp_file_name)){ re = 0; glGenTextures(texture_id, texture); glBindTexture(GL_TEXTURE_2D, *texture); glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } if (TextureImage[0]){ if (TextureImage[0]->data){ free(TextureImage[0]->data); } free(TextureImage[0]); } else printf("紋理不存在"); return re; }
首先聲明一個AUX_RGBImageRec類型的指針,memset初始化之后調用之前的函數載入位圖,glGenTextures生成紋理,參數分別為生成紋理的數量與指針,使用glBindTexture 將一個命名的紋理綁定到一個紋理目標上,glTexImage2D功能是根據指定的參數,生成一個2D紋理,這里的長、寬都必須是2的整數次方,glTexParameteri……是神秘的紋理過濾函數,對我們的紋理分別進行了一次縮小與放大的線性過濾。紋理生成之后,釋放原圖像的空間。
接下來修改繪制函數,將紋理映射到六個面上:
void Draw(void){ glClear(GL_COLOR_BUFFER_BIT ); glLoadIdentity();
glTranslatef(0.0f, 0.0f, -5.0f); glRotatef(xrot, 1.0f, 0.0f, 0.0f); glRotatef(yrot, 0.0f, 1.0f, 0.0f); glRotatef(zrot, 0.0f, 0.0f, 1.0f); glBindTexture(GL_TEXTURE_2D, texture[0]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd(); glFlush(); }
glRotatef用來設置opengl中繪制實體的自轉方式,為我們的模型動起來做准備。比起之前的draw函數,這里多了glTexCoord2f,兩參數分別為X軸坐標,Y軸坐標,用於繪制圖形時指定紋理的坐標,其中x、y的坐標:0.0是紋理的左側,1.0是紋理的右側;0.0是紋理的底部,1.0是紋理的頂部。
void init(void){ glClearColor(1.0, 1.0, 1.0, 0.0); glCullFace(GL_BACK); glEnable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); LoadGLTextures(&texture[0], "111_.bmp", 1); }
使用清屏函數之后,使用glCullFace禁用多邊形背面上的光照、陰影和顏色計算及操作,消除不必要的渲染計算,使用glEnable開啟之前的消除操作。最后載入之前生成的紋理。
給出我們修改以后的主函數:
int main(int argc, char* argv[]){ glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); glutInitWindowSize(600, 600); glutInitWindowPosition(100, 100); glutCreateWindow("貼圖立方體?"); init(); LoadGLTextures(&texture[0], "111_.bmp", 1); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMainLoop(); return 0; }
可以注意到新增了初始化函數與紋理載入函數,結果為:

看起來還只是一個平面圖?為了讓它轉起來,寫入鼠標響應函數:
void RotateRect() { xrot += 1.0f; glutPostRedisplay(); yrot += 1.0f; glutPostRedisplay(); zrot += 1.0f; glutPostRedisplay(); Sleep(10); } void OnMouse(int button, int state, int x, int y){ if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN){ glutIdleFunc(RotateRect); } if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN){ glutIdleFunc(NULL); } }
在RotateRect中修改各個維度的旋轉量,使用glutPostRedisplay進行重繪,為了不使它暴走旋轉,調用winbase.h中的Sleep函數,變量改變一次暫停程序10毫秒。定義鼠標響應函數,左鍵啟動,右鍵暫停(返回NULL),在回調函數glutIdleFunc中調用RotateRect功能。
修改主函數:
int main(int argc, char* argv[]){ glutInit(&argc, argv); //固定格式
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); glutInitWindowSize(600, 600); glutInitWindowPosition(100, 100); glutCreateWindow("貼圖立方體?"); init(); LoadGLTextures(&texture[0], "111_.bmp", 1); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMouseFunc(&OnMouse); glutMainLoop(); return 0; }
到這里 紋理映射實驗就基本結束了。
參考博客:
https://blog.csdn.net/dcrmg/article/details/53223680?utm_source=blogxgwz2
https://www.cnblogs.com/OctoptusLian/p/7366844.html#commentform
https://blog.csdn.net/tyxkzzf/article/details/40921713
https://blog.csdn.net/yangmeng900816/article/details/46816007
