• OpenGL基本架构知识


    OpenGL基本架构知识

    环境:Qt Creator + C++

    参考书籍:《计算机图形学编程(使用OpenGL和C++)》作者:V.斯科特.戈登 约翰.克莱维吉 (人民邮电出版社)

    参考博客:https://blog.csdn.net/weixin_59876363/article/details/122570371

    一、绪论

    简介
    1. OpenGL是什么

      • 用于渲染2D和3D矢量图形的跨语言、跨平台的应用程序编程接口(API)
      • 提供了多级图形管线和GLSL(高级着色语言)进行模型的像素化
      • 核心库使用c编写,同时支持多种系统和语言
    2. 核心模式/可编程管线模式:
      将顶点数据经过管线处理,最终生成显示的像素点。以下为渲染顺序:

      • 顶点着色器:必须自己实现,依靠顶点数据和着色器指令控制顶点的渲染位置和方式
      • 曲面细分着色器:可以在简单图形上生成大量网格图元(常用的是三角)
      • 几何着色器:赋予程序员一次处理一个图元(多个顶点)的能力(常用的是三角)
      • 光栅化:将输入的图元转化成二维图像片段,即像素绘制的片段
      • 片段着色器:为光栅化的像素点指定颜色
      • 测试与混合
    3. GLSL运行在GPU上,OS不是总能捕捉运行的错误,通常需要进行GLSL运行日志的打印进行debug

    4. GLSL代码加载进入着色器的过程

      • C++获取GLSL着色器代码,可以在文件中读取,也可以硬编码在字符串中
      • 创建OpenGL空着色器对象并将GLSL着色器代码加载进着色器对象(一般至少要提供顶点着色器和片段着色器的代码,其他着色器代码是可选的)
      • 最后使用OpenGL命令编译链接着色器对象,并将该对象加载进硬件
    5. QOpenGLWidget提供了三个便捷的虚函数,可以进行重载实现典型的OpenGL任务

      • initializeGL:初始化设置OpenGL资源和状态
      • paintGL:渲染OpenGL场景,widget需要进行实时更新的调用
      • resizeGL:设置OpenGL视口和投影等。widget调整大小时调用
    6. 顶点着色器会在GPU上创建内存,通过GL_ARRAY_BUFFER缓冲类型的顶点缓冲对象进行管理,每一个VBO记录了一种状态。

      • 数据:从内存加载顶点缓冲对象(Vertex Buffer Objects ,VBO)到的显存中
      • 规则:通过顶点数组对象(Vertex Array Object,VAO)对顶点数据进行解释
    7. OpenGL是一个巨大的状态机,数据输入后,绘制状态参数(材质,光照,连接方式···)决定了输出的图像。OpenGL通过改变上下文变量来改变OpenGL状态,从而告知OpenGL如何绘制图像

    8. 在paintGL之外的地方调用绘制函数,没有意义,绘制图像最终将被paintGL()覆盖。如果不想被覆盖则应该使用widget的update()函数进行安排和更新

    缓冲区对象
    1. 缓冲区绑定流程

      // 1.声明VBO和VAO
      GLuint vao[1];// GLuint实际就是unsigned int
      GLuint vbo[2];
      // 2.创建缓冲区,并将返回的ID存入VAO和VBO
      glGenVertexArrays(1, &VAO);
      glGenBuffers(2, &VBO);// (创建ID的数目, 用来保存返回ID的数组)
      // 3.将VAO和VBO绑定成缓冲区对象
      glBindVertexArray(VAO);
      glBindBuffer(GL_ARRAY_BUFFER, VBO);
      // 4. 初始化缓冲区数据
      glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
      // 5. 告诉GPU如何解释VBO中的属性信息
      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), nullptr);
      // 6. 启用0号顶点属性
      glEnableVertexAttribArray(0);
      glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),  (void*)(3 * sizeof(float)));
      glEnableVertexAttribArray(1);
      glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
      glEnableVertexAttribArray(2);
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      // 7. 使用glDrawArrays()绘制对象
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    2. 着色器的绑定:每个缓冲区需要有在顶点着色器中声明的相应的顶点属性变量

      layout(location = 0) in vec3 position
      // 位置值为0的顶点属性指针指向的数据,每次抓取3个到position中
      
      • 1
      • 2
      • layout修饰符是将顶点属性和特定缓冲区关联的方法,这里的0就是绑定的0号ID的VBO
      • in关键字表示顶点属性从缓冲区接收数值
      • vec3的意思是着色器每次调用会抓取三个浮点类型的值,即一个坐标(x, y, z)
      • 变量名称是position
    3. 通常把顶点数据放在一个缓冲区中,并把这个缓冲区和着色器中声明的顶点属性相关联

    4. 在OpenGL中,缓冲区被包含在顶点缓冲对象(Vertex Buffer Object, VBO)中,VBO在C++/OpenGL应用程序中被声明和实例化。一个场景可能需要很多个VBO,通常我们在初始化的阶段生成并填充若干个VBO,方便后续直接使用。

    5. 使用uniform关键字在着色器中声明统一变量

    GLSL
    1. GLSL(OpenGL Shading Language)使用C语言作为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。

      // 典型程序
      #version version_number
      in type in_variable_name;
      in type in_variable_name;
      out type out_variable_name;
      uniform type uniform_name;
      
      void main(){
          out_variable_name = weird_stuff_we_processed;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    2. OpenGL确保至少有16个包含4分量的顶点属性可以使用,但是可以声明的顶点属性数量存在上限

    3. GLSL的容器

      • 向量(Vetor)
        • vecn:n个float类型
        • bvecn:n个boolean类型
        • ivecn:n个intgers类型
        • uvecn:n个无符号整形
        • dvecn:n个double类型
      • 矩阵(Matrix)
    4. 向量重组

      vec2 vect = vex2(0.5, 0.7);
      vec4 result = vex4(vect, 0.0, 0.0);
      
      • 1
      • 2
    5. 如果类型和名字都一致,OpenGL可以把不同程序文件变量都链接在一起

      
      #version 330 core
      // 顶点着色器定义和声明变量
      layout (location = 0) in vec3 aPos;
      out vec4 vertexColor;
      void main() {
          gl_Position = vec4(aPos, 1.0);
          vertexColor = vec4(0.5, 0.0, 0.0, 1.0);
      }
      
      
      #version 330 core
      // 片段着色器接受变量
      out vec4 FragColor;
      in vec4 vertexColor;// 接收的变量
      void main() {
          FragColor = vertexColor; 
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      如果将它们封装在单独的资源文件中,使用shaderProgram.addShaderFromSourceFile()函数进行连接,则首行必须是版本号

    6. paintGL()以外的位置绘制openGL

      makeCurrent();
      // openGL绘制函数
      doneCurrent();
      update();
      
      • 1
      • 2
      • 3
      • 4

      如果不使用上述函数进行包含,绘制的图像将会被paintGL( )覆盖

    7. 顶点着色器的输入来源于openGL形式化的数据,使用layout(location = 0)链接到顶点数据,可以在cpu上配置顶点属性

    8. GPU和CPU之间的数据传输

      • 使用glGetAttribLocation查村属性位置值
      • 绑定GPU的属性值,告诉它向cpu解析的属性值
      // 1.属性值查询的方式
      shaderProgram.bind();
      // 查询属性位置值,如果查询不到返回-1
      Glint posLocation = shaderProgram.attributeLocation("aPos");
      // 告诉显卡如何解析缓冲里的属性值
      glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float),(void*)0);
      
      // 2.绑定的方式
      shaderProgram.bind();
      GLint posLocation = 2;
      shaderProgram.bindAttributeLocation("aPos", poslocation);
      glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
      // 开始VAO管理的第三个属性值
      glENableVertexAttribArray(posLocation);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    9. uniform是一种CPU向GPU中着色器发送数据的方式,是全局的Global,可以被任意着色器程序在任意阶段访问

      • 如果声明一个uniform却没有用过,编译器会默认移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误
      • OpenGL在其核心是一个c库,所以不支持类型的重载 ,在函数参数类型不同时,需要为其定义新的函数。
    10. 在Qt中可以使用QTimer的timeout信号槽,传递一个随着时间改变的值

    11. 将顶点着色器中的数据传到片段着色器中

      // 顶点着色器
      #version 330 core
      layout(location = 0) in vec3 aPos;
      layout(location = 1) in vec3 aColor;
      out vec3 ourColor;# 传递的值
      void main(){
          gl_Position = vex4(aPos, 1.0);
          ourColor = aColor;
      }
      
      // 片段着色器
      #version 330 core
      out vec4 FragColor;
      in vec3 ourColor;
      
      void main(){
          FragColor = vec4(ourColor, 1.0);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
    纹理处理
    1. 当出现复杂图形绘制时候通常采用纹理贴图采样的方式,即对一个图片进行裁剪后的部分显示

    https://blog.csdn.net/weixin_59876363/article/details/122807398

  • 相关阅读:
    Flutter快学快用10 路由设计:Flutter 中是如何实现 Scheme 跳转的
    KNN学习代码理解尝试
    行为型设计模式之观察者模式
    JS防抖与节流
    java基础、底层实现、面试
    Redis 主从复制
    dolphinscheduler-data-quality-3.1.0 部署
    周记之马上要答辩了
    no main manifest attribute, in xxx.jar
    OSG第三方库编译之三十六:Protobuf编译(Windows、Linux、Macos环境下编译)
  • 原文地址:https://blog.csdn.net/qq_43840665/article/details/126678095