題外話
隨著學習的增長,越來越覺得自己很水.關於上一篇博文中推薦用一個 學習opengl的 基於VS2015的 simplec框架.存在
一些問題.
1.這個框架基於VS 的Debug 模式下,沒有考慮Release版本
2.這個版本中chead,c基礎頭文件中有些宏設計的不好,例如
//4.0 控制台打印錯誤信息 #ifndef CERR #define CERR(fmt,...) \ fprintf(stderr,fmt,##__VA_ARGS__),putchar('\n') #endif/* !CERR */ //4.1 控制台打印錯誤信息並退出 #ifndef ERR_EXIT #define ERR_EXIT(fmt,...) \ CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE) #endif/* !ERR */
更加合理的是
//錯誤打印宏
#define CERR_EXIT(fmt,...) \
fprintf(stderr,"[%s:%d]", __FILE__, __LINE__), fprintf(stderr, fmt, ##__VA_ARGS__), fputs("\r\n",stderr)
這裡的輔助宏需要重新設計
3.原先的sc_log 模塊,簡單的日志記錄系統.適用於單用戶多線程模式.隨著項目的增加,需要支持多用戶.需要重構,讓其具備海量多用戶日志記錄能力
4.需要添加一個Session模塊,適用多用戶
5.內部有些模塊需要重構,用樹結構替代鏈表結構,提高查找速率.
以上這些就是推薦學習opengl 使用 simplec框架目前不爽的地方.爭取下一個版本全部搞好.加班太多了,真不想......
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
分享一個程序員傷感的故事
有一天晚上,兩個程序員吃完飯,碰見了不該碰見的人.發現自己都是備胎,
互相安慰說.
還是上去寫寫代碼壓壓驚吧.哈哈
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
正題
本次課程所要講的是繪制簡單的幾何圖形,在實際繪制之前,讓我們先熟悉一些概念。
一、點、直線和多邊形
我們知道數學(具體的說,是幾何學)中有點、直線和多邊形的概念,但這些概念在計算機中會有所不同。
數學上的點,只有位置,沒有大小。但在計算機中,無論計算精度如何提高,始終不能表示一個無窮小的點。另一方面,無論圖形輸出設備(例如,顯示器)如何精確,始終不能輸出一個無窮小的點。一般情況下,OpenGL中的點將被畫成單個的像素(像素的概念,請自己搜索之~),雖然它可能足夠小,但並不會是無窮小。同一像素上,OpenGL可以繪制許多坐標只有稍微不同的點,但該像素的具體顏色將取決於OpenGL的實現。當然,過度的注意細節就是鑽牛角尖,我們大可不必花費過多的精力去研究“多個點如何畫到同一像素上”。
同樣的,數學上的直線沒有寬度,但OpenGL的直線則是有寬度的。同時,OpenGL的直線必須是有限長度,而不是像數學概念那樣是無限的。可以認為,OpenGL的“直線”概念與數學上的“線段”接近,它可以由兩個端點來確定。
多邊形是由多條線段首尾相連而形成的閉合區域。OpenGL規定,一個多邊形必須是一個“凸多邊形”(其定義為:多邊形內任意兩點所確定的線段都在多邊形內,由此也可以推導出,凸多邊形不能是空心的)。多邊形可以由其邊的端點(這裡可稱為頂點)來確定。(注意:如果使用的多邊形不是凸多邊形,則最後輸出的效果是未定義的——OpenGL為了效率,放寬了檢查,這可能導致顯示錯誤。要避免這個錯誤,盡量使用三角形,因為三角形都是凸多邊形)
可以想象,通過點、直線和多邊形,就可以組合成各種幾何圖形。甚至於,你可以把一段弧看成是很多短的直線段相連,這些直線段足夠短,以至於其長度小於一個像素的寬度。這樣一來弧和圓也可以表示出來了。通過位於不同平面的相連的小多邊形,我們還可以組成一個“曲面”。
二、在OpenGL中指定頂點
由以上的討論可以知道,“點”是一切的基礎。
如何指定一個點呢?OpenGL提供了一系列函數。它們都以glVertex開頭,後面跟一個數字和1~2個字母。例如:
glVertex2d
glVertex2f
glVertex3f
glVertex3fv
等等。
數字表示參數的個數,2表示有兩個參數,3表示三個,4表示四個(我知道有點羅嗦~)。
字母表示參數的類型,s表示16位整數(OpenGL中將這個類型定義為GLshort),
i表示32位整數(OpenGL中將這個類型定義為GLint和GLsizei),
f表示32位浮點數(OpenGL中將這個類型定義為GLfloat和GLclampf),
d表示64位浮點數(OpenGL中將這個類型定義為GLdouble和GLclampd)。
v表示傳遞的幾個參數將使用指針的方式,見下面的例子。
這些函數除了參數的類型和個數不同以外,功能是相同的。例如,以下五個代碼段的功能是等效的:
(一)glVertex2i(1, 3);
(二)glVertex2f(1.0f, 3.0f);
(三)glVertex3f(1.0f, 3.0f, 0.0f);
(四)glVertex4f(1.0f, 3.0f, 0.0f, 1.0f);
(五)GLfloat VertexArr3[] = {1.0f, 3.0f, 0.0f};
glVertex3fv(VertexArr3);
以後我們將用glVertex*來表示這一系列函數。
注意:OpenGL的很多函數都是采用這樣的形式,一個相同的前綴再加上參數說明標記,這一點會隨著學習的深入而有更多的體會。
這裡擴展一下,在現在的編譯器中,能夠識別 5.f 相當於 5.0f. 這些技巧推薦用也推薦不用.看人吧. 例如 有時寫代碼覺得 C99/C11 風格好,
有時還是覺得C89的風格最穩妥.
舉例如下:
//C89 int hoge; int piyo; hoge = 16; piyo = 1; printf("hoge = %d, piyo = %d\n",hoge,piyo); //C99 / C11 int hoge = 13; printf("hoge = %d.\n",hoge); int piyo = 5; printf("heoo piyo is %d.\n",piyo);
繼續回到正題
三、開始繪制
假設現在我已經指定了若干頂點,那麼OpenGL是如何知道我想拿這些頂點來干什麼呢?是一個一個的畫出來,還是連成線?或者構成一個多邊形?或者做其它什麼事情?
為了解決這一問題,OpenGL要求:指定頂點的命令必須包含在glBegin函數之後,glEnd函數之前(否則指定的頂點將被忽略)。並由glBegin來指明如何使用這些點。
例如我寫:
glBegin(GL_POINTS);
glVertex2f(0.0f, 0.0f);
glVertex2f(0.5f, 0.0f);
glEnd();
則這兩個點將分別被畫出來。如果將GL_POINTS替換成GL_LINES,則兩個點將被認為是直線的兩個端點,OpenGL將會畫出一條直線。
我們還可以指定更多的頂點,然後畫出更復雜的圖形。
另一方面,glBegin支持的方式除了GL_POINTS和GL_LINES,還有GL_LINE_STRIP,GL_LINE_LOOP,GL_TRIANGLES,GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN等.
這裡簡單對上面兩個函數總結如下:
函數原型:
void glBegin(GLenum mode);
void glEnd(void);
參數說明:
mode:創建圖元的類型。可以是以下數值
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_STRIP:繪制一組相連的三角形,對於奇數n,頂點n、n+1和n+2定義了第n個三角形;對於偶數n,頂點n+1、n和n+2定義了第n個三角形,總共繪制N-2個三角形
GL_TRIANGLE_FAN:繪制一組相連的三角形,三角形是由第一個頂點及其後給定的頂點確定,頂點1、n+1和n+2定義了第n個三角形,總共繪制N-2個三角形
GL_QUADS:繪制由四個頂點組成的一組單獨的四邊形。頂點4n-3、4n-2、4n-1和4n定義了第n個四邊形。總共繪制N/4個四邊形
GL_QUAD_STRIP:繪制一組相連的四邊形。每個四邊形是由一對頂點及其後給定的一對頂點共同確定的。頂點2n-1、2n、2n+2和2n+1定義了第n個四邊形,總共繪制N/2-1個四邊形
GL_POLYGON:繪制一個凸多邊形。頂點1到n定義了這個多邊形。
我並不准備在glBegin的各種方式上大作文章。大家可以自己嘗試改變glBegin的方式和頂點的位置,生成一些有趣的圖案。
程序代碼:
void display(void) { glClear(GL_COLOR_BUFFER_BIT); glBegin( /* 在這裡填上你所希望的模式 */ ); /* 在這裡使用glVertex*系列函數 */ /* 指定你所希望的頂點位置 */ glEnd(); glFlush(); }
把這段代碼改成你喜歡的樣子,然後用它替換第一課中的myDisplay函數,編譯後即可運行。
下面 我舉三個例子
先畫一個 圓,思路是打點,按照 圓的參數方程(r*cos t , r*sin t) ,t 屬於[0,2*Pi]
代碼如下:
#include <glut.h> #define _USE_MATH_DEFINES //開啟 math.h中默認的常量宏 #include <math.h> //繪制圓的頂點數 #define _INT_N (20) /* *這裡繪制 一個圓 */ static void display(void) { int i; GLdouble r = 0.5; glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_POLYGON); for (i = 0;i < _INT_N;++i) { double p = 2 * M_PI * i / _INT_N; glVertex2d(r*cos(p),r*sin(p)); //打點連線 } glEnd(); glFlush(); } int main(int argc, char *argv[]) { glutInit(&argc,argv); //glut 初始化 為 rgb顏色模式,單緩沖 glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); //glut窗體創建 glutInitWindowSize(500, 500); glutInitWindowPosition(123,456); glutCreateWindow("這裡測試圓的繪制");
// 設置繪制函數
glutDisplayFunc(display);
//glut窗體主循環 glutMainLoop(); return 0; }
按照上面代碼敲一遍,基本都明白了.這裡再說一些廢話,前提你需要搭好環境.搭環境完成後學習就已經完成了1/3
學習編程技術 目前 總結 如下
前線作戰 => 搭環境 hello world + 學習基礎api + 項目 => 可以進入戰斗模式了
後線指揮 => 設計理念 + 抗壓和應急解決問題的實力 => 領跑者
第二個例子是畫一個五角星
實例圖如下:
畫這個 五角星 需要 你自己計算各個點的位置,需要初中幾何知識. 代碼參照如下:
#include <glut.h> #define _USE_MATH_DEFINES //開啟 math.h中默認的常量宏 #include <math.h> //繪制圓的頂點數 #define _INT_N (20) //將角度值轉成弧度數 #define TO_F_PII(d) \ (d*(float)M_PI/180) /* *這裡繪制 一個五角星 */ static void display(void) { //5x = 360 => x = 72度, x/2 = 36度 數學是不將中國道理的 GLfloat a = 0.5f;//默認比例是一半 當前屏幕 opengl坐標系 以屏幕中央為(0,0) //計算 縱坐標 從上 到下 為 a , a*cos(72度) , -a*cos(36度) //計算的橫坐標 從左到右 為 -a*sin(72度) , -a*sin(36度), 0, a*sin(36度) , a*sin(72度) //A,B,C,D,E 坐標 依次 如下,需要 你自己計算 三角函數 GLfloat pii72 = TO_F_PII(72.f); GLfloat pii36 = TO_F_PII(36.f); GLfloat pointA[2] = { -a*sinf(pii72), a*cosf(pii72) }, pointB[2] = { 0.f , a }, pointC[2] = { a*sinf(pii72) , a*cosf(pii72) }, pointD[2] = { a*sinf(pii36) , -a*cosf(pii36) }, pointE[2] = { -a*sinf(pii36) , -a*cosf(pii36) }; glClear(GL_COLOR_BUFFER_BIT); //從 A->C->E->B->D->A 順序一筆畫成 glBegin(GL_LINE_LOOP); glVertex2fv(pointA); glVertex2fv(pointC); glVertex2fv(pointE); glVertex2fv(pointB); glVertex2fv(pointD); glEnd(); glFlush(); }
五角星還是比較神聖的,可是自己已經污染了,熱愛家人吧.感謝他們給了我生命和一切.
下面是最後一個例子 繪制 f(x) = sinx , 這個例子是最簡單,做了一點修改是 坐標系擴大了一點,具體看下面代碼.
#define _USE_MATH_DEFINES //開啟 math.h中默認的常量宏 #include <math.h> //繪制三角函數 static void display(void) { GLfloat fz = 10.f,p; glClear(GL_COLOR_BUFFER_BIT); //先繪制坐標系 glBegin(GL_LINES); glVertex2f(-1.f,0.f); glVertex2f(1.f,0.f); glVertex2f(0.f,1.f); glVertex2f(0.f,-1.f); glEnd(); //繪制三角函數 glBegin(GL_LINE_STRIP); for (p = -fz;p < fz;p += 0.01f) glVertex2f(p/fz,sinf(p)/fz); glEnd(); glFlush(); }
到這裡關於 opengl 基礎學習的 第二節 基本完工了.上面例子很簡單,但需要 自己敲過才能知道一些. 這裡再說一些題外話.有沒有讀者對 上面 使用static 感到好奇.
這是為了在一個項目中 添加多個 main 文件 ,都用display 命名函數使用的技巧.因為 static 修飾的函數只在當前文件內有效,就不會出現 同名函數編譯不通過的問題.
關於static 和 extern 關鍵字 還是有很多學問的. 朋友們可以自己 科普一下.
最後說一點,當初寫博文之前,是實在不(ˇˍˇ) 想~看水文.後面自己寫了,才知道,好難.自己也成功的進入了 水筆行業. 對每一個寫博文的同行都表示 敬意.
做過,寫過,思考過,才知道 有些事 貴在堅持,難在 不想 坑人.
(有問題請隨時拍磚,馬上改)