OpenGL(試用篇)——第一個OpenGL程序(2)

2畫個三角形

繪製三角形時,要用到頂點緩存(VBO)的知識。個人感覺,這部分已經是GL比較中級的知識了。所以這裏不做詳細解釋,也不拿VBO和頂點數組(VA)和顯示列表作對比。我覺得,像GL這種實踐性很強的技術,還是先動手把效果實現了再說。《OpenGL超級寶典》之所以提供GLTools,就是想先把複雜的原理屏蔽掉,讓初學者能快速實現效果,隨着程序寫的越多,有些原理自然而然就理解了。否則,一開始就灌輸那麼多原理的東西,但連個最基本的三角形都畫不出來,我想大多數人都會很快失去興趣,尤其是像DirectXOpenGL這種偏底層的技術。

所以,這裏我只簡單說一下VBO的作用。VBO全名Vertex Buffer Object——頂點緩存對象。它的作用就是存儲頂點的一系列屬性,比如頂點的位置,顏色,法線,紋理座標等等。GL在繪製的過程中,就是根據VBO的數據把圖形畫出來。

2.1頂點,快到碗裏來~

2.1.1創建VBO


既然,VBO是存儲頂點的屬性,那首先得有碗吧。

所以,第一步就是創建VBO

voidglGenBuffers(GLsizein,GLuint*buffers);

n:要產生的VBO的數量

buffers:存儲VBO的句柄。

PSGLsizei=intGLuint= unsignedint

有人會問,幹嘛這麼麻煩,直接用int不就好了。OpenGL是跨平臺的API,所以要兼顧各個平臺,所以自己重新定義了類型。這個知道就行,建議養成用GL專有類型的好習慣。


2.1.2綁定VBO


voidglBindBuffer(GLenum  target,GLuint    buffer);

綁定一個緩衝區對象。

target:可能取值是:GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, or GL_PIXEL_UNPACK_BUFFER.

我們現在主要使用GL_ARRAY_BUFFER GL_ELEMENT_ARRAY_BUFFER

GL_ARRAY_BUFFER用於保存之前說的,頂點的各種屬性

GL_ELEMENT_ARRAY_BUFFER用於保存頂點索引,這個在以後用頂點索引模型繪製時會用到。

當進行綁定之後,以前的綁定就失效了。

在綁定的同時,也將該緩存設置成當前緩存。GL對緩存的一切操作,都是對當前緩存有效。


PS這麼做的原因,是因爲OpenGL採用狀態機的模式。glBindBuffer就是講GL的緩存模式切換到當前緩存。可能,習慣面向對象的人不習慣這種模式。GL是一種C語言API,所以GL中並沒有真正的對象改變。GL中的對象,實際上就是一個int類型的標識符,對於熟悉Windows編程的朋友來說,也就是所謂的句柄。所以,GL要對使用或者改變某對象,採用的就是綁定該對象句柄的方式。而不是,C++的真正類,可以直接調用對象的成員方法。


2.1.3VBO填充數據


選擇好要操作的VBO之後,就要爲VBO裝數據,這裏的數據,可以是頂點位置,頂點顏色等等

voidglBufferData(GLenum target,GLsizeiptr size,constGLvoid * data,GLenum usage);

target和之前glBindBuffer中的target含義是一樣的。

size填充數據的字節數,注意是字節數,不是個數

data:具體的數據。通常用數組

usage:緩存區的用途。當不確定用途時,使用GL_DYNAMIC_DRAW是一個比較安全的值。


2.1.4開始畫了

準備工作都做好了,現在就可以開畫了。

我們畫的三角形要到了頂點的兩個屬性:位置和顏色。

所以用到了兩個VBO,我們要創建兩個VBO

首先定義兩個全局變量

init()方法內,添加如下代碼


GLuint vbo[2];
glGenBuffers(2, vbo);
posBuf = vbo[0];
colorBuf = vbo[1];


然後,就是繪製三角形的代碼了。

首先在display內,在glClear()glSwapBuffer()之間,添加如下代碼。


srand(time(0));
                                                                               
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
                                                                               
GLfloat verts[] = {
    200, 100, 0,
    600, 100, 0,
    400, 400, 0
};
                                                                               
GLfloat colors[] = {
    (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f,
    (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f,
    (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f
};
                                                                               
glBindBuffer(GL_ARRAY_BUFFER, posBuf);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glVertexPointer(3, GL_FLOAT, 0, 0);
                                                                               
glBindBuffer(GL_ARRAY_BUFFER, colorBuf);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
glColorPointer(3, GL_FLOAT, 0, 0);
                                                                               
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
    glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);


#1:爲隨機數設置種子,種子爲當前時間。關於C++隨機數的問題,問Google~~,作爲一名合格的碼農,Google是必備技能。


#3 - #4:設置當前爲模型視圖模式,並重置模型試圖矩陣。這裏不詳細解釋,這些步驟都是必須的。等後面講到GLSL時,自然而然就明白了。


#6 - #10:定義三角形的三個頂點。爲了方面閱讀,特意分成3行。3個頂點分別爲(200, 100, 0), (600, 100, 0), (400, 400, 0)。注意哦,這個例子中,我們使用的是二維座標系,原點在左下角,寬是窗口的寬度(800),高是窗口的高度(600)。至於,怎麼設置,稍後分析


#12 - #16:定義三角形的顏色。這下我們srand()就派上用場了。所謂會變色,其實就是每秒隨機生成一種顏色。GL設置顏色是使用浮點類型的,RGBA的範圍都是0.0f~1.0f


#18 - #20:這裏就是關鍵代碼了。

1.glBindBuffer(GL_ARRAY_BUFFER, posBuf);首先,先選擇要操作的VBO。我們先操作,存儲頂點位置的混存。

2.glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);

然後,爲該緩衝區填充數據。注意第二個參數是字節大小,所以可以使用sizeof獲取。

注意,sizeof有個陷阱哦。因爲sizeof()verts是在同一方法體內,所以sizeof(verts)取得的是verts數組的字節大小。但是,如果verts是通過方法形參傳遞給sizeof,那sizeof(verts)取得的是float類型指針的大小,不是數組的大小。因爲,數組在作爲方法實參時,已經退化成指針了。所以,這時候我們就要改成 N *sizeof(float), N是數組的個數

3.glVertexPointer(3, GL_FLOAT, 0, 0);設置頂點位置的指針。說明白點,就是讓GL知道,如果想要頂點位置,就從當前緩存區裏取就可以了。

  • 參數1:頂點位置的大小。因爲是3維座標(x,y,z),自然取3

  • 參數2:頂點位置的類型。GL_FlOAT,這個很明白了

  • 參數3:每個頂點屬性的間隔。簡單地說,我們頂點位置和顏色都是分開設置的,利用不同的數組。所以,每個頂點位置都是連續的。比如(200, 100, 0),後面就是下個頂點(600, 100, 0),頂點位置之間的間隔爲0

  • 參數4:這個參數可能有點迷糊人。雖然它的參數類型是指針類型,但在使用VBO時,實際上指的是偏移位置。我們要用到VBO內的頂點位置,所以設爲0

#22 - #24:設置顏色的方法,和設置頂點位置的方法是一樣的。所以就不重複了。唯一的區別,就是告訴GL,這個緩存colorBuf保存的是頂點顏色。

glColorPointer(3, GL_FLOAT, 0, nullptr);


#26 - #27:開啓對應的頂點屬性。GL對於頂點的各個屬性,默認都是關閉的。

  • GL_VERTEX_ARRAY:頂點位置

  • GL_COLOR_ARRAY:頂點顏色

注意到方法名有個Client嗎?Client是客戶端的意思(+_+沒有人不知道吧~~)。這個例子,我們用的還是固定管線的做法,但是拋棄了glBegin/glEndglVertexglNormal。那玩意實在是太舊了。Client指的就是當前的GL程序,我們在GL程序裏設置好頂點屬性後,再將屬性傳給管線,管線就是服務器了。因爲是固定管線,我們不可以干涉管線的工作。也因爲這樣,我們纔會調用glVertexPointer/glColorPointer來告訴GL,哪個是頂點位置,哪個是頂點顏色。等到後面,可編程管線時。我們就能操作,管線的工作了。到那時,所有的頂點屬性,通通調用glVertexAttribPointer來指定。當然,這是後話。


#28:這句就是真正把三角形畫出來了。

glDrawArrays(GL_TRIANGLES, 0, 3);

GL有多重繪製模型,包括點,線,三角形,三角形扇等等。這裏,我們使用GL_TRIANGLES,來指定GL畫三角形。


#29 - #30:恢復GL的默認設置。養成好習慣,繪製完成後,恢復GL的設置。


大功告成,來試試吧~~

image

說好的三角形呢?

OpenGL瞭解的朋友,可能已經知道問題所在。到目前爲止,都沒有出現glViewport,gluOrtho,gluPerspective,導致沒有設置GL的繪製區域和投影模式。現在我們就來設置。


void reshape(int width, int height){
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
                                                                      
   glViewport(0, 00, width, height);
   gluOrtho2D(0, width, 0, height);
}

#2:選擇當前模式爲投影模式。


#3:重置當前的投影模式。


#5glViewport:設置GL的繪製區域,這裏我們設置爲整個窗口的大小。


#6gluOrtho2D:設置GL的投影模式爲正交投影。關於正交投影和透視投影,老方法,Google大把。


實際上,之前我們設定的二維座標,就是這句代碼起的作用。

打完收工。這下再來看看

image

三角形是有了,顏色也有了。but~~說好的變色呢。~~      

其實新的顏色已經產生了,但是沒有畫出來而已,我們只需在display()中的glSwapBuffers下面添加一句就可以了


glutPostRedisplay();


這句代碼就是告訴glut,重新畫一幀。

終於操蛋的出來了,不容易啊。

再來幾張~~

image

image

附上全部源碼

#define GLEW_STATIC
#include <GL/glew.h>
#include <GL/freeglut.h>
#include <iostream>
#include <ctime>
                                                         
GLuint posBuf, colorBuf;
                                                         
void init(){
    glClearColor(0, 0, 0 ,1);
    glEnable(GL_DEPTH_TEST);
                                                         
    GLuint vbo[2];
    glGenBuffers(2, vbo);
    posBuf = vbo[0];
    colorBuf = vbo[1];
}
                                                         
void display(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    srand(time(0));
                                                         
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
                                                         
    GLfloat verts[] = {
        200, 100, 0,
        600, 100, 0,
        400, 400, 0
    };
                                                         
    GLfloat colors[] = {
        (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f,
        (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f,
        (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f
    };
                                                         
    glBindBuffer(GL_ARRAY_BUFFER, posBuf);
    glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
    glVertexPointer(3, GL_FLOAT, 0, 0);
                                                         
    glBindBuffer(GL_ARRAY_BUFFER, colorBuf);
    glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
    glColorPointer(3, GL_FLOAT, 0, 0);
                                                         
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
        glDrawArrays(GL_TRIANGLES, 0, 3);
    glDisableClientState(GL_COLOR_ARRAY);
    glEnableClientState(GL_VERTEX_ARRAY);
                                                         
    glutSwapBuffers();
    glutPostRedisplay();
}
                                                         
void reshape(int width, int height){
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0, 00, width, height);
    gluOrtho2D(0, width, 0, height);
}
                                                         
void keyboard(unsigned char key, int x, int y){
                                                         
}
                                                         
void keyboardUp(unsigned char key, int x, int y){
                                                         
}
                                                         
void specialKey(int key, int x, int y){
                                                         
}
                                                         
void specialKeyUp(int key, int x, int y){
                                                         
}
                                                         
int main(int argc, char **argv){
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("會變色的三角形");
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutKeyboardUpFunc(keyboardUp);
    glutSpecialFunc(specialKey);
    glutSpecialUpFunc(specialKeyUp);
    glutDisplayFunc(display);
    glFlush();
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    init();
    glutMainLoop();
    return 0;
}


本回到此爲止,欲知後事如何,且聽下回分解~~


PS:本篇作爲一篇試用博文,最想看到就是效果如何。第一次寫這麼詳細的文章,前前後後花了5個小時。其實,我也是接觸OpenGL的一點皮毛,作爲一個初學者,還是能體會學習GL的難度。從剛開始接觸NEHE教程,到紅皮書,到國內出的雜七雜八的GL書籍,再到最後的藍寶書。我也走了不少彎路,爲了讓以後的剛剛接觸GL的初學者不要踏進之前我踩過的坑,於是萌生了把自己的學習心得記錄下來的想法。

寫的過程中,自己肯定有理解的偏差,如果有任何不對的地方,敬請指正~~。如果對本文的寫作風格有任何建議的話,儘管提~~

謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章