【OpenGL】LearnOpenGL学习笔记03 - 着色器

上接: https://blog.csdn.net/weixin_44506615/article/details/149864439?spm=1001.2014.3001.5501

完整代码: https://gitee.com/Duo1J/learn-open-gl

一、着色器

在之前的三角形和矩形的绘制中,我们用到了顶点和片段着色器,接下来我们再稍微深入的了解一下着色器
我们之前写的顶点和片段着色器

cpp 复制代码
// 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

// 片段着色器
#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 

1. 数据类型

GLSL包含的基本数据类型有

cpp 复制代码
int、uint、float、double、bool

除此之外,GLSL还包含两种容器类型: 向量矩阵

现在先了解一下向量,矩阵等之后用到再聊

类型 含义
vecn 包含n个float分量的默认向量
bvecn 包含n个bool分量的向量
ivecn 包含n个int分量的向量
uvecn 包含n个unsigned int分量的向量
dvecn 包含n个double分量的向量

你可以像下面这样访问向量的分量

cpp 复制代码
vec4 v;
vec3 v2 = v.xyz;
vec3 v3 = v.xxx;
vec4 v4 = v.xyzw + v.zwxy;
vec2 v5 = v.yz;

2. 输入和输出

GLSL定义了inout关键字来表示这个变量是否是输入或是输出变量

我们之前还用到了layout (location = 0)来将我们声明的变量链接到顶点数据中 (glVertexAttribPointer的第一个参数设置了顶点的location)

cpp 复制代码
// 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
//定义输出变量
out vec4 vertexColor;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    // 输出变量vectexColor设置为暗红色
   	vertexColor = vec4(0.5, 0.0, 0.0, 1.0);
}

// 片段着色器
#version 330 core
out vec4 FragColor;
// 顶点着色器传来的输入变量,需要名称相同、类型相同
in vec4 vertexColor;

void main()
{
    FragColor = vertexColor;
} 

3. uniform

如果我们想要从CPU传递数据到GPU上,我们可以使用uniform 关键字

unform是全局变量,意味着在每个着色器程序中都是独一无二的,同时uniform在被赋值之后它会一直保存他们的数据,直到被重置或是更新

cpp 复制代码
// 片段着色器
#version 330 core
out vec4 FragColor;
// 定义uniform变量
uniform vec4 ourColor;

void main()
{
    FragColor = ourColor;
}
cpp 复制代码
// 获取时间值
float timeValue = glfwGetTime();
// 通过sin函数计算随时间变化的g值
float greenValue = sin(timeValue) / 2.0f + 0.5f;
// 获取我们声明的uniform变量的位置
int ourUniformColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
// 设置uniform变量
glUniform4f(ourUniformColorLocation, 0, greenValue, 0, 1);

编译运行,顺利的话我们可以看见我们绘制的图形在一直变换颜色
4. 传入更多属性

前面提到,我们可以通过location = n来将我们声明的变量链接到顶点数据中,这意味着我们可以传入更多不同类型的顶点数据
修改一下vertices

cpp 复制代码
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    // 顶部
};

接下来调整一下我们的着色器

cpp 复制代码
// 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
// 从location = 1 传入颜色值
layout (location = 1) in vec3 aColor;

out vec3 vertexColor;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    vertexColor = aColor;
}

// 片段着色器
#version 330 core
out vec4 FragColor;
in vec3 vertexColor;

void main()
{
    FragColor = vec4(vertexColor, 1.0);
}

最后再修改一下我们的顶点属性

对于颜色属性,我们需要注意第一位参数会变为1,以此来链接到location = 1的变量,同时最后一位参数需要设置为3 * sizeof(float)的偏移量

由于多了3个float的颜色值,所以步长将变为6 * sizeof(float)

cpp 复制代码
// 位置属性
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);

编译运行,顺利的话可以看见一个彩色的三角形

这里虽然我们只提供了三个顶点处的颜色值,但片段着色器会进行片段插值,其会根据每个片段在三角形形状上所处的相对位置来决定这些片段的颜色

二、着色器类

接下来我们封装一下我们的着色器类,并且将着色器抽离成单独文件,以便后续使用

直接上代码
Shader.h

cpp 复制代码
#pragma once

#include <glad/glad.h>;

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

/**
* 着色器
*/
class Shader
{
public:
	/**
	* 着色器程序ID
	*/
	unsigned int ProgramID;

	/**
	* @param vertexPath: 顶点着色器路径
	* @param fragmentPath: 片段着色器路径
	*/
	Shader(const char* vertexPath, const char* fragmentPath);

	/**
	* 使用这个着色器程序
	*/
	void Use();

	/**
	* 销毁这个着色器程序
	*/
	void Delete();

	/**
	* 设置bool类型uniform值
	*/
	void SetBool(const std::string& name, bool value) const;

	/**
	* 设置int类型uniform值
	*/
	void SetInt(const std::string& name, int value) const;

	/**
	* 设置float类型uniform值
	*/
	void SetFloat(const std::string& name, float value) const;
};

Shader.cpp

cpp 复制代码
#include "Shader.h"

Shader::Shader(const char* vertexPath, const char* fragmentPath)
{
	std::string vertexCode;
	std::string fragmentCode;
	std::ifstream vertexShaderFile;
	std::ifstream fragmentShaderFile;
	vertexShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	fragmentShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);

	try
	{
		// 打开着色器代码文件
		vertexShaderFile.open(vertexPath);
		fragmentShaderFile.open(fragmentPath);
		std::stringstream vShaderStream, fShaderStream;
		// 读取文件到字符串流中
		vShaderStream << vertexShaderFile.rdbuf();
		fShaderStream << fragmentShaderFile.rdbuf();
		// 关闭文件流
		vertexShaderFile.close();
		fragmentShaderFile.close();
		// 流转换为字符串
		vertexCode = vShaderStream.str();
		fragmentCode = fShaderStream.str();
	}
	catch (std::ifstream::failure e)
	{
		std::cout << "[Error] Failed to read shader file" << std::endl;
	}

	// 转为c-style字符串
	const char* vertexShaderCode = vertexCode.c_str();
	const char* fragmentShaderCode = fragmentCode.c_str();

	// 创建、赋值、编译、链接着色器
	unsigned int vertexShader, fragmentShader;
	int success;
	char infoLog[512];

	// 顶点着色器
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderCode, NULL);
	glCompileShader(vertexShader);
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "[Error] Vertex shader compile failed!\n" << infoLog << std::endl;
	};

	// 片段着色器
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderCode, NULL);
	glCompileShader(fragmentShader);
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		std::cout << "[Error] Fragment shader compile failed!\n" << infoLog << std::endl;
	}

	ProgramID = glCreateProgram();
	glAttachShader(ProgramID, vertexShader);
	glAttachShader(ProgramID, fragmentShader);
	glLinkProgram(ProgramID);
	glGetProgramiv(ProgramID, GL_LINK_STATUS, &success);
	if (!success)
	{
		glGetProgramInfoLog(ProgramID, 512, NULL, infoLog);
		std::cout << "[Error] Shader program link failed!\n" << infoLog << std::endl;
	}

	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);
}

void Shader::Use()
{
	glUseProgram(ProgramID);
}

void Shader::Delete()
{
	glDeleteProgram(ProgramID);
}

void Shader::SetBool(const std::string& name, bool value) const
{
	glUniform1i(glGetUniformLocation(ProgramID, name.c_str()), (int)value);
}

void Shader::SetInt(const std::string& name, int value) const
{
	glUniform1i(glGetUniformLocation(ProgramID, name.c_str()), value);
}

void Shader::SetFloat(const std::string& name, float value) const
{
	glUniform1f(glGetUniformLocation(ProgramID, name.c_str()), value);
}

VertexShader.vs

cpp 复制代码
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

out vec3 vertexColor;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    vertexColor = aColor;
}

FragmentShader.fs

cpp 复制代码
#version 330 core
out vec4 FragColor;
in vec3 vertexColor;

void main()
{
    FragColor = vec4(vertexColor, 1.0);
}

Main.cpp

cpp 复制代码
#include <iostream>

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include "Shader.h"

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 indices[] = {
	0, 1, 3, // 第一个三角形
	1, 2, 3  // 第二个三角形
};

void ProcessInput(GLFWwindow* window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE))
	{
		glfwSetWindowShouldClose(window, true);
	}
}

void OnSetFrameBufferSize(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}

int main()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	GLFWwindow* window = glfwCreateWindow(1280, 720, "OpenGLRenderer", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	glfwSetFramebufferSizeCallback(window, OnSetFrameBufferSize);

	unsigned int VAO;
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	unsigned int VBO;
	glGenBuffers(1, &VBO);
	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);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);

	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

	// 替换为Shader类
	Shader shader("VertexShader.vs", "FragmentShader.fs");

	while (!glfwWindowShouldClose(window))
	{
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		ProcessInput(window);

		// 使用着色器程序
		shader.Use();
		glBindVertexArray(VAO);

		glDrawArrays(GL_TRIANGLES, 0, 3);
		//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// 销毁着色器程序
	shader.Delete();
	glfwTerminate();
	return 0;
}

编译运行,顺利的话可以看见之前绘制的彩色三角形