opengl頂點資料傳送和著色器處理(vao,vbo)
OpenGL學習腳印: 頂點資料傳送和著色器處理1
寫在前面
本節內容翻譯和整理自《Learning Modern 3D Graphics Programming》Chapter1內容。作為學習目的,本文內容上不會完全遵從原文,有刪節。另外原文示例程式碼有它獨有的框架組織方式,為了保持自己的一貫風格,這裡重寫了示例程式程式碼,如果發現錯誤,請糾正我。轉載需經過作者同意。
通過本節,你可以瞭解到:
- OpenGL中頂點資料傳遞方式
- Shader著色器的工作原理和過程
- VAO、VBO的使用
1.頂點資料傳遞方式
把握兩點:
我們要在哪兒分配OpenGL可見的記憶體(給它資料buffer object)?
我們怎麼告訴OpenGL如何解釋分配的記憶體(給它附加描述資訊glVertexAttribPointer)?
繪製管線的第一階段是將頂點資料對映到裁剪空間。在OpenGL這樣處理前,它必須接受一個頂點列表。因此,管線的最初階段是傳送三角形資料到OpenGL。這是我們要傳送的資料:
- 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
- };
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?- //建立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);
//建立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?- #version 330
- layout(location = 0) in vec4 position;
- void main()
- {
- gl_Position = position;
- }
#version 330
layout(location = 0) in vec4 position;
void main()
{
gl_Position = position;
}
看起來很簡單。第一行定義版本3.30。所有GLSL著色器都必須宣告版本。
下一行,定義了頂點著色器的輸入。這個輸入變數是position,型別是4維的浮點型的vec4。Layout 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?- void reshape (int w, int h)
- {
- glViewport(0, 0, (GLsizei) w, (GLsizei) h);
- }
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?- <span style="font-size:14px;">#version 330
- out vec4 outputColor;
- void main()
- {
- outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
- }</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 著色器輔助類標頭檔案
- #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, constchar* fileName);
- static GLuint createProgram(const std::vector<GLuint> &shaderList);
- };
- #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?- #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;
- }
#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?- //依賴庫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;
- }
- }
//依賴庫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;
}
}
執行效果如下圖所示: