天天看點

【OpenGL】繪制一個點

使用 glew

glew 全稱是 OpengGL Extension Wrangler Library,它能夠幫忙解決 OpengGL 不斷擴充的問題。初始化 glew 之後,它将查詢系統上所有可用的擴充功能并自動加載它們,然後提供一個頭檔案作為接口,我們直接通過頭檔案就可以使用這些擴充功能。

glew 的下載下傳位址

http://glew.sourceforge.net/

下載下傳之後進行解壓,得到的目錄結構

|-glew-2.1.0
    |-bin
    |-lib
    |-include
    |-doc
           

接下來開始配置環境,配置的方式有兩種,第一種方式跟配置 glut 一樣,系統級的配置,配置之後所有的 opengl 項目都不需要配置

  • 第一步,把 include 目錄下的頭檔案放在 %VISUAL_STUDIO%\VC\include\gl 目錄下
  • 第二步,把 lib 目錄下的 lib 檔案放在 %VISUAL_STUDIO%\VC\lib 目錄下,然後在項目屬性的 連結器 –> 輸入 添加相應庫的引用
  • 第三步,把 bin 目錄下的 dll 檔案放在 system32 目錄下,同樣的 64 位系統要放在 sysWOW64 目錄下

這種方式雖然能夠一勞永逸,但因為 win32 和 x64 的庫檔案名一樣,是以無法做到相容,即無法同時把 32 位的庫檔案和 64 位的庫拷到系統路徑;另外過多地把庫檔案拷到系統路徑也不好,是以推薦使用第二種方式,即給每個建立的項目配置環境

  • 把頭檔案放在 $PROJECT_ROOT%\include\GL 目錄下,然後在項目屬性的 C/C++ –> 附加包含目錄 中添加 .\include
  • 把靜态庫 lib 檔案放在 $PROJECT_ROOT%\lib 目錄下,然後在項目屬性的 連結器 –> 正常 –> 附加庫目錄 中添加 .\lib,在 連結器 –> 輸入 中添加相應庫的引用
  • 把動态庫 dll 檔案放在可執行程式 exe 同級目錄下

如果一個項目想同時編譯 32 位和 64 位,則可以分别把 32 位的 lib 檔案和 64 位的 lib 檔案放在 .\lib\win32 和 .\lib\x64 目錄下,然後分别修改附加庫目錄,再把相應的 dll 檔案拷到編譯後的 32 位程式和 64 位程式目錄下。

GL context

在開始繪制圖形之前,我們必須先了解 GL context 和 GL objects 這兩個重要概念,參考文檔 https://www.khronos.org/opengl/wiki/Main_Page

上一篇文章講到 OpenGL 渲染是基于狀态(state)的。OpenGL context 是一個重要的概念的,隻有建立了 context,OpenGL 才存在,context 一旦被銷毀了,OpenGL 就不存在了。context 存儲了一個 OpenGL 執行個體的所有狀态,類似于一個程式開辟的所有記憶體空間。context 可以看作程序在作業系統中的一個執行過程,一個程序可以建立多個 context,每一個 context 代表一個可視面,就像一個應用程式的一個界面一樣。

簡單來講,context 儲存了一個 OpenGL 執行個體的所有狀态,在使用 OpenGL 之前必須先建立一個 context。

int main(int argc, char **argv)
{
    char *GL_version=(char *)glGetString(GL_VERSION);
    char *GL_vendor=(char *)glGetString(GL_VENDOR);
    char *GL_renderer=(char *)glGetString(GL_RENDERER);
    return ;
}
           

這裡是想擷取一些系統資訊,但得到的結果卻全是空,這裡因為此時還沒有建立 context,OpenGL 相當于不存在。

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

    glutInitWindowSize(, );
    glutInitWindowPosition(, );
    glutCreateWindow("Create Dot");

    //擷取 OpenGL 版本号
    char *GL_version = (char *)glGetString(GL_VERSION);
    //擷取本機提供 GL 支援的公司
    char *GL_vendor = (char *)glGetString(GL_VENDOR);
    //擷取渲染器的名稱
    char *GL_renderer = (char *)glGetString(GL_RENDERER);
    //擷取着色器的版本号
    char* GL_shader_version =  (char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
    //擷取本機硬體支援的最大頂點屬性數
    GLint max_vertex_attrib;
    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attrib);

    return ;
}
           

這樣就可以正常擷取資訊了,因為

glutCreateWindow

建立一個視窗的同時就相當于已經建立了一個 context。

GL objects

再重申一遍,OpenGL 基于狀态來渲染,可以說 OpenGL 被定義成“狀态機”,所有的 API 都是修改狀态、查詢狀态或者使用狀态來渲染。GL Object 是一些狀态的集合,這點看起來跟 GL Context 有點像,也可以這樣類比,但要知道 object 和 context 的 state 是互相獨立的,context 有一套狀态,每個 object 也會有自己的一些狀态。隻有把 object 綁定到 context 上,它的狀态都會映射到 context 上,綁定之後修改 context 的狀态,object 也會受影響;相反基于 context 狀态的函數也可以使用 object 的狀态。

對象可以分成兩大類,regular objects 和 container objects。

regular object 包括 Buffer Objects,Query Objects,Renderbuffer Objects,Sampler Objects,Texture Objects。

container objects 包括 Vertex Array Objects,Framebuffer Objects,Program Pipeline Objects,Transform Feedback Objects。

對象建立、銷毀和綁定

使用 glGen* 函數給對象生成一個名字,就是建立對象了。

void glGen*(GLsizei n, GLuint *objects);
           

這個函數可以同時建立多個對象,隻需要給定一個對象數組的指針;對象名是 Glsizei 類型,也就是一個 32 位無符号整型,這個整數并不是一個指針,隻是對象的一個引用,用于辨別這個對象。另外,整數 0 這個名字用于特殊對象,是以我們給對象起名時隻能從 1 開始。

void glDelete*(GLsizei n, const GLuint *objects);
           

這個函數用于銷毀對象,同樣如果傳進來的 object 是一個數組的話,可以批量銷毀多個對象。關于對象銷毀有幾點要注意的

  • 如果對象已經綁定到 context ,則對象銷毀後會自動解綁;但如果對象附加到另一個對象上,則這我種附加關系不會解除
  • 對象被 delete 之後并不會立即删除,它的名字還可以使用,但請不要用
void glBind*(GLenum target, GLuint object);
           

這個函數用于把對象綁定到 context。target 指明了對象的類型,有些對象可以綁定為多個類型,比如一個 buffer obejct 可以綁定為 array buffer,index buffer,pixel buffer,transform buffer 或者其它。

VAO && VBO

我們知道了 OpenGL 是一個狀态機,使用 object 來儲存資料和狀态,钴綁定到 context,将 object 的狀态和 context 的狀态關聯起來,然後使用 context 的狀态進行渲染。接下來就着手準備繪制一個點,這裡需要用到兩個對象 VBO 和 VAO。

VAO 即 Vertex Array Object,它儲存了所有頂點資料的狀态,并沒有儲存頂點資料,而是儲存了頂點資料的格式和所需 buffer 對象的引用。每一個狀态屬性都是可以開啟和關閉的,使用下面兩個函數

void glEnableVertexAttribArray(GLuint index);
void glDisableVertexAttribArray(GLuint index);
           

VBO,即 Vertex Buffer Object,在緩存區儲存的就是頂點資料了。為了讓 VAO 能使用 VBO 的資料,我們需要告訴 OpenGL,編号為 index 屬性使用目前綁定到 GL_ARRAY_BUFFER 的 VBO

index 是第幾個屬性,像頂點的第 0 個屬性就是位置;size 指定構成屬性的分量個數,像頂點位置由 x,y,z 三個分量組成,是以 size=3;type 指定屬性值的類型,像頂點位置為 GL_FLOAT;normalized 指屬性在管線中使用之前是否需要被規範化;stride 指兩個相同屬性值之間間隔的位元組數,隻有一個屬性時間隔為 0;pointer 指存儲資料的偏移值,同樣隻有一個屬性時偏移值為 0。

繪制一個點

#include <gl/glew.h>
#include <gl/glut.h>
#include <math3d.h>
#include <iostream>

using namespace std;

void init();
void renderPerFrame();

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

    glutInitWindowSize(, );
    glutInitWindowPosition(, );
    glutCreateWindow("Create Dot");

    char *GL_version = (char *)glGetString(GL_VERSION);
    char *GL_vendor = (char *)glGetString(GL_VENDOR);
    char *GL_renderer = (char *)glGetString(GL_RENDERER);

    GLenum res = glewInit();
    if (GLEW_OK != res)
    {
        cout << "glewInit failed: " << glewGetErrorString(res) << endl;
        system("pause");
        return ;
    }

    init();

    glutDisplayFunc(renderPerFrame);
    glClearColor(, , , );

    glutMainLoop();
}

void init()
{
    //建立 buffer 對象
    GLuint VBO;
    glGenBuffers(, &VBO);

    //綁定 buffer 對象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    //定義資料
    M3DVector3f vertices[];
    vertices[][] = ;
    vertices[][] = ;
    vertices[][] = ;
    //填充 buffer 的值
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}

void renderPerFrame()
{
    glClear(GL_COLOR_BUFFER_BIT);

    //開啟頂點屬性
    glEnableVertexAttribArray();
    //指定屬性使用的 buffer
    glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, , );
    //繪制頂點 
    glDrawArrays(GL_POINTS, , );
    //關閉頂點屬性
    glDisableVertexAttribArray();

    glutSwapBuffers();
}
           

總結一下這個過程:

首先,建立頂點緩沖對象,即 VBO;然後将頂點緩沖對象綁定到 context;最後給頂點緩沖對象賦頂點資料。

經過上面三步之後,在 context 中就已經有了一個頂點緩沖對象,這個對象儲存了頂點資料。接下來就是在主回調函數中取頂點資料進行繪制,這裡使用的是固定管線,是以不用 VAO,後面主要使用可程式設計管線。

繪制的時候是基于狀态的,要繪制頂點,必須知道每個狀态屬性從哪裡取資料,即把頂點對象(VAO)和頂點緩沖對象(VBO)關聯起來。首先,要開啟頂點對象的某個屬性;然後指定該屬性使用的 buffer 對象;然後開始繪制;最後關閉頂點對象屬性。

【OpenGL】繪制一個點

繼續閱讀