1. 程式人生 > >學習OpenGL系列二 圖元

學習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.圖元表達

 為了描述一個圖元,我們需要使用一些有意義的資料來表達他。例如使用頂點資訊或者使用畫素資訊。這些資料會被儲存在

OpenGL的快取,即快取物件(Buffer Object)當中。在OpenGL中,有著8種不同型別的快取物件。不同表達形式,會儲存在不同的快取物件中。

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寫於南京。