學習OpenGL系列二 圖元
學習OpenGL 圖元
1.GLEW
GLEW(the OpenGL Extension Wrangler Library),它是一個跨平臺的C++擴充套件庫,基於OpenGL圖形介面。GLEW為我們提供了非常便捷的整合,只要包含一個glew.h標頭檔案,就能使用gl,glu,glext,wgl,glx的全部函式。從本章開始,將會引入這個庫,具體庫檔案已在第一章給出。
值得注意的是,使用GLEW,需要將glew.h置於 freeglut.h 之前。
同時對GLEW的初始化glewInit() 需要在glut初始化與建立視窗之後。
2.圖元表達
為了描述一個圖元,我們需要使用一些有意義的資料來表達他。例如使用頂點資訊或者使用畫素資訊。這些資料會被儲存在
3.建立快取物件
在OpenGL中建立一個快取物件主要分三步:
使用glGenBuffers()生成新快取物件。
使用glBindBuffer()繫結快取物件。
使用glBufferData()將資料拷貝到快取物件中。
4.關於Gen Bind 的理解
在OpenGL中經常會出現行如glGen*的函式,他們的作用是返回n個未使用的物件控制代碼(或叫物件名稱),卻不分配任何記憶體。而諸如glBind*的函式則是將該物件控制代碼繫結到相應的快取上。一開始看著很奇怪,可以這樣理解的,OpenGL使用了一個容器比如棧來統一管理其中的物件,因此需要先在棧中分配一個位置。而glBind* 則是對全域性變數的一種賦值操作,這樣OpenGL程式內部就可以通過呼叫固定的全域性變數,來達到控制不同物件的效果。狀態機也類同。
5.頂點快取物件
頂點快取物件(VBO Vertex Buffer Object)是緩衝物件的一種,顧名思義,VBO用來儲存頂點的,VBO是在GPU中載入頂點最有效的方式。在使用VBO時,緩衝區必須繫結到GL_ARRAY_BUFFER,下面給出一個例子:
#include <stdio.h> #include <iostream> #include <GL/glew.h> #include <GL/freeglut.h> using namespace std; static void DisplayCallback() { glClear(GL_COLOR_BUFFER_BIT); int index = 0; glEnableVertexAttribArray(index); //啟用頂點陣列 glVertexAttribPointer(index, 3, GL_FLOAT, GL_FALSE, 0, 0); //設定頂點屬性訪問資料量 glDrawArrays(GL_POINTS, 0, 1); //繪製圖元序列 (點) glDisableVertexAttribArray(index); //禁用頂點陣列 glFlush(); } static void Init() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //快取物件三部曲 GLuint VBO; //物件控制代碼 GLfloat Vertices[] = {0.0f, 0.0f ,0.0f}; //頂點資料 glGenBuffers(1, &VBO); //分配控制代碼 glBindBuffer(GL_ARRAY_BUFFER, VBO); //繫結快取 glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), //新增資料 Vertices, GL_STATIC_DRAW); } static void Display() { glutDisplayFunc(DisplayCallback); } int main(int argc, char** argv) { //1.GLUT初始化 glutInit(&argc, argv); //2.建立視窗 glutInitDisplayMode(GLUT_RGBA); glutInitWindowSize(1024, 768); glutInitWindowPosition(100, 100); glutCreateWindow("Hello Primitive"); //3.GLEW初始化 注意這裡的位置 GLenum res = glewInit(); if (res != GLEW_OK) { fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res)); system("pause"); return 1; } //4.資料初始化 Init(); //5.註冊繪製回撥函式 Display(); //6.進入主迴圈 glutMainLoop(); return 0; }
6.1程式碼函式分析
1.glewInit
函式原型:
GLenum glewInit();
在使用glew之前需要使用該函式對其初始化,其使用位置要在GLUT初始化和建立視窗之後。
返回值為錯誤程式碼,GLEW_OK為成功初始化,可以使用glewGetErrorString獲取相應的錯誤資訊。
2.glGenBuffers
函式原型:
void glGenBuffers(GLsizei n,GLuint* buffers);
返回n個當前未使用的快取物件名稱,並儲存到buffers陣列中。返回到buffers中的名稱不一定是連續的。
3.glBindBuffer
函式原型:
void glBindBuffer(GLenum target,GLuint buffer);
將快取物件繫結到相應的快取上,target 可以是很多種快取,目標名GL_ARRAY_BUFFER 是buffer將儲存一個頂點陣列。其他型別下文有所介紹。
4.glBufferData
函式原型:
void glBufferData(GLenum target,GLsizeiptr size,const GLvoid* data,GLenum usage);
綁定了我們的物件後,可以使用該函式在OpenGL服務端記憶體中分配size個儲存單元,用於向其中儲存資料或索引。
Note:如果當前繫結的物件已經存在了關聯的資料,那麼首先會刪除這些資料。
引數 size 為待傳遞資料大小。如果所需size大小超過了能夠分配的額度,那麼glBufferData()將會產生一個GL_OUT_OF_MEMORY的錯誤。
引數 data 為待傳遞資料的指標。如果data為NULL,則僅僅預留給定資料大小的記憶體空間。
引數 target常用設定: (from 紅寶書)
頂點屬性資料:GL_ARRAY_BUFFER
索引屬性:GL_ELEMENT_ARRAY_BUFFER
畫素資料:GL_PIXEL_UNPACK_BUFFER
從OpenGL獲取的畫素資料:GL_PIXEL_PACK_BUFFER
快取間複製資料:GL_COPY_READ_BUFFER、GL_COPY_WIRTE_BUFFER
紋理快取儲存資料:GL_TEXTURE_BUFFER
TransForm FeedBack著色器獲得的結果:GL_TRANSFORM_FEEDBACK_BUFFER
一致變數:GL_UNIFORM_BUFFER
還有更多,但紅寶書中提到的這些會在後文中慢慢出現。
引數usage用於設定分配資料之後的讀取和寫入方式。方式包括:
GL_STATIC_DRAW,GL_STATIC_READ,GL_STATIC_COPY
GL_DYNAMIC_DRAW,GL_DYNAMIC_READ,GL_DYNAMIC_COPY
GL_STREAM_DRAW,GL_STREAM_READ,GL_STREAM_COPY
其中,static 表示緩衝物件的資料將不會被改動,dynamic 表示資料將會被頻繁改動,stream 表示每幀資料都要改變。draw read copy 則代表行為,繪製,讀取,複製。
5.glEnableVertexAttribArray
函式原型:
void glEnableVertexAttribArray(GLuint index);
該函式會啟用一個與index相關聯的頂點屬性陣列。在後續頂點著色器的使用中,可以使用這個函式來傳遞頂點屬性。在這裡啟用後,在後邊會被一些頂點陣列命令訪問,例如glDrawArrays()。當再次呼叫不同index的該函式時,後者會覆蓋前者。
6.glDisableVertexAttribArray
函式原型:
void glDisableVertexAttribArray(GLuint index);
用於取消對index頂點屬性陣列的啟用。預設為全部禁用。
7.glVertexAttribPointer
函式原型:
void glVertexAttribPointer(GLuint index,GLint size,GLenum type,GLboolean normalized,GLsizei stride,const GLvoid* pointer);
用於告訴管線怎樣解析buffer中的資料。在這裡,我們會解析GL_ARRAY_BUFFER中的資料。
引數index 定義了頂點屬性的索引,我們會對該頂點屬性進行修改。
引數size 為屬性中的元素個數,比如現在的3為X,Y,Z 如果是RGBA就為4
引數type 為每個元素的資料型別。
引數normalized 是否歸一化。在unity中normalize會取模為1,這裡應該相同。
引數stride 表示陣列中兩個連續元素之間的間隔位元組數。如果為0,那麼記憶體中各個資料就是緊密結合的。
引數 pointer 為記憶體偏移值。
8.glDrawArrays
函式原型:
void glDrawArrays(GLenum mode,GLint first,GLsizei count);
該函式用於繪製幾何圖形。該函式為最簡單的draw call ,cpu在這才真正進行工作,將整合收集到的引數和狀態資料繪製到螢幕上。在這裡使用順序繪製,就是按照順序一個個處理頂點,較為簡單。還有一種叫做索引繪製,主要用於共用頂點繪圖。
引數 mode 為渲染圖元的型別,比如點GL_POINTS ,三角 GL_TRAINGLES
引數 first 指定啟用陣列中的起始索引。
引數 count 指定要渲染的索引數。
6.2 繪製三角形
#include <stdio.h>
#include <iostream>
#include <GL/glew.h>
#include <GL/freeglut.h>
using namespace std;
static void DisplayCallback()
{
glClear(GL_COLOR_BUFFER_BIT);
int index = 0;
glEnableVertexAttribArray(index); //啟用頂點陣列
glVertexAttribPointer(index, 3, GL_FLOAT, GL_FALSE, 0, 0); //設定頂點屬性訪問資料量
glDrawArrays(GL_TRIANGLES, 0, 3); //繪製圖元序列 (三角形)
glDisableVertexAttribArray(index); //禁用頂點陣列
glFlush();
}
static void Init()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//快取物件三部曲
GLuint VBO; //物件控制代碼
GLfloat Vertices[3][3] = { //頂點資料
{ -1.0f, -1.0f, 0.0f },
{ 1.0f, -1.0f, 0.0f },
{ 0.0f, 1.0f, 0.0f }
};
glGenBuffers(1, &VBO); //分配控制代碼
glBindBuffer(GL_ARRAY_BUFFER, VBO); //繫結快取
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), //分配空間
NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertices[0]), Vertices[0]); //填充資料 0-2
glBufferSubData(GL_ARRAY_BUFFER, sizeof(Vertices[0]), sizeof(Vertices[0]), Vertices[1]); //填充資料 3-5
glBufferSubData(GL_ARRAY_BUFFER, sizeof(Vertices[0])*2, sizeof(Vertices[0]), Vertices[2]); //填充資料 6-8
}
static void Display()
{
glutDisplayFunc(DisplayCallback);
}
int main(int argc, char** argv)
{
//1.GLUT初始化
glutInit(&argc, argv);
//2.建立視窗
glutInitDisplayMode(GLUT_RGBA);
glutInitWindowSize(1024, 768);
glutInitWindowPosition(100, 100);
glutCreateWindow("Hello Triangle");
//3.GLEW初始化 注意這裡的位置
GLenum res = glewInit();
if (res != GLEW_OK) {
fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
system("pause");
return 1;
}
//4.資料初始化
Init();
//5.註冊繪製回撥函式
Display();
//6.進入主迴圈
glutMainLoop();
return 0;
}
在這裡使用了glBufferData()資料為空,同時glBufferSubData()來進行新增資料。
參考文獻
《OpenGL程式設計指南》
Dore寫於南京。