OpenGL笔记

Learn OpenGl

on Modern OpenGL. -> 从graphics的programming开始讲的

Getting Start

OPENGL

  • 是一个进行图像处理的工具
  • 可以被认为是API,但是实际上是specification
    • 明确说明了每个function应该的输入和输出,以及如何perform
    • 用户在用这个说明来解决问题,因为没有给出明确的implement的过程,所以只要结果符合规则,怎么implement都可以

Core-profile vs Immediate mode

  • 以前的版本用的是immediate mode
    • 比较好用来画图
    • 具体的是实现都在lib里面,developer不是很好的能看到如何计算
    • 效率越来越低
  • Core-profile
    • 在3.2版本之后改成了这个
    • 强制使用modern practices,如果想要用被分出去的function就会直接报错
    • 效率高,更灵活,更难学

extensions

  • 支持extensions,只要检查支不支持graphic card就可以知道能不能用
  • 可以直接用比较新的东西,不用等着OPENGL更新新的功能
  • 需要在用之前判断他是不是available的,如果不是需要用原来的方法搞

State Machine

  • OpenGL自己就是一个State Machine:一个var的集合,来判断他现在应该如何操作
  • state -> context
    • 改变state:设定一些options,操作一些buffer,在现在的context来render
  • 例子:
    • 如果我想画三角形,而不是画线了,就改变draw的state
    • 只要这个改变传达到了,下一条线就画的是三角形了
  • state-changing用来改变context,state-using在现在的state上面开始进行操作

Objects

  • 一个集合来表现OpenGL的subset的state
    • 比如可以用一个object来表示对window的设定,可以设置大小,设置支持的颜色等等
      1
      2
      3
      4
      5
      6
      // The State of OpenGL
      struct OpenGL_Context {
      ...
      object_name* object_Window_Target;
      ...
      };
1
2
3
4
5
6
7
8
9
10
// create object
unsigned int objectId = 0;
glGenObject(1, &objectId);
// bind object to context
glBindObject(GL_WINDOW_TARGET, objectId);
// set options of object currently bound to GL_WINDOW_TARGET
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// set context target back to default
glBindObject(GL_WINDOW_TARGET, 0);
  • 流程
    • 首先创建了一个object,里面存了一个ref是这个object的id
    • 然后把这个object和context的目标位置bind在了一起
    • 设置了这些window的参数
    • 最后un-bind这两个东西,把window target改回原来的值
  • 这样的话我们可以创建很多object,提前设置好里面的量,等到需要用的时候就直接bind就可以用了
    • 比如我们有一堆object包含了小人,小马,小鹿
    • 想画哪个就把哪个绑定到draw里面,就可以直接画出来了

Crateing a window

因为操作系统的问题,所有操作系统上面不是很一样。但是已经有一些提供这些功能的函数了,这里用的是GLFW

GLFW

  • 一个lib,用C写的,主要目的是提供把东西渲染到屏幕的功能
  • 可以创建一个context,定义窗口的params,处理用户的输入

已经一口气配置好了这些!
https://www.jianshu.com/p/25d5fbf792a2
记得在link lib里面把openGL的framework加进去!!!!!

GLAD

  • 因为openGL还需要不同版本的driver的支持,需要有东西来处理这部分的内容
  • 和其他的东西不同,GLAD用的是web service
    • 在这个网页上选择好语言,版本号,确保profile是core,然后生成
    • 直接下载下来对应的zip,然后把include放进include里面,.c文件放在project里面
  • 莫名其妙并不需要这一步,神奇,可能是我在include里面已经搞进来了!!

Hello Window

初始化

1
2
3
4
5
6
7
8
9
10
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

return 0;
}
  • 首先进行了初始化
  • 然后configure了GLFW,设置了a large enum of possible options prefixed with GLFW_. (第三行就是最小) -> 大概是设置要用GLFW的版本号
  • 然后也告诉了他想用core

  • 然后需要使用glfwCreateWindow这个函数,来创建这个GLFWwindow* window的变量
    • 创建的函数需要窗口的长宽
    • 窗口名
  • 创建完之后就可以把这个窗口设置成glfwMakeContextCurrent(window);也就是说设置成了现在的thread里面
    1
    2
    3
    4
    5
    6
    7
    8
    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);

GLAD

  • GLAD是为OpenGL来管理这些函数的,在使用这些函数之前需要初始化GLAD
    1
    2
    3
    4
    5
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
    }

viewpoint

  • 在开始render之前我们还需要告诉GL渲染窗口的大小,用到了glViewport这个函数
    • 前面两个参数定义了这个窗口左下角的坐标
    • 后面两个参数定义了需要render的窗口的大小
  • 每次调整window的大小的时候viewport也需要被调整

engines

  • 我们希望这个engine可以一直持续画图,直到最后我们告诉这个窗口要关闭,所以要建立一个循环

    1
    2
    3
    4
    5
    while(!glfwWindowShouldClose(window))
    {
    glfwSwapBuffers(window);
    glfwPollEvents();
    }
  • 在这个循环里面,pollevent是来检查是不是有trigger进来的事情(比如键盘输入),更新窗口的状态,并且call相应的函数

  • swapbuffer,会交换color buffer(包括每个像素点颜色的buffer),然后show在窗口里面

last thing

  • glfwTerminate退出这个循环之后,需要清除这些相关的资源,用这个函数放在最底下

input

  • 需要一些键盘上的操作来调整的时候,写了一个processInput的函数
  • 比如下面这个函数就是检测了有没有按下去esc,如果按了的话就关闭窗口
  • 写完之后把这个函数在while循环里面调用
    1
    2
    3
    4
    5
    void processInput(GLFWwindow *window)
    {
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
    glfwSetWindowShouldClose(window, true);
    }

rendering

  • 希望在一个loop里面放上去所有的rendering的命令,整个循环看起来应该是这个样子的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // render loop
    while(!glfwWindowShouldClose(window))
    {
    // input
    processInput(window);

    // rendering commands here
    ...

    // check and call events and swap the buffers
    glfwPollEvents();
    glfwSwapBuffers(window);
    }

hello triangle


opengl里面所有东西都是在3D的空间里的,但是屏幕上显示的东西是2D的。整个这个转换的过程叫做graphics pipeline,可以分成两个步骤:第一个是把物体的3D坐标转化成2D的坐标,第二个是把2D的坐标转化成pixel上面的具体值

pipeline

  • 所有的转化步骤都可以parallel的进行,现在的显卡有很多小的core来进行 -> shaders
  • 在最开始的时候pass进去了一个list的3D坐标 (Vertex Data)
  • 第一步:vertex shader
    • 把3D的坐标转化成不同的3D坐标(相当于把数据转化成点?)
  • primitive assembly
    • 从上一步得到的左右的点得到输入
    • 然后形成一个基本的图形
  • geometry shader
    • 根据新给的点,形成新的不同的形状,比如在例子里面形成了新的一条线
  • rasterization stage
    • 把上面得到的primitives map到最后的屏幕上面的相应的pixel上面
  • Clipping
    • 这一步丢掉了所有在视线外面的fragments,提升性能
  • fragment shader
    • 计算这个pixel最后的颜色,会在这一步计算光影,以及光线的颜色等等东西
  • 当每个像素的颜色决定了以后,这个object会被送到alpha test和blending
    • 这一步会测试深度原因,判断fragment是在物体的前面还是后面
    • 还会考虑透明度的问题
      虽然上面的东西很复杂,但是在实际应用的时候只需要要考虑vertex和fragment shader

vertex input

  • openGL是3D的东西,所有的点设置input的时候都需要设置三维的坐标 xyz
  • 只有在坐标在 -1 到 1 中间的时候,才会处理这些坐标,这个范围里面的数字是根据屏幕的比例得出来的normalized device coordinates
  • 比如在这个例子里面,需要的渲染一个三角形,那么需要这个三角形的三个点的坐标。注意这个例子里面根本没有考虑深度,而是直接画在了平面上面

    1
    2
    3
    4
    5
    float vertices[] = {
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.0f, 0.5f, 0.0f
    };
  • 在定义好坐标之后需要把这个东西放进vertex shader里面,需要在GPU里面创建一部分内存来存储这个数据,并且需要在GPU里面存储大量的数据(这样不用每次都送了)

  • 每个部分的object都会有一个自己的buffer id,可以通过下面的方法生成一个id。也可以把一串array绑到这个id上面
    1
    2
    unsigned int VBO;
    glGenBuffers(1, &VBO);