1. 程式人生 > >opengl頂點資料傳送和著色器處理(vao,vbo)

opengl頂點資料傳送和著色器處理(vao,vbo)

OpenGL學習腳印: 頂點資料傳送和著色器處理1

寫在前面

               本節內容翻譯和整理自《Learning Modern 3D Graphics Programming》Chapter1內容。作為學習目的,本文內容上不會完全遵從原文,有刪節。另外原文示例程式碼有它獨有的框架組織方式,為了保持自己的一貫風格,這裡重寫了示例程式程式碼,如果發現錯誤,請糾正我。轉載需經過作者同意。

通過本節,你可以瞭解到:

  • OpenGL中頂點資料傳遞方式
  • Shader著色器的工作原理和過程
  • VAO、VBO的使用

1.頂點資料傳遞方式

把握兩點:

我們要在哪兒分配OpenGL可見的記憶體(給它資料buffer object)?

 我們怎麼告訴OpenGL如何解釋分配的記憶體(給它附加描述資訊glVertexAttribPointer)?

繪製管線的第一階段是將頂點資料對映到裁剪空間。在OpenGL這樣處理前,它必須接受一個頂點列表。因此,管線的最初階段是傳送三角形資料到OpenGL。這是我們要傳送的資料:

[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. const GLfloat vertices[] = {  
  2.         -0.5f,-0.5f,0.0f,1.0f,  
  3.         0.5f,0.0f,0.0f,1.0f,  
  4.         0.0f,0.5f,0.0f,1.0f  
  5.      };  
const GLfloat vertices[] = {
		-0.5f,-0.5f,0.0f,1.0f,
		0.5f,0.0f,0.0f,1.0f,
		0.0f,0.5f,0.0f,1.0f
	 };

這些資料每行的4個值代表一個4D頂點座標,因為裁剪座標系是4維的。

這些座標已經在裁剪座標系範圍內了。我們想要OpenGL做得就是根據這些資料繪製三角形。

 儘管我們已經有了資料,OpenGL並不能直接使用它們。OpenGL對它能讀取的記憶體有些限制。你可以按需分配你的頂點資料,但是這些記憶體對OpenGL並不直接可見。因此,第一步就是分配OpenGL可見的記憶體,並填充我們的資料。這是通過快取物件(buffer object,以下簡稱BO)來實現的。

一個快取物件,是一個線性陣列形式的記憶體,由OpenGL根據使用者請求管理和分配。這塊記憶體的內容可由使用者控制,但是使用者也僅能間接地控制。可以把buffer object當做GPU記憶體中的陣列。

GPU可以快速讀取它,因此在它裡面儲存資料有效能優勢。在本節中,快取物件是這樣來建立的:

程式碼片段:

[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. //建立vertex buffer object物件
  2.  glGenBuffers(1,&vboId);  
  3.  glBindBuffer(GL_ARRAY_BUFFER,vboId);  
  4.  glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);  
  5.  glBindBuffer(GL_ARRAY_BUFFER,0);  
 //建立vertex buffer object物件
	 glGenBuffers(1,&vboId);
	 glBindBuffer(GL_ARRAY_BUFFER,vboId);
	 glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
	 glBindBuffer(GL_ARRAY_BUFFER,0);


第一行,建立了buffer object,並將控制代碼儲存在全域性變數中。儘管物件已經存在,它沒有任何空間。因為我們還未給他分配任何空間。glBindBuffer函式將新建的BO繫結到GL_ARRAY_BUFFER上下文中。

glBufferData 函式執行兩個操作。它分配了當前繫結到glBufferData 的快取的空間,這就是我們剛剛建立和繫結的BO。我們已經有了頂點資料,問題是它在我們的RAM中而不是OpenGL的記憶體中。sizeof(vertexPositions) 計算頂點陣列的位元組大小。我們向glBufferData 傳遞此值來表明分配空間的大小。這樣在GPU記憶體中就有足夠空間來儲存頂點資料。

glBufferData 執行的另一個操作是從我們的陣列記憶體RAM中拷貝資料到BO中。第三個引數控制了這個複製。如果這個引數不是NULL,正如此例,glBufferData 會將指標所指資料拷貝到BO中。當這個函式執行完後,BO中就有了頂點資料了。

第四個引數,稍後解釋。

第二次呼叫glBufferData ,執行的是清理任務。通過繫結0值到GL_ARRAY_BUFFER,我們使之前繫結到這個目標的BO從該目標解除繫結。0在這裡充當了NULL指標的作用。這並不是必須的,因為之後的繫結會自動解除已有的繫結。但是除非你嚴格的控制著你的渲染,通常解除你繫結的物件是個好的想法。

這樣完成了傳送頂點資料到GPU的任務。但是BO中的資料時未格式化的,但這是OpenGL關心的。我們只是分配了BO,並填充了些隨機二進位制資料。現在我們需要告訴OpenGL,BO中有頂點資料,並告訴他頂點資料的格式。我們通過下面這樣的程式碼來完成這一任務:

glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);

第一個函式宣告使用BO。第二個函式啟動頂點屬性陣列,這個稍後解釋。

第三個函式是關鍵的。glVertexAttribPointer,儘管,包含”Pointer”一詞,但是實際上它處理的並不是指標,而是BO。

在渲染時,OpenGL從BO中提取頂點資料。我們要做的就是通告OpenGL儲存在BO中的頂點陣列中資料格式。也就是要告訴OpenGL如何解釋BO中的陣列。

在我們的案例中,資料格式如下

  •      表示位置的單個數據值以32位浮點資料儲存,使用C/C++ float型別。
  •      每個位置由4個這樣的值組成。
  •      每4個值之間沒有間隙,資料值在陣列中緊密相連。
  •      陣列中第一個值在BO的開始處

glVertexAttribPointer 函式告訴了OpenGL所有這些情況。第三個引數制定了值得基本型別,即GL_FLOAT,對應32位浮點資料。第二個引數,指定多少個這樣的值組成一個位置,即一個點。在這裡,即4個值組成一個點。第5個引數指定資料間間隙,第6個引數指定BO中資料偏移量,0代表從BO的開始處算起。

第四個引數以後再做解釋。

還有一件事好像沒做,那就是指定從哪個BO中獲取資料。這是一個隱含的而不是顯示的關係。glVertexAttribPointer 總是指向它呼叫是繫結到GL_ARRAY_BUFFER 的快取。因此,這裡不需要傳遞BO的控制代碼。

關於這個函式,更多的解釋,此處不再展開。

一旦OpenGL知道從哪裡獲取頂點資料,就可以渲染三角形了:

glDrawArrays(GL_TRIANGLES, 0, 3);

2.頂點處理和著色器

把握兩點:

頂點和片元著色器的輸入輸出

頂點資料的流向

既然我們能夠告訴OpenGL定點資料了,我們轉到繪製管線的下一階段:頂點處理。

在這個階段中涉及到使用著色器(Shader)

著色器就是執行在GPU上的一個程式而已。在繪製管線中有幾個可能的著色器階段,每個階段都有它自己的輸入輸出。著色器的目的是將輸入包括潛在的其他型別資料,轉換到輸出集中。

每個著色器都在輸入集上執行。值得注意的是,在任何階段,一個著色器都完全獨立於那一階段的其他著色器。獨立執行的著色器之間不會有交叉。每個輸入集的處理從著色器開始到結束階段。著色器定義了它的輸入輸出,通常,沒有完成輸出資料任務的著色器是非法的。

頂點著色器,從名字上來看,操作的就是頂點。具體來說,每次觸發頂點著色器都作用於單個頂點。這些著色器除了使用者定義的輸出外,必須輸出頂點的裁剪座標系中的位置。如何計算這個裁剪座標系位置,完全取決於著色器。

OpenGL中,著色器使用GLSL( OpenGL Shading Language )語言書寫。看起來好像C語言,但實際上受到的限制很大,例如不能使用遞迴。我們的頂點著色器看起來是這樣:

[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. #version 330
  2. layout(location = 0) in vec4 position;  
  3. void main()  
  4. {   
  5.  gl_Position = position;  
  6. }  
#version 330

layout(location = 0) in vec4 position;

void main()

{ 

 gl_Position = position;

}

看起來很簡單。第一行定義版本3.30。所有GLSL著色器都必須宣告版本。

下一行,定義了頂點著色器的輸入。這個輸入變數是position,型別是4維的浮點型的vec4Layout location 0稍後解釋。

就像C語言一樣,以main函式開始。這個著色器很簡單,它的任務,就是將輸入position拷貝到輸出gl_position。這個變數是著色器內建變數,如果你看到”gl_”開頭的變數,那麼一定是內建變數。”gl_”變數,不允許自己定義,你只能使用已經存在的。

gl_Position定義如下

out vec4 gl_Position;

剛剛說過,頂點著色器的基本任務是產生裁剪座標系中頂點位置。這就是gl_Position,它就是裁剪座標系的座標。因為我們定義的頂點資料已經在裁剪座標系下了,因此直接輸出它即可。

頂點屬性

著色器有著輸入輸出,就好比一個有引數和返回值的函式一樣。

輸入和輸出來自和轉到一些地方去了。因此,輸入position 肯定在某處被填充了資料。那麼這些資料來自哪裡呢?頂點著色器的輸入被稱為頂點屬性(vertex attributes)

你可能認得一些類似的頂點屬性術語,例如,“glEnableVertexAttribArray” 或者 “glVertexAttribPointer.”

這就是資料如何從管線中流動下來的。在渲染開始時,BO中的頂點資料,在glVertexAttribPointer初始化工作的基礎上來讀取。

這個函式描述了屬性中資料的來源。glVertexAttribPointer和頂點著色器中某個字串名字對應的輸入之間的連線時有些複雜的。

每個頂點著色器的輸入有一個索引位置稱作屬性索引(attribute index.)。在上例中輸入定義為:

layout(location = 0) in vec4 position;

Layout location部分將屬性索引0賦給position了。屬性索引必須不小於0.並且受到硬體限制。

在程式碼中,當引用屬性時,總是有屬性索引來解引用。glEnableVertexAttribArray、glDisableVertexAttribArray和glVertexAttribPointer函式都將屬性索引作為第一個引數。我們將屬性索引0賦給positon,因此在程式碼中呼叫時也是如此。glEnableVertexAttribArray(0) 啟用了指向position屬性的索引。下圖的圖解釋了資料時如何流到著色器中的:


如果沒有呼叫glEnableVertexAttribArray, 在glVertexAttribPointer按索引呼叫就沒什麼意思。

這個啟用屬性的呼叫不一定非得在頂點屬性指標之前呼叫,但是在渲染前必須呼叫。如果屬性沒有開啟,在渲染階段就不會被使用。

光柵化

現在所有完成的任務是,3個頂點被髮送到OpenGL,並且由頂點著色器轉換為裁剪座標系中的3個位置。接下來,頂點位置將會通過把xyz三個分量除以W分量而轉換為規格化裝置座標系。在我們的例子中,W都是1.0,因此我們的座標已經都是有效地規則化裝置座標了。

在這之後,頂點位置被轉換為螢幕座標。這是通過視口轉換(viewport transform.)來完成的。這樣稱呼是因為完成它的glViewport這個函式。本例中當視窗改變大小是,每次都呼叫這個函式。當視窗大小改變時總是呼叫reshape 函式,該函式如下:

[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. void reshape (int w, int h)  
  2. {   
  3.   glViewport(0, 0, (GLsizei) w, (GLsizei) h);  
  4. }  
void reshape (int w, int h)

{ 

  glViewport(0, 0, (GLsizei) w, (GLsizei) h);

}

這個告訴OpenGL那個可用區域將用來渲染。在本例中,我們使用全部的可用區域。如果沒有這個函式呼叫,調整視窗大小將不會影響渲染。同時,記住我們沒有保持寬高比為常量,這樣會導致三角形變形。

回憶一下,螢幕座標(0,0)在左下角。這個函式把左下角位置作為頭兩個座標,而把視口的寬度和高度作為另外兩個座標。

一旦在螢幕座標中了,OpenGL將會取這3個座標,掃描轉換為一些列的片元。要完成這項任務,OpenGL必須決定這個頂點列表代表什麼。OpenGL解析一個定點列表的方式各有不同。使用命令:

glDrawArrays(GL_TRIANGLES, 0, 3);告訴它繪製三角形。

流向光柵器中資料如下圖所示:


片元處理

片元著色器用於計算輸出片元的顏色。它的輸入包括螢幕座標下片元的XYZ座標,也可以包括使用者定義資料,這裡就不做介紹。

我們的片元著色器定義如下:

[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. <span style="font-size:14px;">#version 330  
  2. out vec4 outputColor;  
  3. void main()  
  4. {    
  5. outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);  
  6. }</span>  
<span style="font-size:14px;">#version 330

out vec4 outputColor;

void main()

{  

outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);

}</span>

首行同樣是版本宣告。

下一行指定片元著色器的輸出變數,型別為vec4

主函式中僅僅使用了4維向量,來表達顏色。

儘管為片元著色器提供了螢幕座標下的座標,但是這裡不需要它,因而也就沒有使用它。在片元著色器執行完後,片元的輸出顏色就被輸出到影象中了。

注意:

在頂點著色器中使用layout(location = #)來表明屬性索引,在頂點陣列和頂點著色器之間建立關聯。但是片元著色器的輸出基本上都是到當前渲染的影象,在我們的例子中是螢幕,因此如果你在片元著色器中只定義了一個輸出變數,那麼這個變數將自動寫入到當前的目標影象。


3.著色器的生成

著色器的生成可以參見下圖:

一個著色器字串被編譯後成為著色器物件Shader Object.一個或者多個著色器物件連結成為著色器程式program Object.

注意在生成著色器物件和程式時,要訪問他們被建立的狀態,如果出錯了,要進行出錯處理。

這裡面沒什麼需要細講的東西,下面給出他們的一個實現版本:

shader.h 著色器輔助類標頭檔案

[cpp] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. #ifndef _SHADER_H_
  2. #define _SHADER_H_
  3. #include <vector>
  4. #include <string>
  5. #include <cstring>
  6. #include <GL/glew.h>
  7. class Shader {  
  8. public:  
  9.     static GLuint createShader(GLenum eShaderType, const std::string &strShaderFile);  
  10.     static GLuint createShader(GLenum eShaderType, constchar* fileName);  
  11.     static GLuint createProgram(const std::vector<GLuint> &shaderList);  
  12. };  
  13. #endif
#ifndef _SHADER_H_
#define _SHADER_H_
#include <vector>
#include <string>
#include <cstring>
#include <GL/glew.h>
class Shader {
public:
	static GLuint createShader(GLenum eShaderType, const std::string &strShaderFile);
	static GLuint createShader(GLenum eShaderType, const char* fileName);
	static GLuint createProgram(const std::vector<GLuint> &shaderList);
};

#endif

shader.cpp 著色器輔助類實現檔案

[html] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. #include <fstream>
  2. #include <sstream>
  3. #include "shader.h"  
  4. //從字串流構造著色器物件  
  5. GLuint Shader::createShader(GLenum eShaderType, const std::string &strShaderFile)  
  6. {  
  7.     GLuint shader = glCreateShader(eShaderType);//根據型別建立shader  
  8.     const char * strFileData = strShaderFile.c_str();  
  9.     glShaderSource(shader,1,&strFileData,NULL);//繫結shader字串  
  10.     glCompileShader(shader);//編譯shader  
  11.     //檢查shader狀態  
  12.     GLint status;  
  13.     glGetShaderiv(shader,GL_COMPILE_STATUS,&status);  
  14.     if(status == GL_FALSE)  
  15.     {  
  16.        GLint infoLogLength;  
  17.        glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&infoLogLength);  
  18.        GLchar *strInfoLog = new GLchar[infoLogLength+1];  
  19.        glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);  
  20.        const char * strShaderType = NULL;  
  21.        switch(eShaderType)  
  22.        {  
  23.             case GL_VERTEX_SHADER : strShaderType = "vertex";break;  
  24.             case GL_GEOMETRY_SHADER : strShaderType = "geometry";break;  
  25.             case GL_FRAGMENT_SHADER : strShaderType = "fragment";break;  
  26.        }  
  27.        fprintf(stderr,"Compile failure in %s shader:\n%s\n",strShaderType,strInfoLog);  
  28.        delete[] strInfoLog;  
  29.     }  
  30.     return shader;  
  31. }  
  32. //從檔案構造著色器物件  
  33. GLuint Shader::createShader(GLenum eShaderType, const char* fileName)  
  34. {  
  35.     std::ifstream infile(fileName);  
  36.     if(!infile)   
  37.     {  
  38.        fprintf(stderr,"Could not open file : %s for reading.",fileName);  
  39.        return 0;  
  40.     }  
  41.     std::stringstream  buffer;  
  42.     buffer <<infile.rdbuf();  
  43.     infile.close();  
  44.     return Shader::createShader(eShaderType,buffer.str());  
  45. }  
  46. //構造著色器程式物件  
  47. GLuint Shader::createProgram(const std::vector<GLuint> &shaderList)  
  48. {  
  49.     GLuint programId = glCreateProgram();//建立program  
  50.     for(std::vector<GLuint>::size_type iLoop = 0;iLoop <shaderList.size();iLoop++)  
  51.         glAttachShader(programId,shaderList[iLoop]);//繫結shader  
  52.     glLinkProgram(programId);//連結shader  
  53.     //檢查program狀態  
  54.     GLint status;  
  55.     glGetProgramiv (programId, GL_LINK_STATUS, &status);  
  56.     if (status == GL_FALSE)  
  57.     {  
  58.         GLint infoLogLength;  
  59.         glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength);  
  60.         GLchar *strInfoLog = new GLchar[infoLogLength + 1];  
  61.         glGetProgramInfoLog(programId, infoLogLength, NULL, strInfoLog);  
  62.         fprintf(stderr, "Linker failure: %s\n", strInfoLog);  
  63.         delete[] strInfoLog;  
  64.     }  
  65.     for(size_t iLoop = 0; iLoop <shaderList.size(); iLoop++)  
  66.         glDetachShader(programId, shaderList[iLoop]);  
  67.     return programId;  
  68. }  
#include <fstream>
#include <sstream>
#include "shader.h"
//從字串流構造著色器物件
GLuint Shader::createShader(GLenum eShaderType, const std::string &strShaderFile)
{
	GLuint shader = glCreateShader(eShaderType);//根據型別建立shader
	const char * strFileData = strShaderFile.c_str();
	glShaderSource(shader,1,&strFileData,NULL);//繫結shader字串
	glCompileShader(shader);//編譯shader
	//檢查shader狀態
	GLint status;
	glGetShaderiv(shader,GL_COMPILE_STATUS,&status);
	if(status == GL_FALSE)
	{
	   GLint infoLogLength;
	   glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&infoLogLength);
	   GLchar *strInfoLog = new GLchar[infoLogLength+1];
	   glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);
	   const char * strShaderType = NULL;
	   switch(eShaderType)
	   {
			case GL_VERTEX_SHADER : strShaderType = "vertex";break;
			case GL_GEOMETRY_SHADER : strShaderType = "geometry";break;
			case GL_FRAGMENT_SHADER : strShaderType = "fragment";break;
	   }
	   fprintf(stderr,"Compile failure in %s shader:\n%s\n",strShaderType,strInfoLog);
	   delete[] strInfoLog;
	}
	return shader;
}
//從檔案構造著色器物件
GLuint Shader::createShader(GLenum eShaderType, const char* fileName)
{
	std::ifstream infile(fileName);
	if(!infile) 
	{
	   fprintf(stderr,"Could not open file : %s for reading.",fileName);
	   return 0;
	}
	std::stringstream  buffer;
	buffer << infile.rdbuf();
	infile.close();
	return Shader::createShader(eShaderType,buffer.str());
}
//構造著色器程式物件
GLuint Shader::createProgram(const std::vector<GLuint> &shaderList)
{
	GLuint programId = glCreateProgram();//建立program
	for(std::vector<GLuint>::size_type iLoop = 0;iLoop < shaderList.size();iLoop++)
		glAttachShader(programId,shaderList[iLoop]);//繫結shader

	glLinkProgram(programId);//連結shader
	//檢查program狀態
	GLint status;
	glGetProgramiv (programId, GL_LINK_STATUS, &status);
	if (status == GL_FALSE)
	{
		GLint infoLogLength;
		glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength);

		GLchar *strInfoLog = new GLchar[infoLogLength + 1];
		glGetProgramInfoLog(programId, infoLogLength, NULL, strInfoLog);
		fprintf(stderr, "Linker failure: %s\n", strInfoLog);
		delete[] strInfoLog;
	}
	for(size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
		glDetachShader(programId, shaderList[iLoop]);
	return programId;
}

4.完整示例

本節的完整示例,利用VBO傳送頂點資料,利用頂點著色器和片元著色器處理頂點,他們的字串都以std::string形式寫在程式碼中了(當然,也可以由檔案讀取)。

著色器shader.h和實現見上文程式碼,示例完整程式碼如下:

[html] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. //依賴庫glew32.lib freeglut.lib  
  2. //使用VAO VBO和著色器繪製三角形(現代OpenGL方式)  
  3. #include <string>
  4. #include <vector>
  5. #include <GL/glew.h>
  6. #include <GL/freeglut.h>
  7. #include "shader.h"  
  8. using namespace std;  
  9. void userInit();  
  10. void reshape(int w,int h);  
  11. void display( void );  
  12. void keyboardAction( unsigned char key, int x, int y );  
  13. GLuint vboId;//vertex buffer object控制代碼  
  14. GLuint vaoId;//vertext array object控制代碼  
  15. GLuint programId;//shader program 控制代碼  
  16. int main( int argc, char **argv )  
  17. {  
  18.     glutInit(&argc, argv);  
  19.     glutInitDisplayMode( GLUT_RGBA|GLUT_DOUBLE);  
  20.     glutInitWindowPosition(100,100);  
  21.     glutInitWindowSize( 512, 512 );  
  22.     glutCreateWindow( "Triangle demo" );  
  23.     glewInit();  
  24.     userInit();  
  25.     glutReshapeFunc(reshape);  
  26.     glutDisplayFunc( display );  
  27.     glutKeyboardFunc( keyboardAction );  
  28.     glutMainLoop();  
  29.     return 0;  
  30. }  
  31. //自定義初始化函式  
  32. void userInit()  
  33. {  
  34.      glClearColor( 0.0, 0.0, 0.0, 0.0 );  
  35.      //建立頂點資料  
  36.      const GLfloat vertices[] = {  
  37.         -0.5f,-0.5f,0.0f,1.0f,  
  38.         0.5f,0.0f,0.0f,1.0f,  
  39.         0.0f,0.5f,0.0f,1.0f  
  40.      };  
  41.      //建立vertex array object物件  
  42.      glGenVertexArrays(1,&vaoId);  
  43.      glBindVertexArray(vaoId);  
  44.      //建立vertex buffer object物件  
  45.      glGenBuffers(1,&vboId);  
  46.      glBindBuffer(GL_ARRAY_BUFFER,vboId);  
  47.      glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);  
  48.      glBindBuffer(GL_ARRAY_BUFFER,0);  
  49.      //建立著色器  
  50.      const std::string vertexStr (  
  51.          "#version 330\n"  
  52.          "layout(location=0) in vec4 position;\n"  
  53.          "void main()\n"  
  54.          "{gl_Position = position;}\n"  
  55.      );  
  56.     const std::string fragmentStr(  
  57.         "#version 330\n"  
  58.         "out vec4 outputColor;\n"  
  59.         "void main()\n"  
  60.         "{outputColor = vec4(1.0f,1.0f,0.0f,1.0f);}\n"  
  61.      );  
  62.     std::vector<GLuint> idVector;  
  63.     idVector.push_back(Shader::createShader(GL_VERTEX_SHADER,vertexStr));  
  64.     idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER,fragmentStr));  
  65.     programId = Shader::createProgram(idVector);  
  66. }  
  67. //調整視窗大小回調函式  
  68. void reshape(int w,int h)  
  69. {  
  70.     glViewport(0,0,(GLsizei)w,(GLsizei)h);  
  71. }  
  72. //繪製回撥函式  
  73. void display( void )  
  74. {  
  75.     glClear( GL_COLOR_BUFFER_BIT);  
  76.     glUseProgram(programId);  
  77.     glBindBuffer(GL_ARRAY_BUFFER,vboId);  
  78.     glEnableVertexAttribArray(0);  
  79.     glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);  
  80.     glDrawArrays(GL_TRIANGLES, 0, 3);  
  81.     glBindBuffer(GL_ARRAY_BUFFER,0);  
  82.     glUseProgram(0);  
  83.     glDisableVertexAttribArray(0);  
  84.     glutSwapBuffers();  
  85. }  
  86. //鍵盤按鍵回撥函式  
  87. void keyboardAction( unsigned char key, int x, int y )  
  88. {  
  89.     switch( key )   
  90.     {  
  91.         case 033:  // Escape key  
  92.             exit( EXIT_SUCCESS );  
  93.             break;  
  94.     }  
  95. }  
//依賴庫glew32.lib freeglut.lib
//使用VAO VBO和著色器繪製三角形(現代OpenGL方式)
#include <string>
#include <vector>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include "shader.h"
using namespace std;

void userInit();
void reshape(int w,int h);
void display( void );
void keyboardAction( unsigned char key, int x, int y );


GLuint vboId;//vertex buffer object控制代碼
GLuint vaoId;//vertext array object控制代碼
GLuint programId;//shader program 控制代碼

int main( int argc, char **argv )
{
	glutInit(&argc, argv);

    glutInitDisplayMode( GLUT_RGBA|GLUT_DOUBLE);
	glutInitWindowPosition(100,100);
    glutInitWindowSize( 512, 512 );
    glutCreateWindow( "Triangle demo" );
	 
    glewInit();
    userInit();
	glutReshapeFunc(reshape);
    glutDisplayFunc( display );
    glutKeyboardFunc( keyboardAction );
    glutMainLoop();
    return 0;
}
//自定義初始化函式
void userInit()
{
	 glClearColor( 0.0, 0.0, 0.0, 0.0 );
	 //建立頂點資料
	 const GLfloat vertices[] = {
		-0.5f,-0.5f,0.0f,1.0f,
		0.5f,0.0f,0.0f,1.0f,
		0.0f,0.5f,0.0f,1.0f
	 };
	 //建立vertex array object物件
	 glGenVertexArrays(1,&vaoId);
	 glBindVertexArray(vaoId);
	 //建立vertex buffer object物件
	 glGenBuffers(1,&vboId);
	 glBindBuffer(GL_ARRAY_BUFFER,vboId);
	 glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
	 glBindBuffer(GL_ARRAY_BUFFER,0);
	 //建立著色器
	 const std::string vertexStr (
		 "#version 330\n"
		 "layout(location=0) in vec4 position;\n"
		 "void main()\n"
		 "{gl_Position = position;}\n"
	 );
	const std::string fragmentStr(
		"#version 330\n"
		"out vec4 outputColor;\n"
		"void main()\n"
		"{outputColor = vec4(1.0f,1.0f,0.0f,1.0f);}\n"
	 );
	std::vector<GLuint> idVector;
	idVector.push_back(Shader::createShader(GL_VERTEX_SHADER,vertexStr));
	idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER,fragmentStr));
	programId = Shader::createProgram(idVector);
}
//調整視窗大小回調函式
void reshape(int w,int h)
{
	glViewport(0,0,(GLsizei)w,(GLsizei)h);
}
//繪製回撥函式
void display( void )
{
    glClear( GL_COLOR_BUFFER_BIT);
	glUseProgram(programId);
	glBindBuffer(GL_ARRAY_BUFFER,vboId);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,0,0);

	glDrawArrays(GL_TRIANGLES, 0, 3);

	glBindBuffer(GL_ARRAY_BUFFER,0);
	glUseProgram(0);
	glDisableVertexAttribArray(0);
	glutSwapBuffers();
}
//鍵盤按鍵回撥函式
void keyboardAction( unsigned char key, int x, int y )
{
    switch( key ) 
	{
		case 033:  // Escape key
			exit( EXIT_SUCCESS );
			break;
    }
}

執行效果如下圖所示: