欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenGL---三角形(附带必要基础知识)

程序员文章站 2022-03-25 17:28:07
...

!!!这篇文章的目的是教会你如何用OpenGL画一个三角形!!!

===========================================================================================

注:代码请看注释,重要的都写在注释里了。

基础知识:

一.渲染管线(我之前发表的文章)

     OpenGL---三角形(附带必要基础知识)

二.顶点的顶点属性

  • 顶点位置(position)
  • 顶点UV坐标(UV1~UV7),一共有七组UV坐标可用
  • 顶点法线(normal)
  • 顶点切线(tangent)
  • 顶点颜色(color)
  • 等等。。。

三.OpenGL中VAO与VBO

  • 顶点数组对象:Vertex Array Object,VAO
  • 顶点缓冲对象:Vertex Buffer Object,VBO

       现在我们来解释一下顶点数组对象(VAO)和顶点缓冲对象(VBO)。

       首先解释VBO,顶点缓冲对象其实就是一块缓冲存储的引用。 可以用来开辟一块存储区间来存储顶点的各顶点属性数据。

       VAO,顶点数组对象其实是用来打包各个顶点属性的。

       比如说我有两组点,第一组点的每个点都有各自的顶点位置属性和颜色属性,第二组点的每个点都有各自的位置,顶点法线和顶点切线。如下图。当然这只是举个栗子VBO可以有多个。OpenGL---三角形(附带必要基础知识)

OpenGL---三角形(附带必要基础知识)

   如图就是VAO和VBO的关系,VAO用于保存属性引用,VBO保存属性的具体数据。有了VAO的好处是,当渲染第一组VAO时,我突然需要渲染第二组VAO而不渲染第一组VAO时,只需解除第一组VAO的绑定,告诉OpenGL绑定第二组VAO。如果没有VAO我们需要分别解除第一组的位置和颜色然后将第二组的位置,法线和切线按需求从VBO获值然后渲染,很不方便。

 

 

===========================================================================================

OpenGL画三角形

          说明:用的是glfw和glad(glfw是用来创建窗口和监听键盘或者鼠标等事件的,glad和glew的作用差不多,都是获得函数接口引用,可以理解成glad是glew的升级版,这样我们就可以用OpenGL的API了),具体·请参考OpenGL配置。这里我们跳过配置。

第一步:初始化窗口

#include<glad\glad.h>
#include<GLFW\glfw3.h>
#include<iostream>
using namespace std;
int main()
{
	glfwInit();//初始化
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//配置GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//配置GLFW
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//使用OpenGL核心配置
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", nullptr, nullptr);//设置初始化的窗口长宽,显示名称,屏幕接口,第五个不知道干啥的


	if (window == nullptr)
	{
		cout << "Failed to create GLFW window" << endl;
		glfwTerminate();//terminate the glfw(end the glfw)
		return -1;
	}

	glfwMakeContextCurrent(window);//设置当前openGL上下文


	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))//初始化GLAD
	{
		cout << "Failed to initialize GLAD" << endl;
		return -1;
	}

	while (!glfwWindowShouldClose(window))//渲染
	{

		glClearColor(0.27f, 0.27f, 0.27f, 1.0f);//覆盖颜色
		glClear(GL_COLOR_BUFFER_BIT);//覆盖

		glfwSwapBuffers(window);//双缓冲(替换窗口的每一个像素值)
	}
	glfwTerminate();
	return 0;

}

初始化一个窗口没啥,固定的。

 

第二步:顶点着色器(vertex shader),片元着色器(fragment shader)

  •           顶点着色器(vertex shader)
const GLchar* vertexShader_Code = 
"#version 330 core\n"//使用OpenGL3.3版本(支持可编程渲染管线版本)
"layout(location=0) in vec3 aPos;\n"//输入一个三元向量aPos(顶点位置数据),编号为0(这个数可以自己设,只要跟OpenGL中的传值得编号对应上就行,后面会讲)
"void main()\n"
"{\n"
"gl_Position=vec4(aPos.x,aPos.y,aPos.z,1.0);\n"//变成四元,这样做的目的是方便做变换,比如齐次矩阵乘法变换
"}";

 

  • 片元着色器(fragment shader)
const GLchar* fragmentShader_Code = 
"#version 330 core\n"
"out vec4 FragColor;\n"//以一个颜色值作为输出(像素的颜色)
"void main()\n"
"{\n"
"FragColor=vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"//返回颜色,个分量的值取值范围[0,1],和[0,255]一回事,就是映射范围不一样
"}";

第三步:编译着色器

//data
	int isSuccess;
	char infoLog[512];

	


	//vertexShader data
	unsigned int vertexShader;//声明一个顶点着色器的引用
	vertexShader = glCreateShader(GL_VERTEX_SHADER);//将引用指向一个顶点着色器(相当于面向对象中 new VertexShader())
	glShaderSource(vertexShader, 1,&vertexShader_Code, NULL);//加载之前写的顶点着色器
	glCompileShader(vertexShader);//编译

	/*编译是否通过*/
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		cout << "ERROR::VertexShader::Compile_failed" << infoLog << endl;
	}
	else
	{
		cout << "SUCCESS::VertexShader" << endl;
	}


/*片元着色器编译原理同上*/
	//fragmentShader Data
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShader_Code, NULL);
	glCompileShader(fragmentShader);
	/*编译是否通过*/
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		cout << "ERROR::FragmentShader::Compile_failed" << infoLog << endl;
	}
	else
	{
		cout << "SUCCESS::FragmentShader" << endl;
	}

 

第四步:组装渲染管线

//Shader Pragram
	unsigned int shaderProgram;//声明一个管线的引用(理解成着色器的系统也没问题)
	shaderProgram = glCreateProgram();//引用指向一个管线(相当于面向对象中 new shaderProgram())
	glAttachShader(shaderProgram, vertexShader);//组装顶点着色器
	glAttachShader(shaderProgram, fragmentShader);//组装片元着色器
	glLinkProgram(shaderProgram);//链接各个着色器成渲染管线

/*链接是否成功*/
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog);
		cout << "ERROR::ShaderProgram::Link_failed" << infoLog << endl;
	}
	else
	{
		cout << "SUCCESS::ShaderProgram" << endl;
		glUseProgram(shaderProgram);//使用当前的管线渲染
		glDeleteShader(vertexShader);//着色器已经加载到显卡得内存中了,cpu管理的内存中的着色器没用了,删除
		glDeleteShader(fragmentShader);////着色器已经加载到显卡得内存中了,cpu管理的内存中的着色器没用了,删除
	}

 

第五步:给顶点着色器赋初值

      1.首先是三角形的三个顶点:

//三角顶点
	float vertices[] = 
	{
		-0.5f,-0.5f,0.0f,//左下角
		 0.5f,-0.5f,0.0f,//右下角
		 0.0f, 0.5f,0.0f//顶端角
	};

注意这里的所有顶点分量都是[-1,1],这是归一化坐标,这是应为我们只是写了个最简单的画三角形的方法,跳过了物体空间坐标世界空间坐标视觉空间坐标裁剪空间坐标,直接给归一化坐标空间的坐标。具体请参考渲染管线

      2.赋值

首先先解释什么是绑定

OpenGL中的绑定类似于将当前对象绑定后,之后的所有相关的命令都将作用于此对象。这和常见的面向对象有点区变。

就像一块画板,你绑一张画纸在上面,然后画画。画完了,将画纸解绑拿下来,原先画上的数据保留在了纸上。之后再画别的,再拿一张画纸。

//vectices data
	unsigned int VAO;//声明一个VAO对象
	unsigned int VBO;//声明一个VBO对象

	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);//!!!注意!!!给vertexshader赋初值。
	glEnableVertexAttribArray(0);//**此编号数据

	glBindBuffer(GL_ARRAY_BUFFER,0);//解除VBO绑定
	glBindVertexArray(0);//解除VAO绑定

注意!!!这行代码!!!

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

还记得顶点着色器中那个layout吗?

layout(location=0) in vec3 aPos;

glVertexAttribPointer()

  • 第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了顶点的位置值吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0
  • 第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
  • 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
  • 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
  • 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
  • 最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。

 

第六步:渲染

while (!glfwWindowShouldClose(window))
		{
			glfwPollEvents();//检查有没有触发事件

			glClearColor(0.27f, 0.27f, 0.27f, 1.0f);//覆盖颜色
			glClear(GL_COLOR_BUFFER_BIT);//覆盖


			glUseProgram(shaderProgram);//使用我们之前组装的渲染管线
			glBindVertexArray(VAO);//绑定我们的VAO(就像将之前画完的画重新绑在画板上,给别人看或使用)

			glDrawArrays(GL_TRIANGLES, 0, 3);//以三角模式(面覆盖),画三角形,从第0个开始,画三角形的三个点


			glfwSwapBuffers(window);//双缓冲(替换窗口的每一个像素值)
		}

除了GL_TRIANGLES外还有GL_POINTS和GL_LINE_STRIP。

  • GL_POINT,点模式绘制,请加入下面的语句,要不然太小基本看不清,想要练眼神的小伙伴请忽略!!!。
  • glPointSize(10.0f);//设置点大小
    

    GL_LINE_STRIP,线模式绘制,请加入下面的语句,要不然太细基本看不清,想要练眼神的小伙伴请忽略!!!

  • glLineWidth(10.0f);//设置线宽
    

     

最后:别忘了结束后关闭窗口

glfwTerminate();
return 0;

 

===========================================================================================

全部代码:

#include<glad\glad.h>
#include<GLFW\glfw3.h>
#include<iostream>
using namespace std;

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
	{
		glfwSetWindowShouldClose(window, GL_TRUE);
	}

}

//vertex shader
const GLchar* vertexShader_Code =
"#version 330 core\n"
"layout(location=0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"gl_Position=vec4(aPos.x,aPos.y,aPos.z,1.0);\n"
"}";


const GLchar* fragmentShader_Code =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"FragColor=vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}";

int main()
{
	//三角顶点
	float vertices[] =
	{
		-0.5f,-0.5f,0.0f,//左下角
		0.5f,-0.5f,0.0f,//右下角
		0.0f, 0.5f,0.0f//顶端角
	};
	glfwInit();//初始化
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//配置GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//配置GLFW
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

	GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", nullptr, nullptr);
	glfwSetKeyCallback(window, key_callback);


	if (window == nullptr)
	{
		cout << "Failed to create GLFW window" << endl;
		glfwTerminate();//terminate the glfw(end the glfw)
		return -1;
	}

	glfwMakeContextCurrent(window);//设置当前openGL上下文


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

	//data
	int isSuccess;
	char infoLog[512];




	//vertexShader data
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShader_Code, NULL);
	glCompileShader(vertexShader);
	/*编译是否通过*/
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		cout << "ERROR::VertexShader::Compile_failed" << infoLog << endl;
	}
	else
	{
		cout << "SUCCESS::VertexShader" << endl;
	}

	//fragmentShader Data
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShader_Code, NULL);
	glCompileShader(fragmentShader);
	/*编译是否通过*/
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
		cout << "ERROR::FragmentShader::Compile_failed" << infoLog << endl;
	}
	else
	{
		cout << "SUCCESS::FragmentShader" << endl;
	}

	//Shader Pragram
	unsigned int shaderProgram;
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);

	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &isSuccess);
	if (!isSuccess)
	{
		glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog);
		cout << "ERROR::ShaderProgram::Link_failed" << infoLog << endl;
	}
	else
	{
		cout << "SUCCESS::ShaderProgram" << endl;
		glUseProgram(shaderProgram);
		glDeleteShader(vertexShader);
		glDeleteShader(fragmentShader);
	}

	//vectices data
	unsigned int VAO;
	unsigned int VBO;

	glGenVertexArrays(1, &VAO);//获得一个顶点数组对象
	glBindVertexArray(VAO);
	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, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

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

	while (!glfwWindowShouldClose(window))
	{
		glfwPollEvents();//检查有没有触发事件

		glClearColor(0.27f, 0.27f, 0.27f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);

		glDrawArrays(GL_TRIANGLES, 0, 3);


		glfwSwapBuffers(window);//双缓冲(替换窗口的每一个像素值)
	}
	glfwTerminate();
	return 0;

}

 

运行结果:

OpenGL---三角形(附带必要基础知识)

 

 

至此,三角形就画在了屏幕上!!!可喜可贺!!!祝小伙伴们学习愉快!!!

 

 

 

 

 

 

 

 

 

相关标签: OpenGL