1. 初识OpenGL
本文是之前学习OpenGL所做的笔记,现在转到这里。
1.1 OpenGL(Open Graphics Library)
一般它被认为是一个应用程序编程接口(API),它包含了一系列可以操作图形、图像的方法。
然而,OpenGL本身并不是一个API,仅仅是一个规范,由Khronos组织制定并维护,所有版本的OpenGL规范文档都被公开的寄存在Khronos。
OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。
OpenGL规范并没有规定实现的细节,具体的OpenGL库允许使用不同的实现,只要其功能和结果与规范相匹配。
1.2 核心模式(Core-profile)与立即渲染模式(Immediate mode)
立即渲染模式(固定渲染管线):该模式下绘制图形很方便,大多数功能都被库隐藏起来,容易使用和理解。但灵活性差,开发者很少能控制OpenGL如何进行计算。
核心模式:要求使用者真正理解OpenGL和图形编程,它有一些难度,然而提供了灵活性和高效率,更重要的是可以更深入的理解图形编程。
注意:当使用新版本的OpenGL特性时,只有新一代的显卡能够支持你的应用程序。这也是为什么大多数开发者基于较低版本的OpenGL编写程序,并有选择的启用新特性。
1.3 扩展(Extension)
当一个显卡公司提出一个新特性或者渲染上的大优化,通常会以扩展的方式在驱动中实现。当这个扩展十分流行或非常有用的时候,它最终将成为未来OpenGL规范的一部分。
使用扩展的代码大多如下:
If (GL_ARB_extension_name)
{
//使用硬件支持的全新的现代特性
}
Else
{
//不支持此扩展:用旧的方式去做
}
1.4 状态机(State Machine)
OpenGL自身是一个巨大的状态机:一个描述OpenGL该如何操作的所有变量的大集合。
OpenGL的状态通常被称为OpenGL上下文(Context)。
我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。
我们可以通过改变一些上下文变量来改变OpenGl状态,从而告诉OpenGL如何去绘图。其中状态设置函数(Sate-changing Function)会改变上下文,状态使用函数(State-using Function)则会根据当前的状态执行一系列操作。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。
把OpenGL当作一个大状态机,就能更容易理解它的大部分特性。
补漏:什么是状态机?
基本定义:状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。
四大概念:
状态(State):一个状态机至少要包含两个状态。例如一个自动门,可以开,关。它有open和closed两个状态。
事件(Event):事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。
动作(Action):事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个Action一般就对应一个函数。
变换(Transition):也就是从一个状态变化为另一个状态。例如“开门过程”就是一个变换。
1.5 对象(Object)
OpenGL库内核是一个C库,由于一些C语言结构不易被翻译到其它高层语言,因此OpenGL设计的时候引入了“对象”这一抽象概念。
在OpenGL中一个对象是指一些选项的集合,代表OpenGL状态的一个子集。
比如,我们可以用一个对象来代表绘图窗口的设置,可以设置它的大小,支持的颜色位数等等。可以把对象看作一个C风格的结构体:
Struct object_name{
GLfloat option1;
Gluint option2;
Glchar[] name;
}
OpenGL定义的这些GL原始类型是平台无关的内存排列方式,而int等类型在不同平台上可能有不同的内存排列方式。使用GL原始类型可以保证你的程序在不同的平台上工作一致。
使用一个对象时,通常看起来如下(把OpenGL上下文比作一个大的结构体):
OpenGL常见工作流
//OpenGL的状态
Struct OpenGL_Context
{
...
Object* object_Window_Target;
...
};
//创建对象
GLuint objectId = 0;
glGenObject(1, &objectId);
//绑定对象至上下文
glBindObject(GL_WINDOW_TARGET, objectId);
//设置GL_WINDOW_TARGET对象的一些选项
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
//将上下文的GL_WINDOW_TARGET对象设回默认
glBindObject(GL_WINDOW_TARGET, 0);
我们首先创建一个对象,然后用一个id保存它的引用(实际数据被储存在后台)。
然后我们将对象绑定至上下文的目标位置(例子里窗口对象的目标位置被定义成GL_WINDOW_TARGET)。
接下来我们设置窗口的选项。
最后我们通过将目标位置的对象id设回0的方式解绑这个对象。
设置的选项被保存在objectID代表的对象中,一旦我们重新绑定这个对象到GL_WINDOW_TARGET位置,这些选项就会重新生效。
使用对象的一个好处是在程序中,我们不只可以定义一个对象,并设置它们的选项,每个对象都可以是不同的设置。
2. GLFW基础知识及窗口创建
一些针对OpenGL的库可以节约书写操作系统相关代码的时间,提供给我们一个窗口和一个OpenGL上下文用来渲染。教程中所用的库为GLFW,它提供了一些渲染物体所需的最低限度的接口。按流程配置好GLFW和GLAD库之后,我们就可以利用这些库来设置OpenGL上下文,创建窗口了。
在创建窗口之前,可以先了解一下GLFW的窗口相关功能。
2.1 窗口对象
GLFWwindow对象封装了窗口和上下文。它们是用glfwCreatewindow创建的,用glfwDestroyWindow或者glfwTerminate销毁。由于窗口和上下文是不可分割的联系在一起的,所以对象指针同时被作用于上下文和窗口句柄。
2.1.2 窗口创建
窗口及其OpenGL或OpenGL ES上下文是用glfwCreateWindow函数创建的,它返回创建的窗口对象的句柄。例如,创建一个窗口模式(windowed mode)下640*480的窗口,执行以下代码:
GLFWwindow* window = glfwCreateWindow(640, 480, “My Title”, NULL, NuLL);
如果窗口创建失败,将返回NULL,因此需要检查返回值。
全屏窗口覆盖显示器的整个显示区域,没有边框或装饰。要创建全屏窗口(Full screen windows),执行以下代码:
GLFWwindow* window = glfwCreateWindow(640, 480, "My Title", glfwGetPrimaryMonitor(), NULL);
使用glfwSetWindowMonitor函数设置显示器可以使窗口模式变为全屏显示,取消设置的话可以使全屏显示转换为窗口显示。
2.1.3 窗口销毁
当不再需要一个窗口时,用glfwDestroyWindow销毁该窗口。
glfwDestroyWindow(window);
在销毁窗口之前,所有的回调函数都被移除,因此不会再为该窗口传递任何事件。当调用glfwTerminate时,所有剩余的窗口也被销毁。
2.1.4 窗口创建提示
创建窗口和上下文之前,可以设置许多提示。有些影响窗口本身,有些影响帧缓冲区或上下文。:
glfwInit():初始化GLFW库。
glfwWindowHint():单独设置整数值提示。
glfwWindowHintString():单独设置字符串值提示。
glfwDefaultWindowHints():一次将所有提示重置为默认值。
这些提示分为窗口相关提示(Window related hints),帧缓冲区相关提示(Framebuffer related hints),显示器相关提示(Framebuffer related hints),以及上下文相关提示(Context related hints)。
某些提示是特定于某一平台的,其他平台会忽略他们,设置这些提示不需要特定于平台的头或调用。
2.2 实例化GLFW窗口
了解了一些基本的GLFW的窗口相关功能之后,就可以开始创建GLFW窗口了。
#include <glad/glad.h>//GLAD头文件包含了OpenGL头文件,所以在使用其它依赖于OpenGL的头文件之前要先包含GLAD
#include <GLFW/glfw3.h>
int main()
{
glfwInit();//初始化GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号,当API以不兼容的方式更改时,该值会增加。
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本号,当特性被添加到API中时,它会增加,但是它保持向后兼容。
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//使用核心模式
return 0;
}
2.2.1 glfwInit()与glfwTerminate()
glfwInit():
在使用大多数GLFW函数之前,必须初始化GLFW。并且在应用程序终止之前,也应该终止GLFW,以便释放在初始化期间或之后分配的任何资源。如果这个函数调用失败,它在返回之前调用glfwTerminate。如果这个函数调用成功,您应该在应用程序退出之前调用glfwTerminate。
glfwTerminate():
此函数销毁所有剩余的窗口和光标,恢复任何修改的gamma ramps,并释放任何其他分配的资源。调用此函数后,必须再次成功调用glfwInit,才能使用大多数GLFW函数。
2.2.2 glfwCreateWindow函数
该函数创建一个窗口及其相关的OpenGL或OpenGL ES上下文,完整函数名如下:
GLFWwindow * glfwCreateWindow (int width, //以屏幕坐标表示的窗口的期望宽度,必须大于零。
int height, //以屏幕坐标表示的窗口期望高度,必须大于零。
const char * title,//窗口标题。
GLFWmonitor * monitor,//用于全屏模式的monitor,对于窗口模式为空。
GLFWwindow * share //要与其共享资源的上下文的窗口,值为NULL表示不共享资源。
)
了解了上面几个基础函数之后,我们开始创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据。
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);
2.2.3 初始化GLAD
GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD。
//给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
2.2.4 framebuffer_size_callback函数
设置视口,确定OpenGL渲染窗口的尺寸大小,这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。我们可以通过调用glViewport函数来设置窗口的维度:
glViewport(0, 0, 800, 600);//前两个参数控制窗口左下角的位置,第三个和第四个参数控制渲染窗口的宽度和高度。
设置一个帧缓冲大小函数,当用户改变窗口的大小的时候,视口也应该被调整:
voidframebuffer_size_callback(GLFWwindow* window, int width, int height){ glViewport(0, 0, width, height); }
这是一个回调函数,它会在每次窗口大小被调整的时候被调用,我们会在创建窗口之后,渲染循环初始化之前注册这些回调函数。
使用glfwGetKey函数实现输入控制,它需要一个窗口以及一个按键作为输入,这个函数将会返回这个按键是否正在被按下。
voidprocessInput(GLFWwindow *window){
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true); //按下ESC,关闭GLFW
}
2.2.5 渲染循环
保证主动关闭应用程序之前,它能够不断绘制图像并能够接受用户输入。
为测试起见,教程设置了一个自定义的颜色清空屏幕。在每个新的渲染迭代开始的时候清屏。
glClearColor函数是一个状态设置函数,设置颜色和Alpha值,用于清除颜色缓冲区,需要使用的填充值范围被归一化在(0,1)之间。
而glClear函数则是一个状态使用的函数,利用当前缓冲区清除值(glClearColor、glClearDepth、glClearIndex、glClearStencil、glClearAccum)等函数所指定的值来清除指定的缓冲区。
当调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。
while (!glfwWindowShouldClose(window))//glfwWindowShouldClose函数在每次循环开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true,渲染循环结束。
{
processInput(window);//循环每次迭代时调用该函数,检测用户是否输入ESC键
//渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//清除颜色设置为X色
glClear(GL_COLOR_BUFFER_BIT);//执行清除
//检查并调用事件,交换缓冲
glfwSwapBuffers(window);//glfwSwapBuffers函数会交换颜色缓冲(储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
glfwPollEvents();//glfwPollEvents函数检查是否触发事件(比如键盘输入、鼠标移动)、更新窗口状态,并调用对应地回调函数(可以通过回调方法手动设置)
}
运行代码之后,屏幕颜色如下:
参考资料:
标签:01,窗口,函数,OpenGL,认知,GL,GLFW,上下文 From: https://www.cnblogs.com/nexus-helix/p/17334382.html