OpenGL程式設計逐步深入(十)索引繪製
準備知識
OpenGl提供了一些繪圖函式。到目前為止我們使用的glDrawArrays繪圖函式屬於”順序繪製”。這意味著頂點緩衝區從指定的偏移量開始被掃描,每X(點為1,直線為2等)個頂點構成一個圖元。這樣使用起來非常方便,缺點是當多個圖元共用一個頂點時,這個頂點必須在頂點緩衝區中出現多次。也就是說,這些頂點沒有共享的概念。屬於”索引繪製”的函式則提供這種共享機制。我們除了一個頂點快取區外,還有一個索引快取區用來存放頂點的索引值。索引快取區的掃描和頂點快取區類似,以每X個索引對應的頂點構成一個基本圖元。共享機制在提高記憶體使用效率上非常重要,因為計算機中的絕大多數圖形物件都是三角形網格構成的,這些三角形有很多都是共用頂點。
我們來看一下順序繪製:
如果是繪製三角形,GPU會將這些頂點分成以下幾組:V0/1/2, V3/4/5, V6/7/8。
接下來看一下索引繪製:
這種情況下,GPU會使用這幾組頂點來繪製三角形:V2/0/1, V5/2/4, V6/5/7。
使用索引繪製方式需要建立索引緩衝區,索引快取區中的資料還要受到頂點緩衝區的限制,繪圖呼叫的API函式也和之前不同。
專案配置
參見前面的教程。
程式程式碼
清單1.主程式程式碼tutorial10.cpp
/*
Copyright 2010 Etay Meiri
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Tutorial 10 - Indexed draws
*/
#include "stdafx.h"
#include <string.h>
#include <assert.h>
#include <math.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include "ogldev_math_3d.h"
GLuint VBO;
GLuint IBO;
GLuint gWorldLocation;
const char* pVSFileName = "shader.vs";
const char* pFSFileName = "shader.fs" ;
static void RenderSceneCB()
{
glClear(GL_COLOR_BUFFER_BIT);
static float Scale = 0.0f;
Scale += 0.01f;
Matrix4f World;
World.m[0][0] = cosf(Scale); World.m[0][1] = 0.0f; World.m[0][2] = -sinf(Scale); World.m[0][3] = 0.0f;
World.m[1][0] = 0.0; World.m[1][1] = 1.0f; World.m[1][2] = 0.0f ; World.m[1][3] = 0.0f;
World.m[2][0] = sinf(Scale); World.m[2][1] = 0.0f; World.m[2][2] = cosf(Scale) ; World.m[2][3] = 0.0f;
World.m[3][0] = 0.0f; World.m[3][1] = 0.0f; World.m[3][2] = 0.0f ; World.m[3][3] = 1.0f;
glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, &World.m[0][0]);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(0);
glutSwapBuffers();
}
static void InitializeGlutCallbacks()
{
glutDisplayFunc(RenderSceneCB);
glutIdleFunc(RenderSceneCB);
}
static void CreateVertexBuffer()
{
Vector3f Vertices[4];
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(0.0f, -1.0f, 1.0f);
Vertices[2] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[3] = Vector3f(0.0f, 1.0f, 0.0f);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}
static void CreateIndexBuffer()
{
unsigned int Indices[] = { 0, 3, 1,
1, 3, 2,
2, 3, 0,
0, 1, 2 };
glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}
static void AddShader(GLuint ShaderProgram, const char* pShaderText, GLenum ShaderType)
{
GLuint ShaderObj = glCreateShader(ShaderType);
if (ShaderObj == 0) {
fprintf(stderr, "Error creating shader type %d\n", ShaderType);
exit(1);
}
const GLchar* p[1];
p[0] = pShaderText;
GLint Lengths[1];
Lengths[0]= strlen(pShaderText);
glShaderSource(ShaderObj, 1, p, Lengths);
glCompileShader(ShaderObj);
GLint success;
glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
if (!success) {
GLchar InfoLog[1024];
glGetShaderInfoLog(ShaderObj, 1024, NULL, InfoLog);
fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
exit(1);
}
glAttachShader(ShaderProgram, ShaderObj);
}
static void CompileShaders()
{
GLuint ShaderProgram = glCreateProgram();
if (ShaderProgram == 0) {
fprintf(stderr, "Error creating shader program\n");
exit(1);
}
string vs, fs;
if (!ReadFile(pVSFileName, vs)) {
exit(1);
};
if (!ReadFile(pFSFileName, fs)) {
exit(1);
};
AddShader(ShaderProgram, vs.c_str(), GL_VERTEX_SHADER);
AddShader(ShaderProgram, fs.c_str(), GL_FRAGMENT_SHADER);
GLint Success = 0;
GLchar ErrorLog[1024] = { 0 };
glLinkProgram(ShaderProgram);
glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
if (Success == 0) {
glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
exit(1);
}
glValidateProgram(ShaderProgram);
glGetProgramiv(ShaderProgram, GL_VALIDATE_STATUS, &Success);
if (!Success) {
glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Invalid shader program: '%s'\n", ErrorLog);
exit(1);
}
glUseProgram(ShaderProgram);
gWorldLocation = glGetUniformLocation(ShaderProgram, "gWorld");
assert(gWorldLocation != 0xFFFFFFFF);
}
int _tmain(int argc, _TCHAR* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA);
glutInitWindowSize(1024, 768);
glutInitWindowPosition(100, 100);
glutCreateWindow("Tutorial 10");
InitializeGlutCallbacks();
// Must be done after glut is initialized!
GLenum res = glewInit();
if (res != GLEW_OK) {
fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
return 1;
}
printf("GL version: %s\n", glGetString(GL_VERSION));
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
CreateVertexBuffer();
CreateIndexBuffer();
CompileShaders();
glutMainLoop();
return 0;
}
程式碼解讀
GLuint IBO;
建立索引快取控制代碼,對索引緩衝區的操作通過該控制代碼完成。
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(0.0f, -1.0f, 1.0f);
Vertices[2] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[3] = Vector3f(0.0f, 1.0f, 0.0f);
為了演示頂點共享,我們需要更復雜的網格模型。很多教程都使用旋轉立方體來演示這個知識點,這需要8個頂點和12個三角形。這裡我們使用旋轉四面體代替,它只需要4個頂點和4個三角形。
當我們從上面(即沿著Y軸)觀看這些頂點時,頂點佈局如下圖所示:
unsigned int Indices[] = { 0, 3, 1,
1, 3, 2,
2, 3, 0,
0, 1, 2 };
索引緩衝區由一個索引陣列組成,每個索引對應頂點緩衝區中的一個頂點。同時觀察索引陣列和上面的頂點分佈圖會發現最後一個三角形構成四面體的底面,另外三個三角形作為側表面(這個例子中四面體並不是對稱的)。
glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
上面程式碼用於建立索引緩衝區並向其中填充資料,不同的是這裡使用的引數是GL_ELEMENT_ARRAY_BUFFER,而建立頂點緩衝區使用的引數為GL_ARRAY_BUFFER。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
和順序繪製方式一樣,在繪製之前我們也要呼叫glBindBuffer,同時使用GL_ELEMENT_ARRAY_BUFFER作為引數。
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);
這裡我們需要使用glDrawElements代替glDrawArrays。第一個引數指定要繪製的圖元型別,第二個引數指定用來生成圖元的索引數量,第三個引數是每個索引的資料型別,這裡可供選擇的有GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT。最後一個引數用來告訴GPU開始掃描索引緩衝區的偏移量,這裡指定為0,表明從第一個索引開始。這個引數是非常有用的,因為有時候多個模型的頂點索引存放在同一個索引緩衝區。
執行效果
可以看到一個四面體在視窗中旋轉。