天天看點

OpenGL ES Shader相關API 總結【5】——VBO與VAO的作用與關系

早期的OpenGL為了将模型的頂點資料傳送到顯示卡,需要逐個頂點進行(備援處理的問題),如果還需要額外的資訊(紋理坐标和法線)的話,當模型比較複雜時,将導緻大量函數的調用,傳輸開銷是相當大的!為了解決這個問題引入了VBO(Vertex Buffer Object),VBO可以将頂點資料儲存在顯存中,繪制時直接從顯存中取資料,減少了資料傳輸的開銷。

頂點屬性(Vertex Attribute),是關于頂點坐标和頂點紋理、頂點法線以及其他資訊的統稱。

頂點的格式:

typedef struct
{
    float x;
    float y;
    float z;
}Vec3;
 
typedef struct
{
    float x;
    float y;
}
typedef struct
{
    Vec v;
    Vec2 vt;
    Vec3 vn;
}Vertex;      

從以上可知,通過VBO我們可以将頂點屬性資料儲存在顯存中,當繪制時問題又來了,需要調用好幾個函數,過程挺複雜的。為了解決這個問題,OpenGL又引入了VAO(Vertex Array Object)來關聯VBO中的資料 (VAO詳解0,VAO詳解1),有了VAO,任何數組形式的GL函數調用都會添加到VAO的繪制清單當中(直到解除VAO綁定),當需要繪制的時候,我們僅需要重新綁定VAO,那麼之前建立的繪制清單将會重新激活,使得繪制代碼更加簡潔。

舉個例子:假設頂點着色器中定義了以下三個變量,用于儲存頂點屬性

attribute vec3 vertex_position;
attribute vec2 vertex_texture_coord;
attribute vec3 vertex_normal;      

在C++代碼中定義一個init函數,用于加載shader、模型、紋理,以及最重要的建立VBO和VAO和關聯它們

// 初始化
void init()
{
    pid = load_shader_program("vertex.glsl", "fragment.glsl");
    
    // 擷取vertex_position、vertex_texture_coord和vertex_nromal的位置
    GLuint vertex_position_loc = glGetAttribLocation(pid, "vertex_position");
    GLuint vertex_texture_coord_loc = glGetAttribLocation(pid, "vertex_texture_coord");
    GLuint vertex_normal_loc = glGetAttribLocation(pid, "vertex_normal");
    
    // 加載紋理
    texture_id = create_texture_2d("texture.bmp");
    // 加載模型
    loadModel("cube.obj", vertices);
    // 設定相機初始位置
    camera.set_position(0.0f, 0.0f, 2.0f);
    
    // 建立VBO
    GLuint vbo_id;
    glGenBuffers(1, &vbo_id);
    glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
    
    // 傳輸資料
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertices.size(), &vertices[0], GL_STATIC_DRAW);
    
    // 建立VAO
    GLuint vao_id;
    glGenVertexArrays(1, &vao_id);
    glBindVertexArray(vao_id);
    
    // 啟用頂點屬性
    glEnableVertexAttribArray(vertex_position_loc);
    glEnableVertexAttribArray(vertex_texture_coord_loc);
    glEnableVertexAttribArray(vertex_normal_loc);
    
    glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
    
    // vertex_position_loc與頂點資料映射
    glVertexAttribPointer(vertex_position_loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
    
    // vertex_texture_coord_loc與紋理資料映射
    glVertexAttribPointer(vertex_texture_coord_loc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (0+sizeof(Vec3)));
    
    // vertex_normal_loc與法線資料映射
    glVertexAttribPointer(vertex_normal_loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)(0+sizeof(Vec3) + sizeof(Vec2)));
    
}      

glBufferData用于向VBO傳遞資料:第二個參數表示資料的大小,第三個參數表示指向資料的指針;

glVertexAttribPointer用于将頂點着色器中的attribute變量與VBO中的資料進行綁定:第一個參數表示屬性的位置,第二個參數表示分量的個數,第三個參數表示資料類型,第四個參數表示是否歸一化,第五個參數表示連續頂點屬性之間的偏移量,第六個參數表示元件的第一個分量在對應的數組頂點屬性中的偏移量

例子中VBO存儲的資料格式如下:

VBO = {v1.x, v1.y, v1.z, vt1.x, vt1.y, vt1.z, vn1.x, vn1.y, vn1.z,
v2.x, v2.y, v2.z, vt2.x, vt2.y, vt2.z, vn2.x, vn2.y, vn2.z,
... ...
vn.x, vn.y, vn.z, vtn.x, vtn.y, vtn.z, vnn.x, vnn.y, vnn.z,}      

以取到法線資料為例,法線資料是vn1.xyz到vnn.xyz,從VBO的格式可知,vn1.x在VBO的 0+sizeof(Vec3)+sizeof(Vec2)位置,vn1.x到vn2.x之間相差了sizeof(Vertex)大小,這就是glVertexAttribPointer第五和第六個參數的由來。

最後繪制的時候,我們隻需要調用以下代碼即可

glBindVertexArray(vao_id);
glDrawArrays(GL_QUADS, 0, (GLsizei)vertices.size());      

繼續閱讀