LearnOpenGL学习笔记:着色器

( 本文对应学习章节:https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/ )

0.前言

在之前绘制三角形的章节中已经初步了解了着色器和着色器语言。在本章节中,教程进一步讲解着色器和着色器语言GLSL,及相关的数据类型,参数传递等。在最后,封装了一个简易的着色器类,简化主体流程。

1.了解着色器与着色器语言

着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。

着色器是使用一种叫GLSL的类C语言写成的,典型的着色器代码结构如下:

#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;
}

数据类型

GLSL中包含常见的基础数据类型:int、float、double、uint和bool。也有两种容器类型:向量(Vector)和矩阵(Matrix)。

向量是一个可以包含至多4个分量的容器,可以看作基本类型的复合形式:vec3就是3个分量的float向量(首字母不带类型默认是float),bvec2表示2个分量的bool向量,ivec4表示4个分量的int向量。

对于向量中的分量,可以通过.xyzw或是.rgba或是.stpq来访问,而且可以灵活的重组和构造:

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);

输入与输出

GLSL定义了in和out关键字,每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。但在顶点和片段着色器中会有点不同。

顶点着色器从顶点数据中直接接收输入。为了指定输入变量,需要为它的输入提供一个额外的layout标识,这样我们才能把它链接到顶点数据。而layout的location偏移是通过glVertexAttribPointer函数的第一个参数指定的:

glVertexAttribPointer定义通用顶点属性数据的数组:

void glVertexAttribPointer(GLuint index​, GLint size​, GLenum type​, GLboolean normalized​, GLsizei stride​, const GLvoid * pointer​);

参数1指定顶点属性的索引;参数2指定顶点属性的分量数,可以是[1-4];参数3指定数据类型;参数4设置是否归一化;参数5指定连续的顶点属性间的字节偏移;参数6指定第一个值的偏移量。可以对照上面的代码示例。

另一个例外是片段着色器,它需要一个vec4颜色输出变量 ,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。

Uniform

Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。

假设我们在片段着色器声明了一个uniform的颜色向量:

uniform vec4 ourColor;

 我们可以通过着色器程序对象来动态设置这个颜色:

int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);

glGetUniformLocation找到uniform变量的位置, glUniform指定当前程序对象的uniform变量的值。glGetUniformLocation返回-1就表示没有找到这个变量。glUniform函数名后面有分量数及类型,如glUniform4f就表示需要4个float作为它的值。

2.封装一个简易的着色器类

代码Github地址:https://github.com/gongjianbo/LearnTheOpenGL

整体比较简单,就是把着色器的初始化流程封装了下,以及对uniform变量操作的封装。教程是读取文件来初始化类的,我为了方便直接使用的字符串。

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

#include <iostream>
#include <string>

class MyShader
{
public:
	//根据着色器代码来初始化
	MyShader(const std::string& vertexCode, const std::string& fragmentCode)
	{
		init(vertexCode, fragmentCode);
	}
	//激活程序
	void useProgram()
	{
		glUseProgram(programID);
	}
	// uniform工具函数
	void setInt(const std::string& name, int value)
	{
		glUniform1i(glGetUniformLocation(programID, name.c_str()), value);
	}
	void setFloat(const std::string& name, float value)
	{
		glUniform1f(glGetUniformLocation(programID, name.c_str()), value);
	}

private:
	void init(const std::string& vertexCode, const std::string& fragmentCode)
	{
		const char* vertex_shader_code = vertexCode.c_str();
		const char* fragment_shader_code = fragmentCode.c_str();

		//顶点着色器
		unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
		glShaderSource(vertex_shader, 1, &vertex_shader_code, NULL);
		glCompileShader(vertex_shader);
		checkError(vertex_shader, "vertex");

		//片段着色器
		unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
		glShaderSource(fragment_shader, 1, &fragment_shader_code, NULL);
		glCompileShader(fragment_shader);
		checkError(fragment_shader,"fragment");

		//着色器程序
		programID = glCreateProgram();
		glAttachShader(programID, vertex_shader);
		glAttachShader(programID, fragment_shader);
		glLinkProgram(programID);
		checkError(programID, "program");

		// 删除着色器,它们已经链接到我们的程序中了
		glDeleteShader(vertex_shader);
		glDeleteShader(fragment_shader);
	}

	void checkError(unsigned int name,const std::string &type)
	{
		int check_flag; 
		char check_info[1024];
		if (type != "program") {
			glGetShaderiv(name, GL_COMPILE_STATUS, &check_flag);
			if (!check_flag) {
				glGetShaderInfoLog(name, 1024, NULL, check_info);
				std::cout << type << " error:" << check_info << std::endl;
			}
		}
		else {
			glGetShaderiv(name, GL_LINK_STATUS, &check_flag);
			if (!check_flag) {
				glGetProgramInfoLog(name, 1024, NULL, check_info);
				std::cout << type << " error:" << check_info << std::endl;
			}
		}
	}

private:
	unsigned int programID;
};

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);


const std::string vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

uniform float aSize;

out vec3 ourColor;

void main()
{
	gl_Position = vec4(aPos*aSize, 1.0);
	ourColor = aColor;
}
)";

//只有一个分量就不是vec2了,直接写float
const std::string fragmentShaderSource = R"(
#version 330 core
in vec3 ourColor;

out vec4 outColor;

void main()
{
	outColor = vec4(ourColor, 1.0);
}
)";

int main()
{
	// glfw: initialize and configure
	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

	// glfw window creation
	GLFWwindow* window = glfwCreateWindow(800, 600, "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: load all OpenGL function pointers
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		glfwTerminate();
		return -1;
	}

	int nrAttributes;
	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
	std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

	// build and compile our shader program
	MyShader a_shader(vertexShaderSource,fragmentShaderSource);

	// 顶点数据
	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);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);

	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
	//glEnableVertexAttribArray(1); //卸载glVertexAttribPointer前后都可以
	

	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	//渲染循环
	while (!glfwWindowShouldClose(window))
	{
		processInput(window);

		// render
		glClear(GL_COLOR_BUFFER_BIT);

		a_shader.useProgram();
		glBindVertexArray(VAO);
		float timeValue = (float)glfwGetTime();
		float sizeValue = sin(timeValue) / 2.0f + 0.5f;
		a_shader.setFloat("aSize", sizeValue);

		// render the triangle
		glDrawArrays(GL_TRIANGLES, 0, 3);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);

	glfwTerminate();
	return 0;
}

void processInput(GLFWwindow* window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}
发布了95 篇原创文章 · 获赞 26 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/gongjianbo1992/article/details/103006963