1. uniform
#include <glad/glad.h>
#include <GLFW/glfw3.h> // 注释顺序,GLAD的头文件包含了正确的OpenGL头文件,所以需要在其它依赖于OpenGL的头文件之前包含GLAD
#include <iostream>
#include <cmath>
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
// 顶点shader代码,GLSL
const char* vertexShaderSource = "#version 330 core\n" // 版本声明,使用核心模式
"layout (location = 0) in vec3 aPos;\n" // 关键字in,声明所有的输入顶点属性(Input Vertex Attribute)
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" // 预定义变量 gl_Position,作为顶点着色器输出
"}\0";
// 片段shader代码,GLSL
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n" // 关键字out,声明输出变量
"uniform vec4 ourColor;\n" // 关键字uniform,可以理解为全局变量
"void main()\n"
"{\n"
" FragColor = ourColor;\n"
"}\n\0";
int main()
{
// 初始化GLFW,配置主版本号,次版本号,核心模式
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// 创建窗口对象(宽,高,名称)
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
// 将窗口上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
// 注册函数,每当窗口调整大小时调用此函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// GLAD管理OpenGL函数指针,故在调用任何OpenGL的函数前,先要初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
glfwTerminate();
return -1;
}
/ 编译构建shader代码
// 顶点shader
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); // 创建顶点着色器对象
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); // 附着源码到顶点着色器对象
glCompileShader(vertexShader); // 编译
// 检查编译是否成功
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); // 检查是否编译失败
if (!success) {
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 片段shader
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// 检查编译是否成功
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 着色器程序,链接顶点shader和片段shader
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// 检查链接是否成功
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 着色器对象链接到程序对象以后,就可以删除着色器对象了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 设置顶点数据,配置顶点属性
float vertices[] = {
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
0.0f, 0.5f, 0.0f // top
};
// 初始化顶点缓冲对象,顶点数组对象
unsigned int VBO, VAO;
// 生成
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 先绑定顶点数组对象,再绑定顶点缓冲对象(到顶点数组对象)
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 拷贝数据到顶点缓冲对象(在顶点数组对象中)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 配置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
// 启用顶点属性
glEnableVertexAttribArray(0);
// 解绑,防止其他VAO调用时意外修改此VAO
glBindVertexArray(0);
// 前面实际已经绑定过了。放在这里只是为了说明,在渲染前绑定需要绑定。由于只有一个VAO,就不放在渲染循环中重复绑定解绑了。
glBindVertexArray(VAO);
// 渲染循环
while (!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// 渲染指令
// ...
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置清空屏幕使用的颜色
glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓冲
// 激活shader,uniform的设置必须在shader程序起来后
glUseProgram(shaderProgram);
// 更新uniform数值
double timeValue = glfwGetTime();
float greenValue = static_cast<float>(sin(timeValue) / 2.0 + 0.5);
// 获取uniform在现存上的位置
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
// 不同类型unifomr使用的设置方法也不一样,这里是float类型
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
// 绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
// 交换颜色缓冲
glfwSwapBuffers(window);
// 检查事件触发
glfwPollEvents();
}
// 释放所有资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
// 释放分配的资源
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// 视口,告诉OpenGL渲染窗口的大小(前两个参数为窗口左下角位置,后两个为渲染窗口的宽高)
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
2.更多的顶点属性
每个顶点数据增加一个颜色属性,顶点着色器输出颜色,传递给片段着色器作为最终颜色。注意输出输入要严格匹配。
// 顶点shader代码,GLSL
const char* vertexShaderSource = "#version 330 core\n" // 版本声明,使用核心模式
"layout (location = 0) in vec3 aPos;\n" // 位置变量的属性位置值为 0
"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";
// 片段shader代码,GLSL
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n" // 关键字out,声明输出变量
"in vec3 ourColor;\n" // 取顶点着色器的输出作为片段着色器的输入
"void main()\n"
"{\n"
" FragColor = vec4(ourColor, 1.0);\n"
"}\n\0";
// ...
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 // 顶部
};
配置顶点属性并启用
// ...
// 配置顶点属性:位置,并启用
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); // 步长变为为6个float
glEnableVertexAttribArray(0);
// 配置顶点属性:颜色,并启用
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); // 单步长内偏移量为3个float
glEnableVertexAttribArray(1);
// ...
3. 着色器类
将着色器相关操作提取出来封装一个类,顶点shader和片段shader代码单独另写一个文件。使用时从文件中读取代码并编译,链接。最终结构如下:
shader_s.h:封装着色器操作
#ifndef SHADER_H
#define SHADER_H
#include <glad/glad.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
class Shader {
public:
// 程序ID
unsigned int ID;
// 构造器
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);
checkCompileErrors(vertex, "VERTEX");
// 片段着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// 着色器程序,链接
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// 删除着色器,它们已经链接到我们的程序中了,已经不再需要了
glDeleteShader(vertex);
glDeleteShader(fragment);
}
// 激活使用
void use()
{
glUseProgram(ID);
}
// uniform工具函数
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);
}
private:
// 检查着色器编译链接是否正常
void checkCompileErrors(unsigned int shader, std::string type)
{
int success;
char 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
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.x, aPos.y, aPos.z, 1.0);
ourColor = aColor;
}
shader.fs:片段着色器代码
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0);
}
主文件:
#include <glad/glad.h>
#include <GLFW/glfw3.h> // 注释顺序,GLAD的头文件包含了正确的OpenGL头文件,所以需要在其它依赖于OpenGL的头文件之前包含GLAD
#include <iostream>
#include <cmath>
#include "Shader_s.h"
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
int main()
{
// 初始化GLFW,配置主版本号,次版本号,核心模式
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// 创建窗口对象(宽,高,名称)
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
// 将窗口上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
// 注册函数,每当窗口调整大小时调用此函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// GLAD管理OpenGL函数指针,故在调用任何OpenGL的函数前,先要初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
glfwTerminate();
return -1;
}
/ 编译构建shader代码
Shader ourShader("shader.vs", "shader.fs");
// 设置顶点数据,配置顶点属性
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 // 顶部
};
// 初始化顶点缓冲对象,顶点数组对象
unsigned int VBO, VAO;
// 生成
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 先绑定顶点数组对象,再绑定顶点缓冲对象(到顶点数组对象)
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 拷贝数据到顶点缓冲对象(在顶点数组对象中)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 配置顶点属性:位置,并启用
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); // 步长变为为6个float
glEnableVertexAttribArray(0);
// 配置顶点属性:颜色,并启用
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); // 单步长内偏移量为3个float
glEnableVertexAttribArray(1);
// 渲染循环
while (!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// 渲染指令
// ...
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置清空屏幕使用的颜色
glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓冲
ourShader.use();
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 交换颜色缓冲
glfwSwapBuffers(window);
// 检查事件触发
glfwPollEvents();
}
// 释放所有资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// 释放分配的资源
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// 视口,告诉OpenGL渲染窗口的大小(前两个参数为窗口左下角位置,后两个为渲染窗口的宽高)
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
4.练习
1.修改顶点着色器让三角形上下颠倒
y置负即可
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
void main()
{
gl_Position = vec4(aPos.x, -aPos.y, aPos.z, 1.0);
ourColor = aColor;
}
2.使用uniform定义一个水平偏移量,在顶点着色器中使用这个偏移量把三角形移动到屏幕右侧
// 渲染文件中
ourShader.use();
ourShader.setFloat("xoffset", 0.5f); // 设置偏移量
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 顶点shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor;
uniform float xoffset; // 偏移量
void main()
{
gl_Position = vec4(aPos.x + xoffset, aPos.y, aPos.z, 1.0);
ourColor = aColor;
}
3.使用out
关键字把顶点位置输出到片段着色器,并将片段的颜色设置为与顶点位置相等(来看看连顶点位置值都在三角形中被插值的结果)。做完这些后,尝试回答下面的问题:为什么在三角形的左下角是黑的?
// 顶点shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec4 ourColor;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
ourColor = gl_Position;
}
// 片段shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec4 ourColor;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
ourColor = gl_Position;
}
// 渲染文件中
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 // 顶部
};
// 绘制
ourShader.use();
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
可以看到,左下角的坐标为(-0.5f, -0.5f, 0.0f),对应到rgb,必须为正数,就会取值为(0.0f,0.0f, 0.0f),也就是黑色。