本文目录
引言
1、本专栏为LearnOpenGL的学习笔记,用来记录基础知识、渲染流程和遇到的问题。如果文中理解有误,还请指正。
2、GLFW API手册
3、OpenGL Specification
1 基础知识
1.1 OpenGL
OpenGL不是API,而是显卡厂商实现这组API的接口与规范。OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。至于内部具体每个函数是如何实现(Implement)的,将由OpenGL库的开发者自行决定。
1.2 OpenGL上下文
OpenGL上下文(整个渲染视图)是以对象的形式存储的,渲染视图中的每个部分作为该对象中的一个嵌套对象存在,每个对象通过成员变量和成员函数完成其属性的设置,如下所示。
// OpenGL上下文
struct OpenGL_Context {
...
object* object_Window_Target;
...
};
1.3 GLFW和GLAD
GLFW是一个轻量级的OpenGL框架,提供了一套用来管理窗口,读取输入,处理事件等操作的API(说白了就是为视口提供一个可以和用户交互的窗口?)。
GLAD专门加载OpenGL的函数指针的Loading Library,因为OpenGL驱动版本太多编译期无法确定使用的是哪个,因此开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用,使用GLAD管理函数指针可以避免大量的查找函数指针这种重复性代码(实际上这些代码都存在,只是不需要我们再自己写了)。
1.4 窗口对象和视口
窗口:视口用来与用户交互的一个中间件
视口:真正的渲染区域
2 渲染流程及API
2.1 初始化GLFW及配置GLFW窗口
初始化GLFW:int glfwInit(void)
此函数用于初始化GLFW库,在使用GLFW函数之前必须初始化GLFW。如果成功则返回GLFW_TRUE、失败则返回GLFW_FALSE并在返回之前调用glfwTerminate释放已分配的资源。
配置GLFW窗口:void glfwWindowHint(int hint, int value)
此函数用于配置glfw,通过该函数可以告诉GLFW我们要使用的OpenGL版本和渲染模式。
//配置GLFW窗口
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3); //设置opengl的主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3); //设置opengl的次版本号
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE); //使用opengl的核心模式,即库不再向后兼容
//初始化GLFW
if(!glfwInit()){ //成功return1 失败return0
std::cout<<"Failed to create GLFW window" << std::endl;
glfwTerminate();//NOTE:在应用程序终止前需要终止glfw
return -1;
}
2.2 创建窗口对象并设为当前进程的主上下文:
创建窗口对象:GLFW window* glfwCreateWiondow(int width, int height, const char* title, GLFWmintor* monitor, GLFWwindow* share)
该函数用于创建GLFW窗口对象,这个窗口对象存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁地用到。
设为当前进程的主上下文:int glfwInit(void)
该函数用于通知GLFW将窗口对象的上下文设置为当前线程的主上下文。
//2、创建一个窗口对象(存放所有和窗口相关的数据)----> 该对象会被GLFW的其他函数频繁调用
GLFWwindow* window = glfwCreateWindow(800,600,"Kkaiw",NULL,NULL); //width height title monitor share,该函数返回一个窗口对象
if(window == NULL){
std::cout<<"Fail to create GLFW window"<<std::endl;
glfwTerminate();
return -1;
}
//创建完窗口后,将窗口的上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
2.3 初始化GLAD
返回OpenGL的函数地址:GLFWglproc glfwGetProcAddress (const char * procname)
该函数用于返回指定的OpenGL核心/扩展函数地址。
初始化GLAD库:int gladLoadGLLoader(GLADloadproc load)
GLAD是用来管理OpenGL的函数指针的,任何OpenGL API都必须在初始化GLAD库后才可以正常访问。如果成功则返回GL_TRUE,失败则返回GL_FALSE。
2.4 动态设置视口尺寸
设置视口尺寸:void glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
该函数用于设置视口在窗口中的位置及其尺寸,其中(x,y)表示视口在窗口左下角的坐标,width和height表示视口的尺寸。
注册回调函数:void framebuffer_size_callback(GLFWwindow* window, int width, int height);
回调函数在窗口改变大小时被触发,使视口可以随窗口尺寸的改变而实时改变尺寸。
void framebuffer_size_callback(GLFWwindow* window,int width,int height){
glViewport(0,0,width,height);//根据更新后的窗口,同比更新视口大小
}
【Note】OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。
2.5 循环渲染
我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),它能在我们让GLFW退出前一直保持运行。
检测GLFW是否被要求退出:glfwWindowShouldClose()
该函数在每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
检测GLFW窗口是否有触发事件:glfwPollEvents()
该函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
交换缓冲区:glfwSwapBuffers()
该函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上,双缓冲可以有效地避免渲染时的闪烁现象。
检测特定按键是否被按下:glfwSwapBuffers
该函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
//5、循环渲染
while(!glfwWindowShouldClose(window)){ //检查glfw是否要求退出,如果是return true
//输入检测
processInput(window);
//当前帧渲染
// 检查并调用事件,交换缓冲
glfwSwapBuffers(window); //交换颜色缓冲(这个缓冲存储着GLFW窗口中没一个像素的颜色值),里面有两个buffer,前缓冲保存最当前帧的输出,后缓冲保存下一阵的渲染指令。避免等待绘制带来的闪烁感。
glfwPollEvents();
}
上述代码是GLFW实现循环渲染和输入检测的框架,接着我们在当前帧渲染区域中填入当前帧的渲染代码就完成了逐帧的动态渲染。
视口颜色状态设置函数:glClearColor()
该函数用于设置清空屏幕所用的颜色。
视口颜色状态使用函数:glClear()
该函数使用状态设置函数设置的颜色来清空屏幕(感觉像是把成员变量重新赋值的逻辑?)
2.6 释放已allocate的上下文资源
当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在main函数的最后调用glfwTerminate函数来完成。
释放GLFW已分配的资源:void glfwTerminate (void)
该函数将销毁所有剩余的窗口和光标,并释放任何GLFW分配的资源。
3 渲染结果
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
//帧缓冲大小函数:当用户改变窗口大小时,视口的大小也应该同比调整,所以需要对窗口注册一个回调,使其在每次窗口大小被调整时被调用,以更新视口大小
//实现方式:我们通过在窗口改变时用glfwSetFramebufferSizeCallback()函数来调用这个回调函数
void framebuffer_size_callback(GLFWwindow* window,int width,int height){
glViewport(0,0,width,height);//根据更新后的窗口,同比更新视口大小
}
void processInput(GLFWwindow *window){
if(glfwGetKey(window,GLFW_KEY_ESCAPE)==GLFW_PRESS){
glfwSetWindowShouldClose(window,true);
}
}
int main(){
//实例化GLFW窗口
//1、初始化GLFW
if(!glfwInit()){ //成功return1 失败return0
std::cout<<"Failed to create GLFW window" << std::endl;
glfwTerminate();//NOTE:在应用程序终止前需要终止glfw
return -1;
}
//配置GLFW窗口
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3); //设置opengl的主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3); //设置opengl的次版本号
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE); //使用opengl的核心模式,即库不再向后兼容
//如果是MAC OS 则需要执行以下命令才能完成上述初始化
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,GL_TRUE);
#endif
//2、创建一个窗口对象(存放所有和窗口相关的数据)----> 该对象会被GLFW的其他函数频繁调用
GLFWwindow* window = glfwCreateWindow(800,600,"Kkaiw",NULL,NULL); //width height title monitor share,该函数返回一个窗口对象
if(window == NULL){
std::cout<<"Fail to create GLFW window"<<std::endl;
glfwTerminate();
return -1;
}
//创建完窗口后,将窗口的上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
//3、视口:在开始渲染前,需要告诉OpenGL渲染窗口的尺寸大小,即视口。这样OpenGL才知道怎么根据窗口大小显示数据和坐标
//glViewport(0,0,800,600); //通过glViewport来设置视口的维度,!前两个参数是视口在窗口左下角的位置,后两个参数是视口的宽度和高度
//设置视口随窗口改变
glfwSetFramebufferSizeCallback(window,framebuffer_size_callback); //这个函数第一次也会执行 所以不需要先glview
//4、GLAD是用来管理OpenGL的函数指针的,所以在调用任何GpenGL函数之前我们需要初始化GLAD
if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){ //给glad传入当前系统加载OpenGL函数指针地址的函数,GLFW提供的该函数为:glfwGetProcAddress
std::cout<<"Failed to initialize GLAD"<<std::endl;
return -1;
}
//5、循环渲染
while(!glfwWindowShouldClose(window)){ //检查glfw是否要求退出,如果是return true
//Input
processInput(window);
glClearColor(0.1f, 0.4f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//glfw:swap buffers and poll IO event(keys pressed/released,mouse moved etc.)
glfwSwapBuffers(window); //交换颜色缓冲(这个缓冲存储着GLFW窗口中没一个像素的颜色值),里面有两个buffer,前缓冲保存最当前帧的输出,后缓冲保存下一阵的渲染指令。避免等待绘制带来的闪烁感。
glfwPollEvents();
}
//6、清空之前allocate的所有glfw资源
glfwTerminate();
return 0;
}
4 遇到及待解决的问题
4.1 编译时函数名未引用
需要将glad.c放在main.c同级目录下。
4.2 快速放大窗口时,视口花了
因为每次更新视口时没有清空上一帧的视口,即循环渲染中没有写glclearcolor和glclear函数,在循环渲染中加入清屏操作即解决。但是为什么不清楚屏幕会出现这种花边的原因暂未解决,因为如果缓慢方法窗口时是不会出现这个花边的。
4.3 GLFW窗口的配置顺序
LearnOpenGL中是先初始化GLFW后配置GLFW,但是看GLFW文档好像应该先配置后初始化,不然是无效的,在这个实验里目前看不出区别。