着色器又叫Shader,是一种运行在GPU上的小程序。为图形渲染管线的某个特定部分运行。
着色器作用是处理输入并转换为输出。各个着色器之间不能相互影响,之间通过输入和输出进行沟通。
着色器对应的语言为 GLSL
GSL是一种类C语言,是为图形计算量身定制的。
着色器的开头是输入变量,输出变量,uniform和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;
}
数据类型
除了C语言中默认的基础数据类型外,GLSL还有Vector(向量)和Matrix(矩阵)两种容器。
向量
GLSL的向量最多可以包含四分量,分量类型和分量个数都在定义分量名时体现:
访问这些分量时,用.
,如 vec4.x。同样,我们可以直接对GLSL中表示的颜色或者纹理坐标使用.
来访问其分量。
向量支持重组和类似的拷贝操作:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
in
,out
来控制。只要一个输出变量和下一个阶段的出入变量匹配,就会传递下去。location
指定输入变量,这样才可以在CPU上配置顶点属性。而在配置过程中,我们要为输入提供一个额外的layout
标识,这样才可以链接到顶点数据。#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0
out vec4 vertexColor; // 为片段着色器指定一个颜色输出
void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}
片段着色器
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)
void main()
{
FragColor = vertexColor;
}
Uniform也是一种从CPU中向GPU发送数据的方式,但uniform和顶点属性不同。主要在于Uniform是全局的,可以在程序的任意阶段被任意程序所访问,和顶点属性只能依次传给下一个阶段不同。
我们可以在着色器中添加uniform 关键字类型类声明一个 uniform类型变量,这样我们就可以在在程序中对这个变量进行赋值了。
片段着色器:
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量
void main()
{
FragColor = ourColor;
}
这里,我们通过uniform声明了片段着色器的颜色,那么我们就无需通过顶点着色器对它传值了。
注意,如果定义了uniform变量不使用的话,可能会造成严重的错误
接下来,我们对uniform变量进行赋值:
首先,我们需要在指定的着色器程序中查找对应的uniform变量的位置值。
然后,我们激活着色器程序并在其中设置uniform值
int vertexColorLocation = glGetUniformLocation(shaderProgram,"ourColor");
glUserProgram(shaderProgram);
glUniform4f(vertexColorLocation,0.0f, 1.0f, 0.0f, 1.0f);
注意GLSL没有重载,都是通过下标来的。
当然,我们也可以在loop绘制的过程中不断改变uniform变量值
while(!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// 渲染
// 清除颜色缓冲
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 记得激活着色器
glUseProgram(shaderProgram);
// 更新uniform颜色
float timeValue = glfwGetTime();
float greenValue = sin(timeValue) / 2.0f + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
// 绘制三角形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 交换缓冲并查询IO事件
glfwSwapBuffers(window);
glfwPollEvents();
}
uniform完整代码:
//窗口创建头文件
#include
#include
#include
//设置一个窗口回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
//检测用户输入
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
//顶点着色器源码
//"out vec4 VertexColor;\n"
//" VertexColor = vec4(0.5, 0.0, 0.0, 1.0);\n"
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
//片段着色器源码
//"in vec4 VertexColor; \n"
const char* fragmentShaderSource = "#version 330 core\n"
"uniform vec4 ourColor;\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"FragColor = ourColor;\n"
"}\n";
//实例化窗口
int main()
{
//*********************L1********************************//
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
//使用核心模式
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfCreateWindow函数设置窗口的宽和高,以及标题
//GLFWwindow类存放窗口对象的指针
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOPenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "faild" << std::endl;
return -1;
}
//将当前窗口的上下文设置为当前线程的上下文,上下文:OpenGL在其中存储渲染信息的一个数据结构
glfwMakeContextCurrent(window);
//初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Faild" << std::endl;
return -1;
}
//设置渲染窗口,p1,p2设置窗口左下角的位置,p3,p4设置窗口的宽度和高度(按像素算)
glViewport(0, 0, 800, 600);
//使用这个函数可以调用设置视口的函数
//glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//***************************L1***********************************//
//***************************L2**********************************//
//创建一个顶点着色器,用ID来引用
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//把着色器源码赋到着色器对象上,然后编译它
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//创建一个片段着色器,用ID来引用
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
//把着色器源码附加到着色器对象上,然后编译它
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
//把编译后的两个着色器链接到一个程序对象上.
//注意,当链接到下一个着色器时,它会把这个着色器的输出作为下一个着色器的输入
unsigned int shaderProgram;
shaderProgram = glCreateProgram(); //创建一个程序对象
glAttachShader(shaderProgram, vertexShader);//附加着色器
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram); //链接着色器
//然后激活就可以使用了,这时我们调用着色器进行渲染的时候就是使用这个程序对象了
glUseProgram(shaderProgram);
//之前定义的着色器对象就可以删除了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
int vertexColorLocation = glGetUniformLocation(shaderProgram,"ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
//缓存弄好了,接下来就是将数据弄到缓存上去了
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
//定义一个顶点缓存对象
//GPU上有一个特定的缓冲ID,通过引用申请一个VBO对象
unsigned int VBO;
glGenBuffers(1, &VBO);
//下一步就是把缓冲对象绑定到对应的缓存中去,这里我们绑定的缓存类型为GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//定义一个顶点数组对象
//该对象也可以被绑定,任何随后的顶点属性都会被绑定在这个VAO中
unsigned int VAO;
glGenVertexArrays(1, &VAO);
//下一步也就是把这个数组对象绑定到缓存上去
glBindVertexArray(VAO);
//GL_STATIC_DRAW :数据不会或几乎不会改变。
//GL_DYNAMIC_DRAW:数据会被改变很多。
//GL_STREAM_DRAW :数据每次绘制时都会改变。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //绑定数据到缓存
//由于顶点着色器的输入很灵活,我们必须定义输入的值对应的顶点属性
//glVertexAttribPointer()函数定义了Opengl中该如何分析我们输入的顶点数据
//p1表示位置值,p2表示顶点属性的大小vec3对应的就是3,p3对应我们输入的数据类型,p4对应我们是否要标准化
//也就是对应是否要将数据映射到-1到1的空间中去,p5表示数据在缓冲区的位移
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//启用顶点属性函数,参数为对应的顶点属
//加入循环
while (!glfwWindowShouldClose(window)) //用于检查window对象是否还在,也就是还没退出渲染
{
//*****************************************L1*************************************//
//在渲染中不断检测用户动作
processInput(window);
//清空缓存并设置一个默认缓存色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//这里是对颜色缓存进行清空,其他还有深度缓等GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT
//*****************************************L1*************************************//
//***************************************L2************************************//
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
//注意,这里定义的图元为一个三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);//用于交换前缓存和后缓存:也就是绘制图像的过程
glfwPollEvents();//用于检测有没有什么触发事件,并更新窗口状态
}
//清空顶点数据
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
//释放资源
glfwTerminate();
//...
return 0;
}
我们之前讲到,我们可以自定义顶点属性,接下来将演示如何让顶点着色器同时对输入的位置值和颜色值进行读取。
这里,我们定义了一个 1X6的数组,同时传入顶点位置信息和RGB信息。
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 // 顶部
};
对应的顶点着色器代码改为:
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
layout (location = 1) in vec3 aColor; // 颜色变量的属性位置值为 1
out vec3 ourColor; // 向片段着色器输出一个颜色
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor; // 将ourColor设置为我们从顶点数据那里得到的输入颜色
}
这里定义了位置0 和位置1 ,我们可以理解为对这个1X6 数组,我们先读取属性位置为0的vec3作为第一个顶点的位置,然后读取下一个位置的vec3,也就是属性位置值为1的vec3作为第一个顶点的颜色,然后这一个顶点的信息读取完了,接着换下一个顶点按照同样的相对位置进行读取赋值。
由于更新了顶点属性,所以我们必须重新规定GPU对顶点属性的读取规则:
// 位置属性
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); //激活
完整代码:
//窗口创建头文件
#include
#include
#include
//设置一个窗口回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
//检测用户输入
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
//顶点着色器源码
//" VertexColor = vec4(0.5, 0.0, 0.0, 1.0);\n"
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"layout(location = 1) in vec3 aColor;\n"
"out vec3 ourColor;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
" ourColor = aColor;\n"
"}\0";
//片段着色器源码
const char* fragmentShaderSource = "#version 330 core\n"
"in vec3 ourColor; \n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"FragColor = vec4(ourColor,1.0f);\n"
"}\n";
//实例化窗口
int main()
{
//*********************L1********************************//
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
//使用核心模式
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfCreateWindow函数设置窗口的宽和高,以及标题
//GLFWwindow类存放窗口对象的指针
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOPenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "faild" << std::endl;
return -1;
}
//将当前窗口的上下文设置为当前线程的上下文,上下文:OpenGL在其中存储渲染信息的一个数据结构
glfwMakeContextCurrent(window);
//初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Faild" << std::endl;
return -1;
}
//设置渲染窗口,p1,p2设置窗口左下角的位置,p3,p4设置窗口的宽度和高度(按像素算)
glViewport(0, 0, 800, 600);
//使用这个函数可以调用设置视口的函数
//glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//***************************L1***********************************//
//***************************L2**********************************//
//创建一个顶点着色器,用ID来引用
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//把着色器源码赋到着色器对象上,然后编译它
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//创建一个片段着色器,用ID来引用
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
//把着色器源码附加到着色器对象上,然后编译它
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
//把编译后的两个着色器链接到一个程序对象上.
//注意,当链接到下一个着色器时,它会把这个着色器的输出作为下一个着色器的输入
unsigned int shaderProgram;
shaderProgram = glCreateProgram(); //创建一个程序对象
glAttachShader(shaderProgram, vertexShader);//附加着色器
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram); //链接着色器
//然后激活就可以使用了,这时我们调用着色器进行渲染的时候就是使用这个程序对象了
glUseProgram(shaderProgram);
//之前定义的着色器对象就可以删除了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
/*
int vertexColorLocation = glGetUniformLocation(shaderProgram,"ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);*/
//缓存弄好了,接下来就是将数据弄到缓存上去了
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 // 顶部
};
//定义一个顶点缓存对象
//GPU上有一个特定的缓冲ID,通过引用申请一个VBO对象
unsigned int VBO;
glGenBuffers(1, &VBO);
//下一步就是把缓冲对象绑定到对应的缓存中去,这里我们绑定的缓存类型为GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//定义一个顶点数组对象
//该对象也可以被绑定,任何随后的顶点属性都会被绑定在这个VAO中
unsigned int VAO;
glGenVertexArrays(1, &VAO);
//下一步也就是把这个数组对象绑定到缓存上去
glBindVertexArray(VAO);
//GL_STATIC_DRAW :数据不会或几乎不会改变。
//GL_DYNAMIC_DRAW:数据会被改变很多。
//GL_STREAM_DRAW :数据每次绘制时都会改变。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //绑定数据到缓存
//由于顶点着色器的输入很灵活,我们必须定义输入的值对应的顶点属性
//glVertexAttribPointer()函数定义了Opengl中该如何分析我们输入的顶点数据
//p1表示位置值,p2表示顶点属性的大小vec3对应的就是3,p3对应我们输入的数据类型,p4对应我们是否要标准化
//也就是对应是否要将数据映射到-1到1的空间中去,p5表示我们读取一个顶点属性的位移,p6表示数据在缓冲区的位移
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//启用顶点属性函数,参数为对应的顶点属性位置
//添加对RGB顶点属性的读取
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1); //激活
//加入循环
while (!glfwWindowShouldClose(window)) //用于检查window对象是否还在,也就是还没退出渲染
{
//*****************************************L1*************************************//
//在渲染中不断检测用户动作
processInput(window);
//清空缓存并设置一个默认缓存色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//这里是对颜色缓存进行清空,其他还有深度缓等GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT
//*****************************************L1*************************************//
//***************************************L2************************************//
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
//注意,这里定义的图元为一个三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);//用于交换前缓存和后缓存:也就是绘制图像的过程
glfwPollEvents();//用于检测有没有什么触发事件,并更新窗口状态
}
//清空顶点数据
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
//释放资源
glfwTerminate();
//...
return 0;
}
为了方便我们管理着色器,我们可以把着色器对象封装到一个抽象类里面,让它可以从硬盘里面读取着色器,然后编译链接它们,并进行错误检测。
注意,这里的#ifndef #define #endif 是为了防止出现重复引用的问题,含义如下:
#ifndef SHADER_H 如果没有引用 shader.h
#define SHADER_H 那么就引用 shader.h
#endif 否者不引用
如果不这样重复引用后可能会报重定义错误
说明:我们在定义shader的时候,指定了顶点着色器和片段着色器的路径,这样在构造着色器程序的时候我们就可以把源码的文本存储在硬盘上了。另外,定义成员函数 use激活着色器程序对象,set()用于查找着色器位置并赋值。
#ifndef SHADER_H
#define SHADER_H
#include ; // 包含glad来获取所有的必须OpenGL头文件
#include
#include
#include
#include
class Shader
{
public:
// 程序ID
unsigned int ID;
// 构造器读取并构建着色器
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);
// 使用/激活程序
void use();
// uniform工具函数
void setBool(const std::string &name, bool value) const;
void setInt(const std::string &name, int value) const;
void setFloat(const std::string &name, float value) const;
};
#endif
Shader(const char* vertexPath, const char* fragmentPath)
{
// 1. 从文件路径中获取顶点/片段着色器
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// 保证ifstream对象可以抛出异常:
vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
try
{
// 打开文件
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// 读取文件的缓冲内容到数据流中
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 关闭文件处理器
vShaderFile.close();
fShaderFile.close();
// 转换数据流到string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.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. 编译着色器
unsigned int vertex, fragment;
int success;
char infoLog[512];
// 顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// 打印编译错误(如果有的话)
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
};
// 片段着色器也类似
[...]
// 着色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
// 打印连接错误(如果有的话)
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if(!success)
{
glGetProgramInfoLog(ID, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
glDeleteShader(vertex);
glDeleteShader(fragment);
void use()
{
glUseProgram(ID);
}
//
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);
}
//构造一个着色器程序
Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
...
while(...)
{
//激活并对uniform赋值
ourShader.use();
ourShader.setFloat("someUniform", 1.0f);
//绘制图元
DrawStuff();
}