由於這里的知識點很細碎又不是很多,所以我邊學OpenGl一邊把需要用到的GLSL知識寫上去。
0.概念和初始化:
着色器分為頂點着色器(Vertex Shader)和片元着色器(Fragment Shader),語法類似C++,OpenGL對每一個頂點都執行一次頂點着色器,對所有片元執行片元着色器(片元可以狹隘的理解為像素)。
初始化:
In C++:
GLuint programID = LoadShaders( "TransformVertexShader.vertexshader", "ColorFragmentShader.fragmentshader" );
獲得到的是鏈接的着色器句柄。但是LoadShaders卻需要我們自己編寫。

GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){ // Create the shaders GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER); GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); // Read the Vertex Shader code from the file std::string VertexShaderCode; std::ifstream VertexShaderStream(vertex_file_path, std::ios::in); if(VertexShaderStream.is_open()){ std::stringstream sstr; sstr << VertexShaderStream.rdbuf(); VertexShaderCode = sstr.str(); VertexShaderStream.close(); }else{ printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !\n", vertex_file_path); getchar(); return 0; } // Read the Fragment Shader code from the file std::string FragmentShaderCode; std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in); if(FragmentShaderStream.is_open()){ std::stringstream sstr; sstr << FragmentShaderStream.rdbuf(); FragmentShaderCode = sstr.str(); FragmentShaderStream.close(); } GLint Result = GL_FALSE; int InfoLogLength; // Compile Vertex Shader printf("Compiling shader : %s\n", vertex_file_path); char const * VertexSourcePointer = VertexShaderCode.c_str(); glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL); glCompileShader(VertexShaderID); // Check Vertex Shader glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> VertexShaderErrorMessage(InfoLogLength+1); glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]); printf("%s\n", &VertexShaderErrorMessage[0]); } // Compile Fragment Shader printf("Compiling shader : %s\n", fragment_file_path); char const * FragmentSourcePointer = FragmentShaderCode.c_str(); glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL); glCompileShader(FragmentShaderID); // Check Fragment Shader glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result); glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1); glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]); printf("%s\n", &FragmentShaderErrorMessage[0]); } // Link the program printf("Linking program\n"); GLuint ProgramID = glCreateProgram(); glAttachShader(ProgramID, VertexShaderID); glAttachShader(ProgramID, FragmentShaderID); glLinkProgram(ProgramID); // Check the program glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result); glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength); if ( InfoLogLength > 0 ){ std::vector<char> ProgramErrorMessage(InfoLogLength+1); glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]); printf("%s\n", &ProgramErrorMessage[0]); } glDetachShader(ProgramID, VertexShaderID); glDetachShader(ProgramID, FragmentShaderID); glDeleteShader(VertexShaderID); glDeleteShader(FragmentShaderID); return ProgramID; }
大意是通過文件流獲取兩個着色器代碼,然后分別編譯這兩個代碼得到兩個着色器句柄,最后把創建一個進程把這兩個句柄合一塊,最后把這個進程鏈接到主程序中。
由於此代碼是如此的枯燥,所以最好封裝成一個庫。
1.傳入屬性(Attribute):
In GLSL,Vertex Shader:
layout(location = 0) in vec3 vertexPosition_modelspace;
我們要知道,我們傳進去的數據是頂點屬性,0代表一個位置,OpenGL確保至少有16個包含4分量的頂點屬性可用,但是有些硬件或許允許更多的頂點屬性,你可以查詢GL_MAX_VERTEX_ATTRIBS來獲取具體的上限。
in vec3表示輸入,使用vec3這種類型,后面的是名字。
In C++:
第一步創建一個VBO把頂點數據拷進去:
GLuint vertexbuffer; glGenBuffers(1, &vertexbuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
參數應該很好懂,最后一個函數glBufferData的第四個參數是GL_STATIC_DRAW,代表靜態數據,不能修改,可以提高性能。
第二步:
glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); glVertexAttribPointer( 0, // attribute. No particular reason for 0, but must match the layout in the shader. 3, // size GL_FLOAT, // type GL_FALSE, // normalized? 0, // stride (void*)0 // array buffer offset );
一步步解釋。首先得知道,我們向着色器里傳進去的是頂點屬性(Vertex Attribute),之前說過它的數量是有限制的。
第一行glEnableVertexAttribArray(0)指定的是啟用location=0的頂點屬性,此時在GLSL中可以用layout(location = 0)接收到。
第二行把vertexbuffer——我們定義的頂點緩沖,之前我把頂點類型為GLfloat的一維數組,其中每連續三個元素代表一個頂點——綁定到當前上下文的GL_ARRAY_BUFFER中,在使用前要綁定,這時我們就可以使用——
第三行glVertexAttribPointer,它用於指定頂點數組的數據格式和位置。首先是位置,第一個參數,這里填的0,對應layout的0;3代表三個元素一個頂點;GL_FLOAT代表我們的類型;GL_FALSE代表不進行標准化(向量標准化就是把(1,10,1,0)變成(0.1,1,0.1,0));0代表步長(此處等效於4),最后一個是數組偏移量,因為我們從數組下標為0開始定義頂點,所以填0。
為什么要指定這些呢?因為OpenGL只知道array buffer里的二進制數據,並不知道這些數據的意義,所以當我們要把這些數據傳給着色器的時候,必須要事先指定類型和位置。
最后,隨手關門好習慣,記得關閉頂點屬性和銷毀頂點數組緩沖區。
glDisableVertexAttribArray(0);//關閉位置為0的頂點屬性 glDeleteBuffers(1, &vertexbuffer);//銷毀頂點數組緩沖
2.傳入變量(Variable)
使用uniform關鍵字來實現,適用於所有着色器統一傳入的常量。
In GLSL,Vertex Shader
使用uniform關鍵字傳入一個矩陣:
uniform mat4 MVP;
注意,雖然說是變量,但uniform傳入的變量實際上不可修改。
In C++:
GLuint MatrixID = glGetUniformLocation(programID, "MVP");
通過glGetUniformLocation獲取指定名字的uniform的句柄,programID是我們之前提到的着色器句柄。
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
通過glUniformMatrix4fv傳入數據,MatrixID是之前獲取到的句柄,1代表數組數量或者矩陣的數量,GL_FALSE指定矩陣是列優先矩陣,最后傳入數據開頭的指針。
除了傳入矩陣,還有以下函數來對uniform進行裝載(沒有重載太蛋疼了):
這是獲取uniform地址,返回一個句柄,接下來的裝載將使用這個句柄。