绘制第一个三角形

上一篇文章已经成功的搭建了环境,传送门:环境搭建。并成功的运行了第一个例子——动态改变背景颜色。

本篇要学习怎么在屏幕上绘制点和三角形

首先我们要了解什么是着色器(shader)

顾名思义啊,着色器就是用来着色的工具(这不废话嘛!)

OpenGL是通过以固定函数作为‘胶水’连接多个着色器的小程序来工作的。不管要绘制点什么,我们总要编写一对着色器。

OpenGL的着色器是以OpenGL着色语言(GLSL)进行编写的,该语言源于C。

将着色器的源代码放入到着色对象中并进行编译,然后多个着色对象链接到一起生成一个程序对象。每个程序对象包含一个或多个着色器阶段的多个着色器。

而OpenGL的着色器阶段包括顶点着色器细分曲面控制和评价着色器几何着色器片段着色器计算着色器。然而最最最最基本的管线配置只有一个顶点着色器,如果要显示到屏幕上,还需要一个片段着色器

上面我们提到了三个东西,着色器着色对象程序对象,我理解的他们的关系如下图所示

 按照上面描述的来编写着色器,我们要写两个着色器,一个是顶点着色器,一个是片段着色器,因为要显示到屏幕上,所以必须有片段着色器

1.第一个顶点着色器

//这条语句是声明 想要着色编译器使用着色语言的4.5版本
//core 关键字表示我们只想用OpenGL核心模式所支持的特性
#version 450 core 

void main(void) {
    gl_Position = vec4(0.0, 0.0, 0.5, 1.0);    //gl_Position表示顶点的输出位置
}

 跟普通C函数没有太大区别,不同的是GLSL着色器的main没有参数;函数内给gl_Position进行赋值,gl_Position是连接着色器至OpenGL其余部分的纽带。

所有以gl_开头的变量都是OpenGL的一部分

2.第一个片段着色器

与顶点着色器写法大致相同

#version 450 core

out vec4 color;    //用out关键字声明color作为一个输出变量

void main(void) {
    color = vec4(0.0, 0.2, 0.8, 1.0);
}

这里color用out关键字声明为一个输出变量。在片段着色器中,输出变量会直接发送到屏幕或者窗口中

现在顶点着色器片段着色器都有了,接下来就是程序对象了,需要将它们进行编译并链接到一个程序对象中,由OpenGL去运行。

3.第一个简单的着色器(程序对象)

这里直接上代码了

#include "sb7.h";
#include <cmath>;
GLuint compile_shaders(void) {
	//顶点着色器
	GLuint vertex_shader;
	//片段着色器
	GLuint fragment_shader;
	//程序对象
	GLuint program;
	
	//顶点着色器源代码(具体的顶点着色器)
	static const GLchar* vertex_shader_source[] =
	{
		"#version 450 core							\n"
		"											\n"
		"void main(void)							\n"
		"{											\n"
		"	gl_postion = vec4(0.0, 0.0, 0.5, 1.0);	\n"
		"}											\n"
	};

	//片段着色器源代码(具体的片段着色器)
	static const GLchar* fragment_shader_source[] =
	{
		"#version 450 core							\n"
		"out vec4 color;							\n"
		"void main(void)							\n"
		"{											\n"
		"	color = vec4(0.0, 0.2, 0.8, 1.0);		\n"
		"}											\n"
	};

	//创建和编译顶点着色器
	vertex_shader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertex_shader, 1, vertex_shader_source, NULL);
	glCompileShader(vertex_shader);

	//创建和编译片段着色器
	fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragment_shader, 1, fragment_shader_source, NULL);
	glCompileShader(fragment_shader);

	//创建程序对象,并将着色器附加给它并链接
	program = glCreateProgram();
	glAttachShader(program, vertex_shader);
	glAttachShader(program, fragment_shader);
	glLinkProgram(program);

	//删除着色器,一旦着色器链接到一个程序对象,程序将包含二进制代码,则不需要保留着色器对象
	glDeleteShader(vertex_shader);
	glDeleteShader(fragment_shader);

    //返回程序对象
	return program;
}

流程如代码所示,定义顶点着色器和片段着色器后,通过glCreateShader()方法创建一个着色器对象,使用glShaderSource()将定义的着色器源代码添加到着色器对象中,再调用glCompileShader()编译着色器源代码,创建程序对象glCreateProgram(),将着色器对象附加到其身上glAttachShader(),链接着色器对象glLinkProgram(),最后删除着色器glDeleteShader()。返回该程序对象,保存在某个位置以用来进行绘图。

这里对这几个函数说明一下:

  • glCreateShader()创建一个空的着色器对象,可以随时接收源代码并进行编译
  • glShaderSource()将着色器源代码传递给着色器对象,以便保留该源代码的副本
  • glCompileShader()对着色器对象中包含的任何源代码进行编译
  • glCreateProgram()创建一个程序对象,可将着色器附加到该对象上
  • glAttachShader()将一个着色器对象附加到一个程序对象中
  • glLinkProgram()将所有附加到程序对象的着色器对象链接到一起
  • glDeleteShader()删除一个着色器对象。一旦着色器链接到程序对象,程序将包含二进制代码,将不再需要着色器

4.创建顶点数组对象(VAO)

绘图前最后还需要做一件事情,就是创建顶点数组对象(vertex array object,VAO),表示OpenGL管线顶点获取阶段的对象,用于向顶点着色器提供输入。

创建VAO,需要调用OpenGL函数glCreateVertexArrays(),并调用glBindVertexArray()将函数绑定到我们的上下文中,否则等于没用!(亲测忘了写绑定,研究了半小时为啥没显示!!)

接下来我们在应用程序中进行实现,在sb7::applicationstartup()(类似onEnter生命周期,启动时执行一次)中进行初始化,初始化着色器和创建顶点数组

class my_application : public sb7::application {
public:
	//启动时调用
	void startup() {
		rendering_program = compile_shaders();    //将生成的程序对象保存在rendering_program中
		glCreateVertexArrays(1, &vertex_array_object);	//创建顶点数组对象
		glBindVertexArray(vertex_array_object);		//将顶点数组对象绑定到上下文以便openGL使用
	}
	//关闭时调用
	void shutdown() {
		glDeleteVertexArrays(1, &vertex_array_object);
		glDeleteProgram(rendering_program);
		glDeleteVertexArrays(1, &vertex_array_object);
	}

private:
	GLuint rendering_program;	//渲染的程序对象
	GLuint vertex_array_object;	//顶点对象数组
};

在关闭时会调用shutdown(),处理完程序对象后调用glDeleteProgram(),并删除顶点数组对象glDeleteVertexArrays()

5.在屏幕上绘制一个点

做完上面的事情之后,我们还需要修改render()中的代码,原来我们只是做了窗口颜色的变换,现在要在窗口中绘制一个点,首先调用glUseProgram()来只是OpenGL使用程序对象进行渲染,然后再调用第一个绘图命令glDrawArrays()

    //渲染函数
    void render(double currentTime) {
		const GLfloat color[] = { (float)sin(currentTime) * 0.5f + 0.5f,
								  (float)cos(currentTime) * 0.5f + 0.5f,
								  0.0f, 1.0f };
		//const GLfloat color[] = { 0.0f, 0.2f, 0.5f, 1.0f };
		glClearBufferfv(GL_COLOR, 0, color);

		//使用程序对象进行渲染
		glUseProgram(rendering_program);

		//绘图命令,绘制一个点
		glPointSize(40.0f);    //修改点的大小,最大64像素
		glDrawArrays(GL_POINTS, 0, 1);
	}

这是glDrawArrays的原型

void glDrawArrays(GLenum mode, GLint first, GLsizei count);

从这里不难看出,第一个参数是告诉OpenGL我们要绘制何种类型的图元(基本图元点、线、三角形),第二个参数是从第几个顶点开始绘制(gl_vertexID类似Index),第三个参数是要渲染的顶点个数

运行程序之后,成功的将点显示在窗口中

6.绘制第一个三角形

上面我们已经能把点绘制出来了,接下来就是绘制三角形了,这时候有同学会说,是不是把glDrawArrays中的GL_POINTS更改为GL_TRIANGLES,再把顶点个数改为3就行了啊?说的对但又没有完全对(狗头)

因为我们的顶点着色器现在只有一个顶点,对于点来说,OpenGL会分配绘制区域,但对于线和三角形来说,同一个位置有两个及以上的点,会造成基元退化,即线的长度为零、三角形面积为零。所以我们要修改顶点着色器,改为三个顶点

GLSL的顶点着色器包含一个名为gl_VertexID的特殊输入,它是此时正在被处理的顶点的索引,即glDrawArrays的第二个参数first,从给定的值开始计算,并且每次向上计算一个顶点直到count个顶点

所以我们先来修改一下顶点着色器,这样就有三角形的三个顶点了,而且还是个直角三角形,最后通过gl_VertexID赋值给gl_Position

#version 450 core

void main(void) {
    const vec4 vertices[4] = vec4[4](vec4( 0.25, -0.25, 0.5, 1.0),
									 vec4(-0.25, -0.25, 0.5, 1.0),
									 vec4( 0.25,  0.25, 0.5, 1.0),
	gl_Position = vertices[gl_VertexID];
}

同时修改glDrawArrays函数

glDrawArrays(GL_TRIANGLES, 0, 3);

最后我们来看看效果

达成目标!!!

7.总结

了解了一个openGL程序的构成,应用程序如何向着色器传递数据的,及着色器的编写,程序对象的使用等。最重要的是绘制了一个三角形!!!!有一个就有第二个,后面肯定有更多有趣的图形!!

猜你喜欢

转载自blog.csdn.net/dl15600383645/article/details/127741420