題外話
聰明人之所以不會成功,是由於他們缺乏堅韌的毅力。
——艾薩克·牛頓(1643年1月4日—1727年3月31日)英國
也許可以理解為 想更深一步的時候,堅持,努力和聰明缺一不可. 挺直腰桿在此向您致敬,願您仍在天國
仍潇灑的思索著,奔跑著.
正文
在第二課中,我們學習了如何繪制幾何圖形,但大家如果多寫幾個程序,就會發現其實還是有些郁悶之處。
例如:點太小,難以看清楚;直線也太細,不舒服;或者想畫虛線,但不知道方法只能用許多短直線,甚至用點組合而成。
這些問題將在本課中被解決。
下面就點、直線、多邊形分別討論。
1、關於點
點的大小默認為1個像素,但也可以改變之。改變的命令為glPointSize,其函數原型如下:
void glPointSize (GLfloat size);
size必須大於0.0f,默認值為1.0f,單位為“像素”。
(注意:對於具體的OpenGL實現,點的大小都有個限度的,如果設置的size超過最大值,則設置可能會有問題。)
這裡順帶說一下 關於上面函數編譯器行為,以Window 代碼為例解釋如下
//具體的函數原型聲明 WINGDIAPI void APIENTRY glPointSize (GLfloat size); /* *APIENTRY 是設置編譯器行為的宏 本質是 __stdcall * *__stdcall是函數調用約定的一種,函數調用約定主要約束了兩件事: *1.參數傳遞順序 *2.調用堆棧由誰(調用函數或被調用函數)清理 *3.函數名(在編譯器這個層次)自動加前導的下劃線,後面緊跟一個@符號,其後緊跟著參數的尺寸 * *C中函數缺省默認是 __cdecl *每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。 *函數采用從右到左的壓棧方式。 *注意:對於可變參數的成員函數,始終使用__cdecl的轉換方式。 */ #define WINAPI __stdcall #define WINAPIV __cdecl #define APIENTRY WINAPI
實際使用 glPointSize例子如下:
#include <glut.h> /* * *這裡是繪制的主函數 */ static void __display(void) { glClear(GL_COLOR_BUFFER_BIT); glPointSize(5.0f); glBegin(GL_POINTS); glVertex2f(0.0f,0.0f); glVertex2f(0.5f,0.5f); glEnd(); glFlush(); }
2、關於直線
(1)直線可以指定寬度:
void glLineWidth(GLfloat width);
其用法跟glPointSize類似。
(2)畫虛線。
首先,使用glEnable(GL_LINE_STIPPLE); 來啟動虛線模式(使用glDisable(GL_LINE_STIPPLE)可以關閉之)。
然後,使用glLineStipple來設置虛線的樣式。
void glLineStipple(GLint factor, GLushort pattern);
pattern是由1和0組成的長度為16的序列,從最低位開始看,如果為1,則直線上接下來應該畫的factor個點將被畫為實的;如果為0,則直線上接下來應該畫的factor個點將被畫為虛的。
再細說一點
glEnable(GL_LINE_STIPPLE); glLineStipple(1, Ox3F07); //此時模式為Ox3F07(二進制形式為0011111100000111). //它所畫出來的直線是這樣的: //先連續繪制3個像素,然後連續5個像素留空,再連續繪制6個像素,最後兩個像素留空(注意,首先是從低位開始的)。 //如果factor是2,那麼這個模式便被擴展為: //先連續繪制6個像素,然後連續10個像素留空,再連續繪制12個像素,最後4個像素留空。 //如果沒有啟用點畫線功能,OpenGL會自動把pattern當做為OxFFFF,把factor當成1。
更詳細的例子代碼如下:
#include <glut.h> //繪制函數 static void __display(void) { glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_LINE_STIPPLE); //開啟畫虛線模式 glLineStipple(2,0x0F0F); //虛線的樣式 glLineWidth(10.f); //畫一個線 glBegin(GL_LINES); glVertex2f(0.0f,0.0f); glVertex2f(0.5f,0.5f); glEnd(); glDisable(GL_LINE_STIPPLE); //關閉畫虛線模式 glFlush(); }
3、關於多邊形
多邊形的內容較多,我們將講述以下四個方面。
(1)多邊形的兩面以及繪制方式。
雖然我們目前還沒有真正的使用三維坐標來畫圖,但是建立一些三維的概念還是必要的。
從三維的角度來看,一個多邊形具有兩個面。每一個面都可以設置不同的繪制方式:填充、只繪制邊緣輪廓線、只繪制頂點,其中“填充”是默認的方式。
可以為兩個面分別設置不同的方式。
glPolygonMode(GL_FRONT, GL_FILL); // 設置正面為填充方式
glPolygonMode(GL_BACK, GL_LINE); // 設置反面為邊緣繪制方式
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); // 設置兩面均為頂點繪制方式
(2)反轉
一般約定為“頂點以逆時針順序出現在屏幕上的面”為“正面”,另一個面即成為“反面”。
生活中常見的物體表面,通常都可以用這樣的“正面”和“反面”,“合理的”被表現出來
(請找一個比較透明的礦泉水瓶子,在正對你的一面沿逆時針畫一個圓,並標明畫的方向,然後將背面轉為正面,畫一個類似的圓,體會一下“正面”和“反面”。
你會發現正對你的方向,瓶的外側是正面,而背對你的方向,瓶的內側才是正面。正對你的內側和背對你的外側則是反面。
這樣一來,同樣屬於“瓶的外側”這個表面,但某些地方算是正面,某些地方卻算是反面了)。
但也有一些表面比較特殊。例如“麥比烏斯帶”(請自己Bing一下),可以全部使用“正面”或全部使用“背面”來表示。
可以通過glFrontFace函數來交換“正面”和“反面”的概念。
glFrontFace(GL_CCW); // 設置CCW方向為“正面”,CCW即CounterClockWise,逆時針
glFrontFace(GL_CW); // 設置CW方向為“正面”,CW即ClockWise,順時針
下面是一個示例程序,請用它替換前面教程中的__display函數,並將glFrontFace(GL_CCW)修改為glFrontFace(GL_CW),並觀察結果的變化。
#include <glut.h> //繪制函數 static void __display(void) { glClear(GL_COLOR_BUFFER_BIT); glFrontFace(GL_CCW); // 設置逆時針方向為正面 glPolygonMode(GL_FRONT, GL_FILL); // 設置正面為填充模式 glPolygonMode(GL_BACK, GL_LINE); // 設置反面為線形模式 //先逆時針繪制一個 正方形,在左下方 glBegin(GL_POLYGON); glVertex2f(-0.5,-0.5f); glVertex2f(0.0f,-0.5f); glVertex2f(0.0f,0.0f); glVertex2f(-0.5f,0.0f); glEnd(); //後順時針繪制一個正方形,在右上方 glBegin(GL_POLYGON); glVertex2f(0.0f,0.0f); glVertex2f(0.0f,0.5f); glVertex2f(0.5f,0.5f); glVertex2f(0.5f,0.0f); glEnd(); glFlush(); }
(3)剔除多邊形表面
在三維空間中,一個多邊形雖然有兩個面,但我們無法看見背面的那些多邊形,而一些多邊形雖然是正面的,但被其他多邊形所遮擋。如果將無法看見的多邊形和可見的多邊形同等對待,無疑會降低我們處理圖形的效率。在這種時候,可以將不必要的面剔除。
首先,使用glEnable(GL_CULL_FACE);來啟動剔除功能(使用glDisable(GL_CULL_FACE)可以關閉之)
然後,使用glCullFace來進行剔除。
glCullFace的參數可以是GL_FRONT,GL_BACK或者GL_FRONT_AND_BACK,分別表示剔除正面、剔除反面、剔除正反兩面的多邊形。
注意:剔除功能只影響多邊形,而對點和直線無影響。例如,使用glCullFace(GL_FRONT_AND_BACK)後,所有的多邊形都將被剔除,所以看見的就只有點和直線。
(4)镂空多邊形
直線可以被畫成虛線,而多邊形則可以進行镂空。
首先,使用glEnable(GL_POLYGON_STIPPLE);來啟動镂空模式(使用glDisable(GL_POLYGON_STIPPLE)可以關閉之)。
然後,使用glPolygonStipple來設置镂空的樣式。
void glPolygonStipple(const GLubyte *mask);
其中的參數mask指向一個長度為128字節的空間,它表示了一個32*32的矩形應該如何镂空。其中:第一個字節表示了最左下方的從左到右(也可以是從右到左,這個可以修改)8個像素是否镂空(1表示不镂空,顯示該像素;0表示镂空,顯示其後面的顏色),最後一個字節表示了最右上方的8個像素是否镂空。
對於mask定義 一種簡單方式 是這樣的
win + R => mspaint + Enter => 設置畫布像素為 32*32 => 自己隨便編輯 => 保存為 my.bmp =>程序讀取這個文件 轉成 mask
下面分享一個Opengl 中 一個 老例子 如下:
#include <sc_head.h> #include <glut.h> static GLubyte __masks[] = { 0x00, 0x00, 0x00, 0x00, // 這是最下面的一行 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x01, 0xC0, // 麻 0x06, 0xC0, 0x03, 0x60, // 煩 0x04, 0x60, 0x06, 0x20, // 的 0x04, 0x30, 0x0C, 0x20, // 初 0x04, 0x18, 0x18, 0x20, // 始 0x04, 0x0C, 0x30, 0x20, // 化 0x04, 0x06, 0x60, 0x20, // , 0x44, 0x03, 0xC0, 0x22, // 不 0x44, 0x01, 0x80, 0x22, // 建 0x44, 0x01, 0x80, 0x22, // 議 0x44, 0x01, 0x80, 0x22, // 使 0x44, 0x01, 0x80, 0x22, // 用 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC, 0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30, 0x07, 0xE1, 0x87, 0xE0, 0x03, 0x3F, 0xFC, 0xC0, 0x03, 0x31, 0x8C, 0xC0, 0x03, 0x3F, 0xFC, 0xC0, 0x06, 0x64, 0x26, 0x60, 0x0C, 0xCC, 0x33, 0x30, 0x18, 0xCC, 0x33, 0x18, 0x10, 0xC4, 0x23, 0x08, 0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0C, 0x08, 0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08 // 這是最上面的一行 }; //bmp 圖片創建函數 void bmp_create(GLubyte mask[], int len); // 屏幕繪制函數 void display(void); #define _STR_BMP "fly.bmp" int main(int argc, char *argv[]) { //這裡先 創建 一個bmp bmp_create(__masks, sizeof __masks); glutInit(&argc,argv); // 初始化glut環境 glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); //設置窗口繪制為RGB模式和單緩沖模式 glutInitWindowPosition(500,500); glutInitWindowSize(300,300); glutCreateWindow(argv[0]); //初始化啟動窗口 glutDisplayFunc(display); glutMainLoop();//循環等待函數,直到glut創建的窗口關閉 return 0; } //bmp 圖片創建函數 void bmp_create(GLubyte mask[], int len) { FILE *bmp = NULL; if (NULL == mask || len <= 0) ERR_EXIT("check params error [NULL == mask || len <= 0]."); if ((bmp = fopen(_STR_BMP, "wb")) == NULL) //練習就寫的簡單一點 ERR_EXIT("fopen error!"); // 文件寫入 fwrite(mask, sizeof(GLubyte), len, bmp); fclose(bmp);; } // 屏幕繪制函數 void display(void) { GLubyte mask[sizeof __masks]; //先讀取文件中內容 FILE *bmp = NULL; if ((bmp = fopen(_STR_BMP, "rb")) == NULL) //練習就寫的簡單一點 ERR_EXIT("fopen error!"); // 文件寫入 fread(mask, sizeof(GLubyte), sizeof mask, bmp); fclose(bmp); //這裡開始繪制 glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_POLYGON_STIPPLE); //啟動镂空模式 glPolygonStipple(mask); //設置镂空的樣式 glRectf(-0.5f,-0.5f,0.0f,0.0f); // 在左下方繪制一個有镂空效果的正方形 glDisable(GL_POLYGON_STIPPLE); //關閉镂空模式 glRectf(0.0f,0.0f,0.5f,0.5f); //往右上方繪制一個無镂空效果的正方形 glFlush(); }
最後的效果圖如下:
小結
本課學習了繪制幾何圖形的一些細節。
點可以設置大小。
直線可以設置寬度;可以將直線畫成虛線。
多邊形的兩個面的繪制方法可以分別設置;在三維空間中,不可見的多邊形可以被剔除;可以將填充多邊形繪制成镂空的樣式。
了解這些細節會使我們在一些圖象繪制中更加得心應手。
另外,把一些數據寫到程序之外的文件中,並用專門的工具編輯之,有時可以顯得更方便。
做為一個菜鳥,歡迎交流指正.希望此刻更有趣.加油.