opengl是一个由Khronos组织制定并维护的规范(Specification) 。是一系列的图形软件编程接口,和gdi相似。opengl有不少封装的库最有名的GLFW库。接下来不少东西以GLFW 为例子来讲明一些api的使用问题,但这并不影响opengl自己的逻辑表述。ios
OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态一般被称为OpenGL上下文(Context)。咱们一般使用以下途径去更改OpenGL状态:设置选项,操做缓冲。最后,咱们使用当前OpenGL上下文来渲染。编程
假设当咱们想告诉OpenGL去画线段而不是三角形的时候,咱们经过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦咱们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。api
当使用OpenGL的时候,咱们会遇到一些状态设置函数(State-changing Function),这类函数将会改变上下文。以及状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操做。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。函数
在OpenGL中一个对象是指一些选项的集合,它表明OpenGL状态的一个子集 。oop
当咱们使用一个对象时,一般看起来像以下同样:线程
// OpenGL的状态 struct OpenGL_Context { ... object* object_Window_Target; ... }; // 建立对象 unsigned int 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); // 将上下文对象设回默认 glBindObject(GL_WINDOW_TARGET, 0);
这一段代码展示了使用OpenGL时常见的工做流。咱们首先建立一个对象,而后用一个id保存它的引用(实际数据被储存在后台)。而后咱们将对象绑定至上下文的目标位置(例子中窗口对象目标的位置被定义成GL_WINDOW_TARGET)。接下来咱们设置窗口的选项。最后咱们将目标位置的对象id设回0,解绑这个对象。设置的选项将被保存在objectId所引用的对象中,一旦咱们从新绑定这个对象到GL_WINDOW_TARGET位置,这些选项就会从新生效。code
咱们要开始一个图形渲染程序,首要是要选择gl库,由于要使用api.而后建立窗口、设置视口、设置窗口大小调整后的回调在回调中要处理视口、接着是渲染循环、还要处理处输入等。对象
int main() { glfwInit(); //初始化glfw glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //配置glfw glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); return 0; }
首先,咱们在main函数中调用glfwInit函数来初始化GLFW,而后咱们可使用glfwWindowHint函数来配置GLFW。glfwWindowHint函数的第一个参数表明选项的名称,咱们能够从不少以GLFW_开头的枚举值中选择;第二个参数接受一个整形,用来设置这个选项的值接口
接下来咱们建立一个窗口对象,这个窗口对象存放了全部和窗口相关的数据,并且会被GLFW的其余函数频繁地用到。事件
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL); if (window == NULL) { glfwTerminate(); return -1; } glfwMakeContextCurrent(window);
glfwCreateWindow函数须要窗口的宽和高做为它的前两个参数。第三个参数表示这个窗口的名称(标题),。这个函数将会返回一个GLFWwindow对象,建立完窗口咱们就能够通知GLFW将咱们窗口的上下文设置为当前线程的主上下文了。
必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。咱们能够经过调用glViewport函数来设置窗口的维度(Dimension):
glViewport(0, 0, 800, 600);
glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。
OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标,处理过的OpenGL坐标范围只为-1到1。
当窗口大小发生变换的时候须要设置视口的大小:
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* window, int width, int height){ glViewport(0, 0, width, height); });
咱们一样也但愿可以在GLFW中实现一些输入控制,这能够经过使用GLFW的几个输入函数来完成。咱们将会使用GLFW的glfwGetKey函数,它须要一个窗口以及一个按键做为输入。这个函数将会返回这个按键是否正在被按下。咱们将建立一个processInput函数来让全部的输入代码保持整洁。
void processInput(GLFWwindow *window) { if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); }
须要在程序中添加一个while循环,咱们能够把它称之为渲染循环(Render Loop),它能在咱们让GLFW退出前一直保持运行。下面几行的代码就实现了一个简单的渲染循环:
// 渲染循环 while(!glfwWindowShouldClose(window)) { // 输入 processInput(window); // 渲染指令 ... glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // 检查并调用事件,交换缓冲 glfwPollEvents(); glfwSwapBuffers(window); } glfwTerminate();
true
而后渲染循环便结束了,以后为咱们就能够关闭应用程序了。应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是由于生成的图像不是一会儿被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是经过一步一步生成的,这会致使渲染的结果很不真实。为了规避这些问题,咱们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而全部的的渲染指令都会在后缓冲上绘制。当全部的渲染指令执行完毕后,咱们交换(Swap)前缓冲和后缓冲,这样图像就当即呈显出来,以前提到的不真实感就消除了。
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window); // settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; int main() { //glfw初始化和设置 glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //建立窗口配置窗口 GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { glfwTerminate(); return -1; } //设置问当前窗口上下文 glfwMakeContextCurrent(window); //设置窗口大小改变的回调 处理视口变化 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // render loop while (!glfwWindowShouldClose(window)) { // input processInput(window); // render glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) // ------------------------------------------------------------------------------- glfwSwapBuffers(window); glfwPollEvents(); } // glfw: terminate, clearing all previously allocated GLFW resources. glfwTerminate(); return 0; } void processInput(GLFWwindow *window) { if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); }