1. GLSL GLSL(OpenGL Shading Language) :是一种专门为GPU设计 的编程语言,用于编写顶点着色器 和片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 #version version_number in type in_variable_name; out type out_variable_name; uniform type uniform_name; void main () { }
2. 向量 向量(vector) :是一个包含有n个分量 的容器,分量的类型可以是基础数据类型int、float、double、uint和bool
类型 含义 vecn 包含n个float分量的向量 bvecn 包含n个bool分量的向量 ivecn 包含n个int分量的向量 uvecn 包含n个unsigned int分量的向量 dvecn 包含n个double分量的向量
如何获取分量? 通用/位置:使用.x、.y、.z、.w获取它们的(x,y,z,w)分量 颜色:使用.r、.g、.b、.a获取它们的(r,g,b,a)分量 纹理:使用.s、.t获取它们的(s,t)分量 重组(Swizzling) :将向量的分量重新组合成一个新的向量
1 2 3 4 5 6 7 8 9 10 11 12 vec2 v1; vec4 v2 = v1. xyxx; vec3 v3 = v2. zyw; vec4 v4 = v1. xxxx + v3. yxzy; vec2 v1 = vec2 (0.5 , 0.7 ); vec2 v2 = vec2 (v1); vec4 v3 = vec4 (v1, 0.0 , 0.0 );
3. 输入输出 layout (location = <索引>):顶点着色器不是获取顶点数组,而是直接获取顶点数据 ,因此需要指定索引 来告诉OpenGL从哪个位置获取数据赋值给输入变量
1 2 3 4 layout (location = <索引>) in type in_variable_name;out type out_variable_name;
着色器通信:在发送方着色器声明一个输出变量,在接收方着色器声明一个输入变量 ,且这两个变量的类型和名称必须一致 ,否则OpenGL无法链接这两个变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #version 330 core layout (location = 0 ) in vec3 aPos;out vec4 vertexColor; void main () { gl_Position = vec4 (aPos, 1.0 ); vertexColor = vec4 (0.5 , 0.0 , 0.0 , 1.0 ); } #version 330 core in vec4 vertexColor; out vec4 FragColor; void main () { FragColor = vertexColor; }
Uniform是一种从CPU向GPU发送数据 的方式,因为GPU不允许CPU对其进行写操作 ,所以我们需要一个全局变量Uniform ,使得CPU可以写入值,而GPU可以读取值
1 2 3 4 5 6 7 8 9 10 uniform type uniform_name; int vertexColorLocation = glGetUniformLocation (shaderProgram, "uniform_name" );glUseProgram (shaderProgram);glUniform4f (vertexColorLocation, color_value);
glUniform有一个特定的后缀,可以标识设定的类型,如f需要一个float类型,i需要一个int类型,3f需要一个3个float值,4f需要4个float值
5. 着色器处理多个属性 只需要通过layout (location = <索引>)来指定索引来获取不同索引的数据即可
假设我们有如下数据
1 2 3 4 5 6 float vertices[] = { 0.5f , -0.5f , 0.0f , 1.0f , 0.0f , 0.0f , -0.5f , -0.5f , 0.0f , 0.0f , 1.0f , 0.0f , 0.0f , 0.5f , 0.0f , 0.0f , 0.0f , 1.0f };
在顶点着色器中获取位置和颜色
1 2 3 4 5 6 7 8 #version 330 core layout (location = 0 ) in vec3 aPos;layout (location = 1 ) in vec3 aColor;out vec3 ourColor; void main () { gl_Position = vec4 (aPos, 1.0 ); ourColor = aColor; }
在片段着色器中获取颜色
1 2 3 4 5 6 #version 330 core in vec3 ourColor; out vec4 FragColor; void main () { FragColor = vec4 (ourColor, 1.0 ); }
需要更新VAO
1 2 3 4 5 6 glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 6 * sizeof (float ), (void *)0 );glEnableVertexAttribArray (0 );glVertexAttribPointer (1 , 3 , GL_FLOAT, GL_FALSE, 6 * sizeof (float ), (void *)(3 * sizeof (float )));glEnableVertexAttribArray (1 );
为什么三角形不是单纯的红蓝绿?片段插值(Fragment Interpolation) :片段着色器隐式地将三角形内每个片段的颜色通过三个顶点的颜色插值得到 ,比如,如果一条线的上端是绿色而下端是蓝色,位于线段靠上三等分点的位置的片段颜色将是66.7%蓝色和33.3%绿色的线性组合 ,最终呈现出颜色渐变 的效果
6. 着色器类 6.1 结构 为什么需要一个着色器类? 如果每一个着色器都需要在main文件中编程,那么代码将会复杂且冗余,因此我们希望可以编写一个类文件 来封装 这些形式相同,类型和数据不同 的函数操作,使得main函数可以利用类来使用着色器 着色器代码手动填写到字符串中,不利于编写和维护,因此我们希望可以从硬盘中读取着色器代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #ifndef SHADER_H #define SHADER_H #include <glad/glad.h> ; #include <string> #include <fstream> #include <sstream> #include <iostream> using namespace std;class Shader { public : unsigned int ID; Shader (const char * vertexPath, const char * fragmentPath); void use () ; void setBool (const string &name, bool value) const ; void setInt (const string &name, int value) const ; void setFloat (const string &name, float value) const ; }; #endif
6.2 构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Shader (const char * vertexPath, const char * fragmentPath) { string vertexCode; string fragmentCode; ifstream vShaderFile; ifstream fShaderFile; vShaderFile.open (vertexPath); fShaderFile.open (fragmentPath); stringstream vShaderStream, fShaderStream; vShaderStream << vShaderFile.rdbuf (); fShaderStream << fShaderFile.rdbuf (); vShaderFile.close (); fShaderFile.close (); vertexCode = vShaderStream.str (); fragmentCode = fShaderStream.str (); const char * vShaderCode = vertexCode.c_str (); const char * fShaderCode = fragmentCode.c_str (); unsigned int vertex, fragment; vertex = glCreateShader (GL_VERTEX_SHADER); fragment = glCreateShader (GL_FRAGMENT_SHADER); glShaderSource (vertex, 1 , &vShaderCode, NULL ); glShaderSource (fragment, 1 , &fShaderCode, NULL ); glCompileShader (vertex); glCompileShader (fragment); ID = glCreateProgram (); glAttachShader (ID, vertex); glAttachShader (ID, fragment); glLinkProgram (ID); glDeleteShader (vertex); glDeleteShader (fragment); }
6.3 成员函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void use () { glUseProgram (ID); } void setBool (const string &name, bool value) const { glUniform1i (glGetUniformLocation (ID, name.c_str ()), (int )value); } void setInt (const string &name, int value) const { glUniform1i (glGetUniformLocation (ID, name.c_str ()), value); } void setFloat (const string &name, float value) const { glUniform1f (glGetUniformLocation (ID, name.c_str ()), value); }
6.4 使用着色器类 在main函数中使用着色器类
1 2 3 4 5 6 7 8 9 10 Shader ourShader ("path/to/shader.vs" , "path/to/shader.fs" ) ;while (...){ ourShader.use (); ourShader.setFloat ("someUniform" , 1.0f ); [...]; }