一、什么是shader?
shader是一段GLSL(openGL着色語言)小程序,運行在GPU(圖形處理器),而非CPU使用GLSL語言編寫,看上去像c或c++,但卻是另外一種不同的語言。使用shader就像寫個普通程序一樣,寫代碼-->編譯-->鏈接在一起才能生成最終的程序。
着色器類似一個函數調用的方式--數據傳輸進來,經過處理,然后再傳輸出去。每個着色器看起來像一個完整的c程序,它的輸入點就是一個名為main()的函數,但與c不同的是,GLSL的main函數沒有任何參數,也沒有返回值。
着色器的建立:
1>創建着色器(頂點和片元)在程序啟示的位置,總是要用#version來聲名所使用的版本。
glCreatShader(GLenum type),type為GL_VERTEX_SHADER或者是GL_FRAGMENT_SHADER
unsigned int vertexShader; vertexShader = glCreateShader(GL_VERTEX_SHADER);
我們把需要創建的着色器類型以參數形式提供給glCreateShader。由於我們正在創建一個頂點着色器,傳遞的參數是GL_VERTEX_SHADER。
下一步我們把這個着色器源碼附加到着色器對象上,然后編譯它:void glShaderSource(GLuint shader,GLsizei count,const GLchar * const *string,const GLint *length);
頂點着色器源碼(儲存在一個C的字符串中),所以第二個參數為1.第三個參數是頂點着色器真正的源碼。
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
2>編譯着色器
glCompileShader(Gluint shader)。結果查詢使用glGetShaderiv(),例如:glGetShaderiv(Gluint shader,GL_COMPILE_STATUS,&compiled)
1 int success; 2 char infoLog[512]; 3 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
首先我們定義了一個整型變量來表示是否成功編譯,還定義了一個存儲錯誤消息的容器(如果有的話)。然后用glGetShaderiv檢查是否編譯成功。如果編譯失敗,用glGetShaderInfoLog獲取錯誤消息,然后打印出來。
1 if(!success) 2 { 3 glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); 4 std::cout << "頂點着色器編譯錯誤\n" << infoLog << std::endl; 5 }
3>把着色器添加到程序中
GLuint glCreatPogram();創建一個空的程序
void glAttachShader(GLuint program,GLuint shader);把着色器加到程序中
4>鏈接你的程序
鏈接程序的時候會把頂點着色器的輸出作為片段着色器的輸入。
void glLinkProgram(GLuint program);
void glGetProgramiv(program,GL_LINK_STATUS,&linked);
5>使用程序
void glUseProgram(GLuint program)
那shader到底干了什么?這取決於是哪種shader.
二、Vertex Shader
頂點着色器主要用來將點(x,y,z)變換成不同的點。頂點只是幾何形狀中的一個點,一個點叫vectex,多個點叫vertices。在本教程中,我們的三角形需要三個頂點(vertices)組成。
Vertex Shader的GLSL代碼如下:
1 #version 330 core
2 layout(location = 0) in vec3 vert;
3 void main() {
4 // does not alter the vertices at all
5 gl_Position = vec4(vert, 1.0);//或者是gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
6 }
第一行#version 330
告訴OpenGL這個shader使用GLSL版本3.3核心版本
第二行 layout(location = 0)設定輸入變量的位置值,之后會有用到
in vec3 vert;
告訴shader有一個輸入變量(in)vert
第三行定義函數main
,這是shader運行入口。這看上去像C,(區別:)但GLSL中main
不需要帶任何參數,並且不用返回,用void。
第四行gl_Position = vec4(vert, 1);
將輸入的頂點直接輸出,變量gl_Position
是OpenGL定義的全局變量,用來存儲vertex shader的輸出。所有vertex shaders都需要對gl_Position
進行賦值。
gl_Position
是4D坐標(vec4),但vert
是3D坐標(vec3),所以我們需要將vert
轉換為4D坐標vec4(vert, 1)
。第二個的參數1
是賦值給第四維坐標。(因為矩陣變換均是利用齊次坐標)
Vertex Shader在本文中沒有做任何事,后續我們會修改它來處理動畫,攝像機和其它東西。
頂點着色器源碼(儲存在一個C的字符串中)
三、Fragment Shader
片元着色器的主要功能是計算每個需要繪制的像素點的顏色。
一個”fragment”基本上就是一個像素,所以你可以認為片段着色器(fragment shader)就是像素着色器(pixel shader)。在本文中每個片段都是一像素,但這並不總是這樣的。你可以更改某個OpenGL設置,以便得到比像素更小的片段。
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);//橘黃色
}
創建並編譯:
1 unsigned int fragmentShader; 2 fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); 3 glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); 4 glCompileShader(fragmentShader);
現在兩個着色器都編譯過了,剩下的事情是把兩個着色器對象鏈接到一個用來渲染的着色器程序中。(shader program)
四、着色器程序
着色器程序對象是多個着色器合並之后最終鏈接完成的,若要使用剛才編譯的着色器,我們必須把它們鏈接為一個着色器程序對象,然后再渲染對象的時候激活這個着色器程序。
1 //創建 2 unsigned int shaderProgram; 3 shaderProgram = glCreateProgram(); 4 //依附並鏈接 5 glAttachShader(shaderProgram, vertexShader); 6 glAttachShader(shaderProgram, fragmentShader); 7 glLinkProgram(shaderProgram);
得到的結果就是一個程序對象,我們可以調用glUseProgram函數,用剛創建的程序對象作為它的參數,以激活這個程序對象。
glUseProgram(shaderProgram);
就像着色器的編譯一樣,我們也可以檢測鏈接着色器程序是否失敗,並獲取相應的日志
1 glGetProgramiv(shaderProgram,GL_LINK_STATUS,&success); 2 if (!success) { 3 glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog); 4 cout << "着色器程序鏈接出錯" << infoLog << endl; 5 }
在把着色器對象鏈接到程序對象以后,記得刪除着色器對象,我們不再需要它們了:
1 glDeleteShader(vertexShader); 2 glDeleteShader(fragmentShader);
現在,我們已經把輸入頂點數據發送給了GPU,並指示了GPU如何在頂點和片段着色器中處理它。就快要完成了,但還沒結束,OpenGL還不知道它該如何解釋內存中的頂點數據,以及它該如何將頂點數據鏈接到頂點着色器的屬性上。我們需要告訴OpenGL怎么做。
五、鏈接頂點屬性
使用glVertexAttribPointer函數告訴OpenGL該如何解析頂點數據.
1 glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),0); 2 glEnableVertexAttribArray(0);
第一個參數指定我們要配置的頂點屬性.之前我們在頂點着色器中使用layout(location = 0)
定義了position頂點屬性的位置值(Location)。我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入0
。
第二個參數指定頂點屬性的大小。頂點屬性是一個vec3
,它由3個值組成,所以大小是3。
第三個參數指定數據的類型,這里是GL_FLOAT(GLSL中vec*
都是由浮點數值組成的)。
第四個參數定義我們是否希望數據被標准化(Normalize)。如果我們設置為GL_TRUE,所有數據都會被映射到0(對於有符號型signed數據是-1)到1之間。我們把它設置為GL_FALSE。
第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。由於下個組位置數據在3個float
之后,我們把步長設置為3 * sizeof(float)
。要注意的是由於我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)
最后一個參數的類型是void*
,所以需要我們進行這個奇怪的強制類型轉換。表示偏移量,設為0.