(自用)learnOpenGL(入门)基础总结

本文用于记录总结在学习OpenGL入门部分的思考。

参考的资源有:learnOpenGL,b站傅老师,csdn其他有关文章。

目录

窗口初始化和渲染循环

VAO/VBO/EBO

VertexShader FragmentShader

Texture

坐标系和旋转矩阵

摄像机



总体渲染管线pipeline理解

渲染的整体流程用简单的语言来描述的话,分为两部分:

1.把描述物体的3D坐标转换成2D坐标(这里面有mvp等变化)

2.把2D坐标转换成有颜色的像素打到屏幕上。(也就是确定屏幕上每个像素点的颜色是什么)

tips:2D坐标和像素有区别,2D坐标是精确的坐标位置,但一个像素只能有一个颜色,如果一个像素里面有多个坐标点?


窗口初始化和渲染循环

窗口初始化

    glfwInit();
	//设置主要次要版本
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	//生成一个名字为learnopengl的800x600的窗口
	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);

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

	//设置viewpoint尺寸,这个窗口大小也就是画布的大小,之后的那些映射-1到1会投到0到800.
	glViewport(0, 0, 800, 600);

	//注册窗口大小监听
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

先上代码。我们需要先初始化、然后设置glfw的主次版本和核心模型。然后就是创建window窗口,前两个参数是宽高,第三个是标题。

然后是一个比较关键的代码,glfwMakeContextCurrent(window),因为OpenGL是个状态机,是通过一系列变量来描述OpenGL该如何运行,把window和状态(context)连接,也就是表示,把这个窗口和状态相连。

GLAD是用来管理opengl的函数指针的,在使用函数指针之前需要激活GLAD。

viewport,前两个参数指定视口左下角的位置,后两个表示视口的宽高,可以比前面的窗口大也可以小。

渲染循环

// 渲染循环
while(!glfwWindowShouldClose(window))
{
    // 输入
    processInput(window);

    // 渲染指令
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

    // 检查并调用事件,交换缓冲
    glfwPollEvents();
    glfwSwapBuffers(window);
}

这里需要有一个概念就是:画面不是静止的,而是在不断渲染重复的过程。所以需要while来进行渲染循环。

输入部分通过processInput()来实现,用于检查是否有外部设备的输入,鼠标、键盘的输入等。

渲染指令:这里只是用一种颜色来填充整个屏幕。

检查事件并交换缓冲:glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。这里需要讲一下双缓冲(一个画面是从左到右、从上到下,一个像素一个像素的画出来的。但是速度没有快到可以一瞬间,在人眼没有反应过来之前就画好。所以需要双缓冲。即:在给你看第一帧画面的同时在画第二帧的画面,画好后把第二帧的画面与第一帧的画面交换swap,循环交替)


VAO/VBO/EBO

这里我先摆出一幅图,后面我会依次来解释这幅图中分别是什么。

现在来分开聊聊各部分是什么

顶点坐标:用来描述物体的一组顶点数据(这里面包含顶点坐标、顶点颜色、纹理坐标等)也就是图中的vertex部分。

如同所示,这里面有三个点的数据,依次为坐标xyz、颜色rgb、纹理坐标st(也叫做uv)。

而通过顶点着色器来识别每部分谁是谁,并分配到后面的部分中。

VBO(顶点缓冲对象):存在CPU中的顶点数组回传递到GPU中,并放进VBO中。(通俗来讲,VBO中的数据就是顶点数组,只不过是在GPU中)

VAO(顶点数组对象):VAO是一个索引模块,用于识别VBO中的数据谁是谁。在VAO中有0-15号位置来存VBO中的数据,例如:0号位置描述的是一个兔子,1号位置描述的是一个狗。也就是说!!在最开始传入VBO中的数据有特别特别多,可以想象我把一个世界的所有坐标都传入了VBO,现在我用VAO来分辨出这组数据中,哪些属于狗,哪些属于猫。

EBO(索引缓冲对象):EBO是用来帮助VAO更好的识别VBO的。可以这么理解:我要画一个正方形,但是在OpenGL的世界中,正方形是由三角形组成的。也就是说我要画2个三角形,也就是需要6个点,但是实际情况是我只需要4个点就行了。(换到那个小世界的例子就是,那些数据点我不可能只用一次,是需要重复利用的)所以需要EBO来描述那些点是一组的。

讲到这里,对于顶点数据的处理就讲完了。现在来讲一下在代码中是如何实现的

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

	//VBO
	unsigned int VBO;
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

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

可以看到每个VAO/VBO/EBO都需要创建、绑定,填充数据的。

VAO: 先定义,然后把这个通过Gen这行来生成VAO并把生成的VAO的id传到VAO的这个int中,1表示的生成的VAO个数,后面需要生成多个VAO时改这个参数就行,最后我们还需要把这个生成的VAO塞进pipeline中,通过glBindVertexArray()来实现。

VBO:同样道理,定义,生成、绑定,不过这里的绑定需要注意,使用的时glBindBuffer(),因为是在缓冲区的东西,并要指明绑定在那个位置上GL_ARRAY_BUFFER。之后就是填充数据,将CPU中的数据填充到VBO中,参数分别是(目标缓冲类型、传输数据的大小、需要传输的数据、显卡如何管理数据),最后一项暂时不考虑。

EBO:同理,不过是要绑定在GL_ELEMENT_ARRAY_BUFFER上。

总结一下:整个pipeline就像是个工厂流水线,每个地方的部件是需要先生产然后放到指定的位置才能发挥工作的,也就是说VAO\ABO\EBO是需要定义、生成、绑定、填充。


VertexShader FragmentShader

在前面的部分我们已经在VAOVBOEBO这些东西都做完了,现在需要做的是把pipeline中的顶点着色器和片段着色器设置好。

首先,我们需要有一个概念:顶点着色器片段着色器是相互关联的小程序,他们顶点着色器处理顶点数据然后传递给片段着色器,片段着色器再根据传过来的数据做处理。

所以需要 in out来确定输入输出的数据类型和名称

顶点着色器

//vertexSourse.txt
#version 330 core			
layout (location = 0) in vec3 aPos;					
layout (location = 1) in vec3 aColor;	
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;	
out vec2 TexCoord;

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;


void main()							
{									
	gl_Position =  projMat * viewMat * modelMat * vec4(aPos.x,aPos.y,aPos.z,  1.0f);	

	ourColor = aColor;		
	
	TexCoord  = aTexCoord;
}

这里给出一个顶点着色器,可以看到有layout(location=0)in ,为了定义顶点数据改如何管理,我们可以把位置坐标放0号位置,也可以放1号。

而片段着色器需要一个颜色信息来画图,如果片段着色器没有定义颜色的话,就会把物体画成黑色或者白色。

片段着色器

//fragmentSourse.txt
#version 330 core			

in vec3 ourColor;		
in vec2 TexCoord;

out vec4 FragColor;	

uniform sampler2D ourTexture;
uniform sampler2D ourFace;
uniform float mixValue;

void main()					
{							
	//FragColor = texture(ourTexture,TexCoord);		
	//FragColor = texture(ourTexture,TexCoord) * texture(ourFace,vec2(1-TexCoord.x,TexCoord.y),0.2);
	FragColor = mix(texture(ourTexture,TexCoord),texture(ourFace,TexCoord),mixValue);
}	

这里可以看到一个除in out外的特殊变量uniform,这是一个全局变量,是用于我们可以随时改变渲染方式的参数,例如,我们需要不停的变化位置信息,变换纹理、颜色等。

那么该如何使用uniform呢,在渲染循环中,通过glGetUniformLocation查找uniform位置,然后glUniform4f()等函数来修改他。

链接顶点属性

这里插入一条和前面VBO联动的知识点。

在顶点着色器中,我们需要传入顶点的各个信息,那么我们改如何把信息从VBO放到着色器中。!!!有没有想到之前VBO和VAO,我们需要把VBO数据放在VAO的0,1,2号槽位上,而这些槽位就是顶点属性。

	//设置顶点属性指针
	//位置
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	颜色
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);
	//纹理坐标
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(2);

我们的vertexShader和FragmentShader的源码都是用txt写的,现在需要把txt文件编译然后变成我们熟悉的程序。

最右边是硬盘,里面有着我们写的着色器源码txt文件,但是我们怎么把txt文件传入cpu中呢?

在Shader/cpp和Shader.h中我需要完成的有:在内存中开辟一块buffer,把txt存进去。然后需要stringbuffer来对内存中的文件进行解释(哪个文件是做什么的)。因为OpenGL操作的是char,所以这里面还有个string转成char的过程,以及最重要的编译过程。

这里我就不仔细描述shader.cpp如何写的了,主要注重的是两个txt着色器是如何编写的。


Texture

纹理可以帮助我们让图形变得更加生动,可以想象一个墙纸,把它贴在我们的物体上,这样我们就不需要定义更多的顶点就可以画出一幅图了。

那么首先需要把纹理图片加载到程序中,这里使用的是最流行的stb_image.h库。

对于纹理,我们需要确定的是纹理坐标,也就是每个顶点的颜色应该是纹理图片中的哪个位置,纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1)。例如:一个三角形,我定义了在一个图片中的纹理坐标,那么三角形中的纹理则会在纹理图片中插值得到。

纹理环绕方式/纹理过滤

环绕方式用于处理当纹理坐标超出(0,1)时该取什么样的纹理。

纹理过滤用于处理当近物体和远物体该如何采样的问题。

(这一部分,后面有机会我会仔细写一篇我的理解)

代码实现

    //纹理
    stbi_set_flip_vertically_on_load(true);

	unsigned int TexBufferA;
	glGenTextures(1, &TexBufferA);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, TexBufferA);
	//环绕方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	int width, height, nrChannel;
	unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannel, 0);
	if (data) {

		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else {
		cout << "load image failed" << endl;
	}

	stbi_image_free(data);

材质和VBO那些很象,都是先需要生成然后绑定、填充等。不过这里绑定的通道是GL_TEXTURE_2D。然后定义环绕方式、过滤方式。加载需要的纹理图片等。


坐标系和旋转矩阵


摄像机

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值