opengl基础

opengl

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.而后建立窗口、设置视口、设置窗口大小调整后的回调在回调中要处理视口、接着是渲染循环、还要处理处输入等。对象

实例化配置glfw
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();
  • glfwWindowShouldClose函数在咱们每次循环的开始前检查一次GLFW是否被要求退出,若是是的话该函数返回true而后渲染循环便结束了,以后为咱们就能够关闭应用程序了。
  • glfwPollEvents函数检查有没有触发什么事件(好比键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(能够经过回调方法手动设置)。
  • glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每个像素颜色值的大缓冲),它在这一迭代中被用来绘制,而且将会做为输出显示在屏幕上。
  • glClear函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲,可能的缓冲位有GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT。
  • 调用了glClearColor来设置清空屏幕所用的颜色
  • glClearColor函数是一个状态设置函数,而glClear函数则是一个状态使用的函数,它使用了当前的状态来获取应该清除为的颜色。
  • glfwTerminate(); 释放全部申请的资源
双缓冲(Double Buffer)

应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是由于生成的图像不是一会儿被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是经过一步一步生成的,这会致使渲染的结果很不真实。为了规避这些问题,咱们应用双缓冲渲染窗口应用程序。缓冲保存着最终输出的图像,它会在屏幕上显示;而全部的的渲染指令都会在缓冲上绘制。当全部的渲染指令执行完毕后,咱们交换(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);
}
相关文章
相关标签/搜索