Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)

好的,又是因为要面试,这个面试需要复习opengl、图像算法、机器学习、算法题。所以会写相应的系列文章。。。

opengl从入门到菜鸡工程师之路

  • 基础架构介绍
  • 顶点着色器、片段着色器
    • 顶点输入
    • 顶点着色器
    • 片段着色器
    • 把上面过程串起来
    • 数据输入
    • 纹理的使用

基础架构介绍

首先opengl是一个做显示的的东西。首先对他的一个基本流程有一个初步的概念。
常见的工作流程:

  1. 创建一个对象,用一个id保存他的应用
  2. 将对象绑定至上下文的目标位置(比如窗口对象)
  3. 设置绑定的一些选项
  4. 进行一系列的操作
  5. 将目标位置对象id设置回0,解绑这个对象

opengl的搭建可以借助vs2017,在nuget安装nupengl
安装之后可以试一下跑一个最简单的函数

//初始化 glut
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(SCR_WIDTH, 70);
	glutInitWindowSize(SCR_WIDTH, SCR_HEIGHT);
	glutCreateWindow("test");
	// 初始化 glew(功能与glad类似)
	glewInit();

	// 程序中需要的初始化,比如加载shader、加载文字、加载素材等
	init();
	glutTimerFunc(20, timer, 0);
	// 设置主函数
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboard);
	glutMouseFunc(MouseEvent);
	glutMotionFunc(MotionMove);
	// 主函数循环
	glutMainLoop();

顶点着色器、片段着色器

我们能看到的三维效果其实也是一系列的变化后的二维图片,这个过程就是opengl图像渲染管线要干的事。其实随着技术的迭代跟新,已经由传统的顶点着色器和片段着色器,衍生了很多别的部分,如下图所示
Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)_第1张图片
针对顶点着色器:

  1. 顶点做色器主要作用是指定形状
  2. 以数组(顶点数据)的形式传递3个3D坐标作为这个过程的输入
  3. 顶点数据含有顶点属性,最简单的顶点属性是颜色,初此之外还有法向量等
  4. 为了让opengl知道你的这个顶点需要构成什么,需要制定一个模式,常见的有GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP。如果是point,那么opengl会一个个读数组数据,如果是triangle则三个三个的读数组数据。即使三角形,也分为下面几种的绘制方式。这主要影响数组中数据排列方式。
    Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)_第2张图片
    针对片段着色器:
    1.片段着色器主要指定颜色、光照、阴影等光影效果。

顶点输入

顶点包括x、y和z方向,需要注意,要将数据归一化到 [-1,1] 这个区间。
比如这是一组顶点数据

float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};

绘制出来的的三角形如下
Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)_第3张图片
在高级一点的版本里有顶点缓冲对象(VBO),它主要做缓存处理,如果不使用,顶点会一个个发送到显卡内存区,比较占CPU。

顶点着色器

下面是一个三角形的顶点着色器

#version 330 core
// 输入数据 aPos输入是一个(x,y)的向量
// aColor是一个4维向量
attribute vec2 aPos;
attribute vec4 aColor;
// 输出至片段着色器的数据
out vec4 Color;
void main()
{
	
	// color在顶点着色器里不用,直接输出
	Color=aColor;
	// gl_Position是内置函数,这里是4个参数,x,y,z还有alpha值,暂时alpha值为1
    gl_Position = vec4(aPos, 0.0, 1.0);
}

片段着色器

下面是一个三角形的顶点着色器

#version 330 core
// 从顶点着色器的输入
in vec4 Color;

void main()
{    
	// gl_FragColor 是内置函数,这里是4个参数
    gl_FragColor = Color;
}

把上面过程串起来

最简单的串接步骤如下所示:

  1. 分别读Shader内容(readShaderSource函数)
  2. 分别编译为顶点着色器、片段着色器(genShader函数)
  3. 把同一组的顶点着色器、片段着色器连接起来(linkProgram函数)

因为这个步骤比较程式化,所以写好一个shader读取函数基本就是通用变了。

我这里贴出我自己的一个框架:

static char* readShaderSource(const char *fileName)
{
	FILE *fp;
	char *content = NULL;
	int count = 0;

	if (fileName != NULL)
	{
		fp = fopen(fileName, "rt");

		if (fp != NULL)
		{
			fseek(fp, 0, SEEK_END);
			count = ftell(fp);
			rewind(fp);
			if (count > 0)
			{
				content = (char *)malloc(sizeof(char) * (count + 1));
				count = fread(content, sizeof(char), count, fp);
				content[count] = NULL;
			}
			fclose(fp);
		}
	}
	return content;
}

static GLuint genShader(GLenum type, const char* filename, char*& log)
{
	GLuint shader = glCreateShader(type);
	char* shaderSource = readShaderSource(filename);
	if (!shaderSource)
	{
		printf("Error:\tCan not read shader file--%s ,please confirm the file name is right!\n",filename);
		pause();
	}
	const char* ptrShaderSource = shaderSource;

	glShaderSource(shader, 1, &ptrShaderSource, NULL);
	free(shaderSource);
	glCompileShader(shader);
	GLint status = 0;
	//查看编译状态
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
	if (!status)
	{
		GLint length;
		//读取日志信息的长度
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
		log = (GLchar*)malloc(length);
		//读取日志信息
		glGetShaderInfoLog(shader, length, &length, log);
#if 1
		printf("Error in generating shaders!\n");
		printf("\tError:%s\n", log);
#endif
		//删除着色器对象
		glDeleteShader(shader);
		pause();
	}
	return shader;
}

static GLuint linkProgram(GLuint* shader, int shaderNum, char*& log)
{
	//创建着色器程序
	GLuint program = glCreateProgram();
	int i;
	//往着色器程序中加入着色器对象
	for (i = 0; i < shaderNum; i++)
		glAttachShader(program, shader[i]);
	//链接着色器程序
	glLinkProgram(program);
	GLint status;
	//查看链接状态
	glGetProgramiv(program, GL_LINK_STATUS, &status);
	if (!status)
	{
		GLint length;
		//读取日志信息的长度
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
		log = (GLchar*)malloc(length);
		//读取日志信息
		glGetProgramInfoLog(program, length, &length, log);
#if 1
		printf("Error in linking shaders!\n");
		printf("\tError:%s\n", log);
#endif
		//删除着色器对象
		glDeleteProgram(program);
		pause();
	}
	return program;
}

static GLuint Shader(const char *vsfileName, const char *fsfileName, const char* geometryPath = nullptr)
{
	char* log = NULL;
	GLuint tvertexShader = genShader(GL_VERTEX_SHADER, vsfileName, log);
	if (!tvertexShader)
	{
		free(log);
		return false;
	}
	GLuint tfragmentShader = genShader(GL_FRAGMENT_SHADER, fsfileName, log);
	if (!tfragmentShader)
	{
		free(log);
		return false;
	}
	GLuint tgeometryShader;
	if (geometryPath != nullptr)
	{
		tgeometryShader = genShader(GL_GEOMETRY_SHADER, geometryPath, log);
		if (!tgeometryShader)
		{
			free(log);
			return false;
		}
	}
	GLuint program;
	if (geometryPath == nullptr)
	{
		GLuint shader[2] = { tvertexShader, tfragmentShader };
		program = linkProgram(shader, 2, log);
	}
	else if (geometryPath != nullptr)
	{
		GLuint shader[3] = { tvertexShader, tfragmentShader, tgeometryShader };
		program = linkProgram(shader, 3, log);
	}
	if (!program)
	{
		glDeleteShader(tvertexShader);
		glDeleteShader(tfragmentShader);
		free(log);
		return false;
	}
	return program;
}

void LoadShader(int id)
{
	shader_id = Shader("shader/prog_2D_color.vs", "shader/prog_2D_color.fs");
	Prgm_2D_color_ATTR_aPos = glGetAttribLocation(shader_id,  "aPos");
	Prgm_2D_color_ATTR_aCol = glGetAttribLocation(shader_id, "aColor");		
}

数据输入

上面的这个我没借助VAO,用的 glVertexAttribPointer ,和上面的glGetAttribLocation对应起来用。

glVertexAttribPointer(Prgm_3D_road_ATTR_aPos, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), road_vertice);

在通过下面的代码绘制

// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();

如果使用VAO,其实更像是一个指针管理。如下图所示
Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)_第4张图片
这部分我没使用,完整代码opengl绘制矩形、opengl绘制三角形

纹理的使用

前面我们能绘制矩形,我们希望这个矩形不只是有颜色,还有纹理。
效果如下图所示
Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)_第5张图片
首先这个纹理需要制定对应坐标。
比如上图对应的坐标为

float texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 上中
};

接着要指定环绕方式

  • GL_REPEAT 对纹理的默认行为。重复纹理图像。

  • GL_MIRRORED_REPEAT 和GL_REPEAT一样,但每次重复图片是镜像放置的。

  • GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。

  • GL_CLAMP_TO_BORDER 超出的坐标为用户指定的边缘颜色。

具体效果如下图所示
Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)_第6张图片
注意选择合理的效果!!!!我就曾经在这踩过坑,建议用最后一个啊
对应的代码

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
//我们还需要指定一个边缘的颜色
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

总体而言,加载纹理的代码主要如下(这个也是不怎么需要改的,直接用就好了)

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);

纹理的数据怎么传输?
这里含有顶点数据,纹理数据,颜色(主要是透明度)数据
大概数据结构长这样

float vertices[] = {
// ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 -
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
};

Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)_第7张图片
通过设置读取的步长也就是以下函数,告诉顶点着色器怎么接受这些数据。

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

完整的代码:https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/4.1.textures/textures.cpp
这个代码是绘制一个矩形box
Opengl复习笔记(一)——顶点着色器、片段着色器(内含代码)_第8张图片

你可能感兴趣的:(shader,opengl)