着色器使用glsl的类C语言写成的。着色器开头声明版本,接着是输入和输出变量,uniform和main函数。每个着色器入口点是main函数,在该函数中处理输入变量,将结果存放到输出变量中。
着色器结构如下:
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
// 处理输入并进行一些图形操作
...
// 输出处理过的结果到输出变量
out_variable_name = weird_stuff_we_processed;
}
unsigned int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
int, float, double, uint以及bool,还有两种容器类型向量(Vector)和矩阵(Matrix)。
向量:
它包含1,2,3,4个分量的容器,分量类型是默认基本类型中的任意一个。
有以下形式:
vecn:包含n个 float分量的默认向量
bvecn:包含n个 bool分量的向量
ivecn:包含n个int分量的向量
uvecn:包含n个unsigned int分量的向量
dvecn:包含n个double分量的向量
大多时候使用的是vecn,因为float型满足大多数要求。
可通过vec.x,vec.y,vec.z,vec.w来获取对应的第1,2,3,4个分量,也允许对颜色使用rgba或是对纹理坐标使用stpq访问相同分量。
向量还可重组,重组如下形式:
vec2 somVec;
vec4 differentVec = someVec.xyxx;
vec3 anothervec = differentVec.zyw;
vec4 otherVec = somVec.xxxx + anothervec.yxyz;
可使用上面4个字符任意组合来创建一个和原来向量一样长的新向量,必须是同类的,只要原来向量有那些分量即可;然后,不允许使用vec2向量去获取.z元素。也可把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
输入与输出:
使用in关键字设定输入,使用out关键字设定输出。
只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。但在顶点和片段着色器中会有点不同。
1.顶点着色器是从顶点数据中直接接收输入。为了定义定点数据该如何管理,使用location这一元数据指定输入变量,如此才可以在CPU上配置顶点属性。
layout (location = 0),顶点着色器需要为它的输入提供一个额外的layout标识以把它链接到顶点数据。
也可忽略layout (location = 0)标识符,在opengl代码中使用glGetAttribLocation查询属性位置值。
2.片段着色器需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。若没有定义,OpenGL会把物体渲染为黑色或白色。
因此,若打算从一个着色器向另外一个着色器发送数据,必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似输入。当类型和名字都一样时,OpenGL会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。如下代码展示它是如何工作的,将片段着色器1改为暗红色:
...
const char *vertexShaderSource = "#version 330 core\n" //版本声明为3.3,它与opengl版本相匹配
"layout (location = 0) in vec3 aPos;\n" //in关键字声明输入顶点属性,创建一个vec3的输入变量aPos,现只关心位置属性。layout (location = 0)设置输入变量的位置值
"out vec4 vertexColor;\n" //为片段着色器指定一个颜色输出
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n" // glsl的向量数据类型float gl_Position,main函数最后,gl_Position设置的值会成为该顶点着色器的输出
" vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f);\n" //把输出变量设置为暗红色
"}\n";
// 片段着色器1
const char *fragmentShader1Source = "#version 330 core\n"
"out vec4 FragColor;\n" // out关键字声明输出变量FragColor
"in vec4 vertexColor;\n" //从顶点着色器传来的输入变量
"void main()\n"
"{\n"
" FragColor = vertexColor;\n"//"vec4(1.5f, 1.5f, 0.2f, 1.0f);\n" // RGBA A=1.0f表示完全不透明,RGB搭配为黄色
"}\n";
...
即成功的从顶点着色器向片段着色器发送数据,下面从应用程序直接给片段着色器发送一个颜色:
Uniform
它是从CPU中的应用向GPU中的着色器发送数据的方式,它与顶点属性有些不同:
1.uniform是全局的,全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且可被着色器程序的任意着色器在任意阶段访问。
2.无论给uniform设置成什么值,uniform都会一直保存它们的数据。除非重置或更新它们。
声明:
可在片段着色器中添加uniform关键字类型和变量名来声明一个glsl的uniform,再在cpu中设置它的值,让它的颜色值随着时间的改变而改变,如下代码:
片段着色器:
// 片段着色器1
const char *fragmentShader1Source = "#version 330 core\n"
"out vec4 FragColor;\n" // out关键字声明输出变量FragColor
"in vec4 vertexColor;\n" //从顶点着色器传来的输入变量
"uniform vec4 ourColor;\n" // 在opengl程序代码中设置该变量
"void main()\n"
"{\n"
" FragColor = ourColor;\n" //"vertexColor; \n"//"vec4(1.5f, 1.5f, 0.2f, 1.0f); \n" // RGBA A=1.0f表示完全不透明,RGB搭配为黄色
"}\n";
...
float timeValue = glfwGetTime(); //获取运行的秒数
float redValue= (sin(timeValue) / 2.0f) + 0.5f; //使用sin函数让颜色从0.0到1.0之间改变,且将结果存储在greenValue中
int vertexColorLocation = glGetUniformLocation(shaderProgramYellow, "ourColor");//查询uniform ourColor的位置值,提供着色器程序和uniform名字
//注意:查询uniform地址不要求之前使用过着色器程序,但是更新一个uniform之前必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置
// uniform的。
//画第一个三角形
glUseProgram(shaderProgramYellow);//使用着色器程序对象
glUniform4f(vertexColorLocation, redValue, 0.0f, 0.0f, 1.0f); // 设置uniform值
glBindVertexArray(VAOs[0]); //绑定顶点数组对象
...
输出得到红色的渐变色图形。
由于opengl核心为C库,不支持重载,因此参数不同时就要为其定义新的函数。glUniform就是一个例子,该函数有一个特定的后缀,标识设定的uniform类型,如下:
后缀f:函数需要一个float作为它的值
后缀i:函数需要一个int作为它的值
后缀ui:函数需要一个unsigned int作为它的值
后缀3f:函数需要3个float作为它的值
后缀fv:函数需要一个float向量或数组作为它的值
将颜色数据加入顶点数据中:
1.修改顶点数据:
float vertices[] = {
//位置 //颜色
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, //右下
-0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, //左下
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f //顶部
};
const char *vertexShaderSource = "#version 330 core\n" //版本声明为3.3,它与opengl版本相匹配
"layout (location = 0) in vec3 aPos;\n" //in关键字声明输入顶点属性,创建一个vec3的输入变量aPos,现只关心位置属性。layout (location = 0)设置输入变量的位置值
"layout (location = 1) in vec3 aColor;\n" // 颜色变量的属性位置设为1
"out vec3 ourColor;\n" // 向片段着色器输出一个颜色
"out vec4 vertexColor;\n" //为片段着色器指定一个颜色输出
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n" // glsl的向量数据类型float gl_Position,main函数最后,gl_Position设置的值会成为该顶点着色器的输出
" ourColor = aColor;\n"
" vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f);\n" //把输出变量设置为暗红色
"}\n";
// 片段着色器1
const char *fragmentShader1Source = "#version 330 core\n"
"out vec4 FragColor;\n" // out关键字声明输出变量FragColor
"in vec4 vertexColor;\n" //从顶点着色器传来的输入变量
"uniform vec4 ourColor;\n" // 在opengl程序代码中设置该变量
"void main()\n"
"{\n"
" FragColor = vec4(ourColor, 1.0);\n" //"vertexColor; \n"//"vec4(1.5f, 1.5f, 0.2f, 1.0f); \n" // RGBA A=1.0f表示完全不透明,RGB搭配为黄色
"}\n";
由于添加了一个顶点属性,并更新了VBO内存,因此更新后的VBO内存看起来就像:
4.在cpu端使用glVertexAttribPointer函数更新顶点格式:
glBindVertexArray(VAOs[0]);
glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW);
//位置属性
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))); //指定颜色值的偏移量为3*sizeof(float)
// 以顶点属性位置值作为参数启用顶点属性;顶点属性默认是禁用的。
glEnableVertexAttribArray(1);
输出:
该图片可能不是所期望的那种,因为只提供了3个颜色,而不是现在看到的大调色板。这是在片段着色器中进行的所谓片段插值(Fragment Interpolation)的结果。当渲染一个三角形时,光栅化(Rasterization)阶段通常会造成比原指定顶点更多的片段。光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置。
基于这些位置,它会插值(Interpolate)所有片段着色器的输入变量。比如说,我们有一个线段,上面的端点是绿色的,下面的端点是蓝色的。如果一个片段着色器在线段的70%的位置运行,它的颜色输入属性就会是一个绿色和蓝色的线性结合;更精确地说就是30%蓝 + 70%绿。
这正是在这个三角形中发生了什么。我们有3个顶点,和相应的3个颜色,从这个三角形的像素来看它可能包含50000左右的片段,片段着色器为这些像素进行插值颜色。如果你仔细看这些颜色就应该能明白了:红首先变成到紫再变为蓝色。片段插值会被应用到片段着色器的所有输入属性上。
将着色器写到一个类里面,从文件读取着色器内容:
创建一个shader_s.h文件,输入如下代码:
#ifndef SHADER_H
#define SHADER_H
#include
#include
#include
#include
#include
#include
class Shader
{
public:
unsigned int ID;
// constructor generates the shader on the fly
// ------------------------------------------------------------------------
Shader(const char* vertexPath, const char* fragmentPath, const char* geometryPath = nullptr)
{
// 1. retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::string geometryCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
std::ifstream gShaderFile;
// ensure ifstream objects can throw exceptions:
vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
gShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
try
{
// open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// close file handlers
vShaderFile.close();
fShaderFile.close();
// convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
// if geometry shader path is present, also load a geometry shader
if(geometryPath != nullptr)
{
gShaderFile.open(geometryPath);
std::stringstream gShaderStream;
gShaderStream << gShaderFile.rdbuf();
gShaderFile.close();
geometryCode = gShaderStream.str();
}
}
catch (std::ifstream::failure e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char * fShaderCode = fragmentCode.c_str();
// 2. compile shaders
unsigned int vertex, fragment;
int success;
char infoLog[512];
// vertex shader
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// fragment Shader
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// if geometry shader is given, compile geometry shader
unsigned int geometry;
if(geometryPath != nullptr)
{
const char * gShaderCode = geometryCode.c_str();
geometry = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry, 1, &gShaderCode, NULL);
glCompileShader(geometry);
checkCompileErrors(geometry, "GEOMETRY");
}
// shader Program
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
if(geometryPath != nullptr)
glAttachShader(ID, geometry);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// delete the shaders as they're linked into our program now and no longer necessery
glDeleteShader(vertex);
glDeleteShader(fragment);
if(geometryPath != nullptr)
glDeleteShader(geometry);
}
// activate the shader
// ------------------------------------------------------------------------
void use()
{
glUseProgram(ID);
}
// utility uniform functions
// ------------------------------------------------------------------------
void setBool(const std::string &name, bool value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}
// ------------------------------------------------------------------------
void setInt(const std::string &name, int value) const
{
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void setFloat(const std::string &name, float value) const
{
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
// ------------------------------------------------------------------------
void setVec2(const std::string &name, const glm::vec2 &value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec2(const std::string &name, float x, float y) const
{
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}
// ------------------------------------------------------------------------
void setVec3(const std::string &name, const glm::vec3 &value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec3(const std::string &name, float x, float y, float z) const
{
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
// ------------------------------------------------------------------------
void setVec4(const std::string &name, const glm::vec4 &value) const
{
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void setVec4(const std::string &name, float x, float y, float z, float w)
{
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}
// ------------------------------------------------------------------------
void setMat2(const std::string &name, const glm::mat2 &mat) const
{
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat3(const std::string &name, const glm::mat3 &mat) const
{
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
// ------------------------------------------------------------------------
void setMat4(const std::string &name, const glm::mat4 &mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
private:
// utility function for checking shader compilation/linking errors.
// ------------------------------------------------------------------------
void checkCompileErrors(GLuint shader, std::string type)
{
GLint success;
GLchar infoLog[1024];
if(type != "PROGRAM")
{
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
else
{
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if(!success)
{
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
}
}
}
};
#endif
再在源文件中创建两个着色器文件:3.3.shader.vs以及3.3.shader.fs
//3.3.shader.vs
#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;
}
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0f);
}
在cpu端:
包含:
#include
将之前创建着色器的代码屏蔽,替换为:
Shader ourShader("3.3.shader.vs", "3.3.shader.fs");
将类似glUseProgram(shaderProgramYellow)激活使用着色器代码替换为:
ourShader.use();
之后的图形修改只需要修改文件3.3.shader.vs以及3.3.shader.fs文件即可。