前言
這是從零開始openGL系列文章的第二篇,在上篇文章中介紹了基本的環境配置,這篇文章將介紹如何繪制基本圖形(圓、三角形、立方體、圓柱、圓錐)。
基本框架
下面這里我先給出opengl的3D繪圖的基本框架
#include <windows.h> #include <string.h> #include <stdlib.h> #include <gl\glui.h> #include <math.h> #include "common.h" int g_xform_mode = TRANSFORM_NONE; int g_main_window; double g_windows_width, g_windows_height; CObj g_obj; //the lighting static GLfloat g_light0_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f};//環境光 static GLfloat g_light0_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};//散射光 static GLfloat g_light0_specular[] = {1.0f,1.0f,1.0f,1.0f}; //鏡面光 static GLfloat g_light0_position[] = {0.0f, 0.0f, 100.0f, 0.0f};//光源的位置。第4個參數為1,表示點光源;第4個參數量為0,表示平行光束{0.0f, 0.0f, 10.0f, 0.0f} static GLfloat g_material[] = {0.96f, 0.8f, 0.69f, 1.0f};//材質 static GLfloat g_rquad = 0; static GLfloat g_rquad_x = 0; static GLfloat g_rquad_y = 0; static float g_x_offset = 0.0; static float g_y_offset = 0.0; static float g_z_offset = 0.0; static float g_scale_size = 1; static int g_press_x; //鼠標按下時的x坐標 static int g_press_y; //鼠標按下時的y坐標 const int n = 1000; const GLfloat R = 0.5f; const GLfloat Pi = 3.1415926536f; int g_view_type = VIEW_FLAT; int g_draw_content = SHAPE_TRIANGLE; void DrawTriangle() {//繪制三角形 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBegin(GL_TRIANGLES); glNormal3f(0.0f, 0.0f, 1.0f); //指定面法向 glVertex3f( 0.0f, 1.0f, 0.0f); // 上頂點 glVertex3f(-1.0f,-1.0f, 0.0f); // 左下 glVertex3f( 1.0f,-1.0f, 0.0f); // 右下 glEnd(); glFlush(); } void DrawCube() {//繪制立方體 glBegin(GL_QUADS); glNormal3f( 0.0f, 0.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); //前---------------------------- glNormal3f( 0.0f, 0.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); //后---------------------------- glNormal3f( 0.0f, 1.0f, 0.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); //上---------------------------- glNormal3f( 0.0f,-1.0f, 0.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); //下---------------------------- glNormal3f( 1.0f, 0.0f, 0.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); //右---------------------------- glNormal3f(-1.0f, 0.0f, 0.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(); } void DrawCircle() {//繪制圓 glBegin(GL_POLYGON); glNormal3f(0.0f, 0.0f, 1.0f); for (int i = 0; i < n; ++i) { glVertex2f(R*cos(2 * Pi / n * i), R*sin(2 * Pi / n * i)); } glEnd(); } void DrawCylinder() {//繪制圓柱 } void DrawTorus() { }void myInit() { glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//用白色清屏 glLightfv(GL_LIGHT0, GL_AMBIENT, g_light0_ambient);//設置場景的環境光 glLightfv(GL_LIGHT0, GL_DIFFUSE, g_light0_diffuse);//設置場景的散射光 glLightfv(GL_LIGHT0, GL_POSITION, g_light0_position);//設置場景的位置 glMaterialfv(GL_FRONT, GL_DIFFUSE, g_material);//指定用於光照計算的當前材質屬性 glEnable(GL_TEXTURE_2D); glEnable(GL_LIGHTING);//開啟燈光 glEnable(GL_LIGHT0);//開啟光照0 glShadeModel(GL_SMOOTH); //設置着色模式為光滑着色 glEnable(GL_DEPTH_TEST);//啟用深度測試 glMatrixMode(GL_MODELVIEW); //指定當前矩陣為模型視景矩陣 glLoadIdentity(); //將當前的用戶坐標系的原點移到了屏幕中心:類似於一個復位操作 gluLookAt(0.0, 0.0, 8.0, 0, 0, 0, 0, 1.0, 0);//該函數定義一個視圖矩陣,並與當前矩陣相乘. //第一組eyex, eyey,eyez 相機在世界坐標的位置;第二組centerx,centery,centerz 相機鏡頭對准的物體在世界坐標的位置;第三組upx,upy,upz 相機向上的方向在世界坐標中的方向 }void myGlutDisplay() //繪圖函數, 操作系統在必要時刻就會對窗體進行重新繪制操作 { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); //清除顏色緩沖以及深度緩沖 glEnable(GL_NORMALIZE); //打開法線向量歸一化,確保了法線的長度為1 glMatrixMode(GL_MODELVIEW);//模型視圖矩陣 glPushMatrix(); //壓入當前矩陣堆棧 if (g_draw_content == SHAPE_MODEL) {//繪制模型 } else if (g_draw_content == SHAPE_TRIANGLE) //畫三角形 { glLoadIdentity(); glTranslatef(0.0f, 0.0f, -6.0f); DrawTriangle(); } else if(g_draw_content == SHAPE_CUBE) //畫立方體 { glLoadIdentity(); glTranslatef(0.0f, 0.0f, -6.0f); glRotatef(g_rquad, g_rquad, g_rquad, 1.0f); // 在XYZ軸上旋轉立方體 DrawCube(); g_rquad+=0.2f;// 增加旋轉變量 } else if (g_draw_content == SHAPE_CIRCLE) // 畫圓 { glLoadIdentity(); glTranslatef(0.0f, 0.0f, -6.0f); DrawCircle(); } else if (g_draw_content == SHAPE_CYLINDER) {//TODO: 添加畫圓柱的代碼 } else if (g_draw_content == SHAPE_TORUS) {//TODO:添加畫圓環的代碼 } glPopMatrix(); glutSwapBuffers(); //雙緩沖 } void myGlutReshape(int x,int y) //當改變窗口大小時的回調函數 { if (y == 0) { y = 1; } g_windows_width = x; g_windows_height = y; double xy_aspect = (float)x / (float)y; GLUI_Master.auto_set_viewport(); //自動設置視口大小 glMatrixMode( GL_PROJECTION );//當前矩陣為投影矩陣 glLoadIdentity(); gluPerspective(60.0, xy_aspect, 0.01, 1000.0);//視景體 glutPostRedisplay(); //標記當前窗口需要重新繪制 } void myGlutKeyboard(unsigned char Key, int x, int y) {//鍵盤時間回調函數 } void myGlutMouse(int button, int state, int x, int y) { if (state == GLUT_DOWN) //鼠標的狀態為按下 { g_press_x = x; g_press_y = y; if (button == GLUT_LEFT_BUTTON) {//按下鼠標的左鍵表示對模型進行旋轉操作 g_xform_mode = TRANSFORM_ROTATE; } else if (button == GLUT_RIGHT_BUTTON) {//按下鼠標的右鍵表示對模型進行平移操作 g_xform_mode = TRANSFORM_TRANSLATE; } else if (button == GLUT_MIDDLE_BUTTON) {//按下鼠標的滑輪表示按下鼠標的右鍵表示對模型進行縮放操作 g_xform_mode = TRANSFORM_SCALE; } } else if (state == GLUT_UP) {//如果沒有按鼠標,則不對模型進行任何操作 g_xform_mode = TRANSFORM_NONE; } } void myGlutMotion(int x, int y) //處理當鼠標鍵摁下時,鼠標拖動的事件 { if (g_xform_mode == TRANSFORM_ROTATE) //旋轉 {//TODO:添加鼠標移動控制模型旋轉參數的代碼 } else if(g_xform_mode == TRANSFORM_SCALE) //縮放 {//TODO:添加鼠標移動控制模型縮放參數的代碼 } else if(g_xform_mode == TRANSFORM_TRANSLATE) //平移 {//TODO:添加鼠標移動控制模型平移參數的代碼 } // force the redraw function glutPostRedisplay(); } void myGlutIdle(void) //空閑回調函數 { if ( glutGetWindow() != g_main_window ) glutSetWindow(g_main_window); glutPostRedisplay(); } void glui_control(int control ) //處理控件的返回值 { switch(control) { case CRTL_LOAD://選擇“open”控件 loadObjFile(); g_draw_content = SHAPE_MODEL; break; case CRTL_CHANGE://選擇Type面板 if (g_view_type == VIEW_POINT) { glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); // 設置兩面均為頂點繪制方式 } else if (g_view_type == VIEW_WIRE) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // 設置兩面均為線段繪制方式 } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 設置兩面為填充方式 } break; case CRTL_TRIANGLE: g_draw_content = SHAPE_TRIANGLE; break; case CRTL_CUBE: g_draw_content = SHAPE_CUBE; break; case CRTL_CIRCLE: g_draw_content = SHAPE_CIRCLE; break; case CRTL_CYLINDER: g_draw_content = SHAPE_CYLINDER; break; case CRTL_CONE: g_draw_content = SHAPE_TORUS; break; case CRTL_MODEL: g_draw_content = SHAPE_MODEL; break; default: break; } } void myGlui() { GLUI_Master.set_glutDisplayFunc( myGlutDisplay ); //注冊渲染事件回調函數, 系統在需要對窗體進行重新繪制操作時調用 GLUI_Master.set_glutReshapeFunc( myGlutReshape ); //注冊窗口大小改變事件回調函數 GLUI_Master.set_glutKeyboardFunc( myGlutKeyboard );//注冊鍵盤輸入事件回調函數 glutMotionFunc( myGlutMotion);//注冊鼠標移動事件回調函數 GLUI_Master.set_glutMouseFunc( myGlutMouse );//注冊鼠標點擊事件回調函數 GLUI_Master.set_glutIdleFunc(myGlutIdle); //為GLUI注冊一個標准的GLUT空閑回調函數,當系統處於空閑時,就會調用該注冊的函數 //GLUI GLUI *glui = GLUI_Master.create_glui_subwindow( g_main_window, GLUI_SUBWINDOW_RIGHT); //新建子窗體,位於主窗體的右部 new GLUI_StaticText(glui, "GLUI" ); //在GLUI下新建一個靜態文本框,輸出內容為“GLUI” new GLUI_Separator(glui); //新建分隔符 new GLUI_Button(glui,"Open", CRTL_LOAD, glui_control); //新建按鈕控件,參數分別為:所屬窗體、名字、ID、回調函數,當按鈕被觸發時,它會被調用. new GLUI_Button(glui, "Quit", 0,(GLUI_Update_CB)exit );//新建退出按鈕,當按鈕被觸發時,退出程序 GLUI_Panel *type_panel = glui->add_panel("Type" ); //在子窗體glui中新建面板,名字為“Type” GLUI_RadioGroup *radio = glui->add_radiogroup_to_panel(type_panel, &g_view_type, CRTL_CHANGE, glui_control); //在Type面板中添加一組單選按鈕 glui->add_radiobutton_to_group(radio, "points"); glui->add_radiobutton_to_group(radio, "wire"); glui->add_radiobutton_to_group(radio, "flat"); GLUI_Panel *draw_panel = glui->add_panel("Draw" ); //在子窗體glui中新建面板,名字為“Draw” new GLUI_Button(draw_panel,"Triangle",CRTL_TRIANGLE,glui_control); new GLUI_Button(draw_panel,"Cube",CRTL_CUBE,glui_control); new GLUI_Button(draw_panel,"Circle",CRTL_CIRCLE,glui_control); new GLUI_Button(draw_panel,"Cylinder",CRTL_CYLINDER,glui_control); new GLUI_Button(draw_panel,"Torus",CRTL_CONE,glui_control); new GLUI_Button(draw_panel,"Model",CRTL_MODEL,glui_control); glui->set_main_gfx_window(g_main_window ); //將子窗體glui與主窗體main_window綁定,當窗體glui中的控件的值發生過改變,則該glui窗口被重繪 GLUI_Master.set_glutIdleFunc( myGlutIdle ); } int main(int argc, char* argv[]) //程序入口 { /****************************************/ /* Initialize GLUT and create window */ /****************************************/ freopen("log.txt", "w", stdout);//重定位,將輸出放入log.txt文件中 glutInit(&argc, argv);//初始化glut glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);//初始化渲染模式 glutInitWindowPosition(200, 200); //初始化窗口位置 glutInitWindowSize(800, 600); //初始化窗口大小 g_main_window = glutCreateWindow("Model Viewer"); //創建主窗體Model Viewer myGlui(); myInit(); glutMainLoop();//進入glut消息循環 return EXIT_SUCCESS; }

#ifndef COMMON #define COMMON #define VIEW_POINT 0x00 #define VIEW_WIRE 0x01 #define VIEW_FLAT 0x02 #define CRTL_LOAD 0x00 #define CRTL_CHANGE 0x01 #define CRTL_TRIANGLE 0x02 #define CRTL_CUBE 0x03 #define CRTL_CIRCLE 0x04 #define CRTL_CYLINDER 0x05 #define CRTL_CONE 0x06 #define CRTL_MODEL 0x07 #define SHAPE_TRIANGLE 0x00 #define SHAPE_CUBE 0x01 #define SHAPE_CIRCLE 0x02 #define SHAPE_CYLINDER 0x03 #define SHAPE_TORUS 0x04 #define SHAPE_MODEL 0x05 #define TRANSFORM_NONE 0x51 #define TRANSFORM_ROTATE 0x52 #define TRANSFORM_SCALE 0x53 #define TRANSFORM_TRANSLATE 0x54 #endif
運行這段代碼可以得到如下所示的結果
圖形繪制
在上面那段代碼中,已經給出了三角形、圓、正方體的繪制代碼,下面還將介紹圓柱與圓環的繪制
圓
在opengl中並不能直接繪制圓,那么,此時想到了極限的方法,如果把圓分割成很多個扇形,這個扇形的角度足夠小的話,那么曲線自然可以看作直線。有了這個思路,代碼就很好寫了。
void DrawCircle() {//繪制圓 glBegin(GL_POLYGON); glNormal3f(0.0f, 0.0f, 1.0f); for (int i = 0; i < n; ++i) { glVertex2f(R*cos(2 * Pi / n * i), R*sin(2 * Pi / n * i)); } glEnd(); }
三角形
三角形的繪制就十分的簡單了,確定三個頂點,然后連線
void DrawTriangle() {//繪制三角形 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBegin(GL_TRIANGLES); glNormal3f(0.0f, 0.0f, 1.0f); //指定面法向 glVertex3f( 0.0f, 1.0f, 0.0f); // 上頂點 glVertex3f(-1.0f,-1.0f, 0.0f); // 左下 glVertex3f( 1.0f,-1.0f, 0.0f); // 右下 glEnd(); glFlush(); }
立方體
原理同三角形,確定八個頂點坐標,然后連線,不過這里要注意的是立方體為3D圖形,要展示光照效果的話需要在繪制的時候確定各個面的法向量。
void DrawCube() {//繪制立方體 glBegin(GL_QUADS); glNormal3f( 0.0f, 0.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); //前---------------------------- glNormal3f( 0.0f, 0.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); //后---------------------------- glNormal3f( 0.0f, 1.0f, 0.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); //上---------------------------- glNormal3f( 0.0f,-1.0f, 0.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); //下---------------------------- glNormal3f( 1.0f, 0.0f, 0.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); //右---------------------------- glNormal3f(-1.0f, 0.0f, 0.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(); }
圓柱
對於圓柱的繪制,思想同圓十分相似,就是分割。不過需要注意的是上下兩個圓面和一個側面需要分開來繪制。這里需要思考的是這個側面該如何繪制呢?想象以下,把圓柱側面展開,我們得到的是一個矩形,那分割成小片段的話也就是矩形了,即繪制無數個矩形,然后拼接形成側面。
注:繪制時注意法向量的選取
void DrawCylinder() {//繪制圓柱 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glBegin(GL_POLYGON); glNormal3f(0.0f, 1.0f, 0.0f); for (int i = 1; i < n; i++) { glVertex3f(R*cos(2 * Pi / n * i), 1.0f, R*sin(2 * Pi / n * i)); } glEnd(); glBegin(GL_POLYGON); glNormal3f(0.0f, -1.0f, 0.0f); for (int i = 1; i < n; i++) { glVertex3f(R*cos(2 * Pi / n * i), 0.0f, R*sin(2 * Pi / n * i)); } glEnd(); glBegin(GL_QUADS); for (int i = 1; i <= n; i++) { glNormal3f(R*cos(2 * Pi / n * i), 0.0f, R*sin(2 * Pi / n * i)); glVertex3f(R*cos(2 * Pi / n * i), 1.0f, R*sin(2 * Pi / n * i)); glVertex3f(R*cos(2 * Pi / n * i), 0.0f, R*sin(2 * Pi / n * i)); glNormal3f(R*cos(2 * Pi / n * (i + 1)), 0.0f, R*sin(2 * Pi / n * (i + 1))); glVertex3f(R*cos(2 * Pi / n * (i + 1)), 0.0f, R*sin(2 * Pi / n * (i + 1))); glVertex3f(R*cos(2 * Pi / n * (i + 1)), 1.0f, R*sin(2 * Pi / n * (i + 1))); } glEnd(); }
圓環
圓環的繪制稍微有些麻煩,先來看看下面這個圓環的線圖
這里的難點就是各點的坐標表示,首先我們需要做的是把圓環壓縮成一個圓面
壓縮之后的表示為 R+r*cos(θ),然后再把壓縮完后的點映射到x y軸上
X軸:(R+r*cos(θ))*cosα
Y軸:(R+r*cos(θ))*sinα
Z軸:r*sin(θ)
這樣,我們的圓環就可以實現了
void DrawTorus() { int num = n / 50; for (int i = 0; i < num; i++) { glBegin(GL_QUAD_STRIP); for (int j = 0; j <= num; j++) { for (int k = 1; k >= 0; k--) { double s = (i + k) % num + 0.5; double t = j % num; glNormal3f(cos(2 * Pi / num * s) * cos(2 * Pi / num * t), cos(2 * Pi / num * s)*sin(2 * Pi / num * t), sin(2 * Pi / num * s)); glVertex3f((1 + R*cos(2 * Pi / num * s))*cos(2 * Pi / num * t), (1 + R*cos(2 * Pi / num * s))*sin(2 * Pi / num * t), R*sin(2 * Pi / num * s)); } } glEnd(); } }
小節
以上介紹了如何使用opengl繪制基本圖形,下篇文章中將介紹如何使用opengl加載繪制模型,以及鼠標交互的實現。