通过之前的教程,我们已经拥有了开发环境,但是在真正开发程序之前,我们首先了解下Opengl的基本概念。

Opengl是什么?

通常网上会说Opengl是一种规范,一种接口,但是这种说法有点抽象,我们不妨先看看下面这个简单的gl流程

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
int main()
{//glfw的初始化和设置// -----------------------------------------------------//调用glfwInit函数来初始化GLFWglfwInit();//配置GLFW,第一个参数代表选项的名称,第二个参数接受一个整型,用来设置这个选项的值//此处设置表示使用的OpenGL版本号3.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//glfwWindowHint作为窗口创建的一种提示,可以设置窗口的多种属性,包括透明度等等,感兴趣的可以在glfw文档中查询glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//使用流水线配置模式glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//glfw窗口创建// -----------------------------------------------------//glfwCreateWindow函数需要窗口的宽和高作为它的前两个参数,第三个参数表示这个窗口的名称,最后两个参数我们暂时忽略,它返还一个GLFWwindow对象GLFWwindow* window = glfwCreateWindow(800, 800, "OpenGL", NULL, NULL);//创建完窗口,通知GLFW将我们窗口的上下文设置为当前线程的主上下文了glfwMakeContextCurrent(window);while (!glfwWindowShouldClose(window)){//OpenGL采用双缓冲来渲染窗口glfwSwapBuffers(window);//处理按键事件glfwPollEvents();}return 0;
}

代码中可能有人对GLFW_OPENGL_PROFILE这类参数感到疑惑,或者对glfwWindowHint的更多设置感兴趣,你可以在glfw窗口官方文档中具体了解

运行代码,会获得一个如图的结果

在上面流程中,我们通过glfwInit()、glfwWindowHint()、glfwCreateWindow()等等就创建了一个窗口对象,并没有写具体逻辑,也就是说,我们是通过调用一系列的接口去实现了这个窗口对象,这是opengl作为接口的解释。

那么具体逻辑是谁实现的呢?我们首先要了解一个背景,Khronos组织一直在维护opengl接口,如果Khronos定义了一个glDrawLIne的接口,那么支持opengl的GPU设备厂商会让自己的程序员在硬件产品上用图像逻辑去实现这个接口,而当前主流的GPU设备厂商都是支持opengl的。

所以是Khronos和GPU设备厂商帮我们实现了具体逻辑,而我们要做的就是遵循这一系列接口的规范,去得到想要的内容。

Opengl的流程

在理解opengl的概念后,可能大家对GPU和opengl的关系有所了解,接下来不妨看看下面这张图。

opengl渲染管线

简单来说opengl的流程,首先调用opengl的接口,将Attributes、textturedata、uniform等数据输入到vetexshader等着色器中,如果是attributes数据,顶点着色器会处理它,并把结果流转到下一个primitive assembly中实现光栅化,紧接着配合textturedata通过片元着色器进行处理,最终渲染出结果,你可以对照着opengl渲染管线图进行参考(本文流程中并未介绍几何着色器,因为它并非是必须的,也许以后我们会详细解释它)。

opengl常见的名词

这里提及到着色器,光栅化等词汇,这些都是opengl常见的名词,我们不妨先了解下其含义:

Attributes 一种变量类型,用来接收经常发生改变的数据,比如颜色数据、顶点数据、纹理数据、光照法线等;Attributes只能传给顶点着色器里面,不能直接传递到片元着色器,需要通过GLSL代码间接传递给片元着色器。
着色器(Shader) 在GPU上执行的单独程序,用来处理顶点和执行光栅化任务。
GLSL

全称,(

openGL Shading Language,Opengl shader语言,一种语言规范,就像C++一样,它属于一种语言,shader在GPU上运行去处理Attributes、Uniforms等数据,那么shader就是通过GLSL编写。

Uniforms 一种变量类型,用来接收比较统一,不经常发生改变的数据,比如旋转矩阵、视频的颜色空间YUV数据;Uniforms既可以传给顶点着色器也可以传递给片元着色器里面。
顶点(Vertex) OpenGL顶点是4个分量(x, y, z,w),w为0时代表空间中的点,w为1时代表方向,x,y,z为三维空间坐标数据。
纹素(texture element) 全程纹理元素,纹素可以由图像范围来定义,它是计算机图形纹理空间中的基本单元。如同图像是由像素排列而成,纹理是由纹素排列表示的。
纹理数据(Texture Data) 简单理解可以视它为一张图片数据,模型往往会贴图,那么TextureData能够配合片元着色器决定纹理图像中的哪一个纹素赋予哪个顶点。
顶点着色器(Vertex Shader) 可以接收Uniforms、Attributes、Texture Data等数据,它用来处理顶点坐标,例如模型的三维空间坐标的缩放、移动等。(Texture Data通常直接装配到fragment shader处理)
图元(Primitives) 一维或二维的实体或表面(点,直线,多边形)。
光栅化(Rasterization) 将一个图元转变成一个二维图像的过程,简单来说就是把三维世界中的物体转换成屏幕上像素的过程。
图元装配(Primitive Assembly) 图元装配有两部分,第一步进行装配,装配即将顶点着色器输出的所有顶点作为输入,将所有的点装配成指定图元的形状,第二步实现光栅化,将图元转换为一组二维片元。
片元(Fragment ) 二维图像上每个点都包含了颜色、深度和纹理数据。将该点和相关信息叫做一个片元,在opengl流程中片元代表可以在屏幕上绘制的像素。
片元着色器(Fragment Shader) 可以接收由光栅化阶段生成的每个片元数据和纹理数据,用来计算出每个像素的最终颜色。

在了解了一些opengl名词的含义和opengl的流程后,可能大部分人对GLSL,着色器等概念有点困惑,可以先看看下面这个实际运用的例子。

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;//Shader创建
// -----------------------------------------------------
//GLSL编写的用于顶点着色器的shader
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";//用于片段着色器的shader,rgba格式
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(0.41f, 0.35f, 0.80f, 1.0f);\n"
"}\n\0";//顶点数据
float vertices[] = {0.5f,  0.5f, 0.0f,  0.5f, -0.5f, 0.0f,  -0.5f, -0.5f, 0.0f,  -0.5f,  0.5f, 0.0f,0.5f,  0.4f, 0.0f,-0.4f, -0.5f, 0.0f
};//索引数据
unsigned int indices[] = {    0, 2, 3,1, 4, 5
};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(800, 800, "OpenGL", NULL, NULL);glfwMakeContextCurrent(window);//glad初始化// -----------------------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}//着色器创建和链接// -----------------------------------------------------//顶点着色器创建unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);//片段着色器创建unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);//链接着色器unsigned int shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);//创建数据对象和绑定数据对象// -----------------------------------------------------//顶点数组对象:Vertex Array Object,VAO//顶点缓冲对象:Vertex Buffer Object,VBO//索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBOunsigned int VBO, VAO, EBO;//unsigned int VBO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);//绑定VAO数据对象glBindVertexArray(VAO);//复制顶点数组到缓冲中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//复制索引数组到缓冲中供OpenGL使用glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//解释顶点数据glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);//启用顶点属性glEnableVertexAttribArray(0);//解绑VAOglBindVertexArray(0);while (!glfwWindowShouldClose(window)){//按照rgba格式设置窗口背景颜色glClearColor(0.0f, 0.0f, 0.0f, 1.0f);//将当前设置的color写入启用的缓冲区glClear(GL_COLOR_BUFFER_BIT);//加载shdaerglUseProgram(shaderProgram);//绑定VAO数据对象glBindVertexArray(VAO); //获取索引,根据GL_TRIANGLES类型绘制图像glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);glfwSwapBuffers(window);glfwPollEvents();}return 0;
}

执行结果:

上文代码实现的流程,简单的将opengl渲染过程进行了介绍,窗口的创建、shader的编写、着色器的创建及链接、VAO,VBO,EBO创建及绘图。

Opengl功能分析

窗口的创建在本文开头已提过

shader的编写

shader是采用GLSL语言编写的,你可能发现了这个语法和C语言非常类似,而且是用字符串格式标注的,简单理解可以说是这样的,在此基础上多了一些例如:opengl的版本声明,数据location声明,及专用属性,in,out、vec3等等,同时shader实际上是运行在GPU里的,这意味着,我们无法编写shader的log,但是我们可以在shader编译的时候进行错误检查,具体方法会在以后补充。

着色器的创建及链接

着色器的创建及编译

前文提到的着色器,写成代码,还是比较容易理解,通过专用的语法glCreateShader及参数,我们便创建shader及获取其对应的id,通过对id操作glShaderSource,我们便将前文编写的shader和对应着色器的进行了绑定,仅仅绑定还不够,还需要glCompileShader来进行编译。

在opengl中,我们有且必有的两个着色器:顶点着色器和片段着色器(又名:片元着色器),两者都是通过相同的三步:glCreateShader、glShaderSource、glCompileShader进行创建。

着色器的链接

我们创建并编译好了顶点着色器和片段着色器,接下来我们还需要将它们合并链接到着色器程序对象,我们在opengl中进行的着色器处理,使用的就是着色器程序对象(需要注意的是,着色器的链接到着色器程序对象有严格顺序,上一个着色器的输出,对应下一个着色器的输入,数据对不上就会出错!),着色器程序对象的创建与着色器类似,通过glCreateProgram创建并获取id,根据id,我们使用glAttachShader将各个着色器对象附加上去,最后通过glLinkProgram完成整体链接。

总结来讲通过三步:glCreateProgram、glAttachShader、glLinkProgram

VAO,VBO,EBO/IBO创建及绘图

VAO,全称Vertex Array Object,顶点数组对象,VBO全称Vertex Buffer Object,顶点缓冲对象,EBO全称Element Buffer Object,IBO全称Index Buffer Object,都指索引数组对象

在opengl中,我们创建好着色器程序对象,紧接着就需要输入数据,数据的类型如何定义呢?

顶点坐标数据

opengl中的顶点坐标采用的是3D坐标,简单来讲就是空间直角坐标系,对应x,y,z三个方向,你可能发现代码中我们实际顶点数据为(x,y,z,w),w向量是用于透视法处理,作为基础知识,就不展开详谈,但以后我们会具体介绍,如代码中,vertices实际上就装载了我们要绘制的各个点坐标数据,z置为0,就是2D平面坐标,对应平面直角坐标系(需要注意的是,opengl中坐标范围为0-1,所有输入的坐标都需要进行归一化处理)

索引数组数据

代码中的indices数组,可能有人会对此有疑惑,这就是索引点,每一列数据如(0,1,2),对应着顶点坐标数据中的第一行,第二行,第三行数据,这样opengl绘制的时候就会去寻找这三行数据对应的顶点按照绘制图像进行链接,以此类推,我们绘制下一个图像可以反复使用,例如(0,1,3),openlg绘制就寻找第一行,第二行,第四行数据,索引数组在实际项目中经常使用。

VBO的含义及创建流程

我们定义的数组,必须绑定到VBO中才能输入到着色器中

创建流程:我们通过glBindBuffer来绑定对象类型及地址,然后使用glBufferData,将值输入到VBO中

VAO的含义及创建流程

在实际项目中,我们不可能仅仅绘制两个图像,数量过大的时候,每次绘制一个图像,我们都需要实现VBO的调用,绘制会非常复杂,而VAO可以保存VBO对象,比如VAO1对应VBO1,VAO2对应VBO2,在绘制的时候,我们调用VAO1,VAO2...我们就可以将对象的VBO数据都绘制出来

创建流程:通过glGenVertexArrays创建VAO1,再在需要绑定的VBO1之前,使用glBindVertexArray绑定VAO1,这样VAO1会对应上后文的VBO1,在绘制的时候,直接调用glBindVertexArray便调用了VBO1的内容

glVertexAttribPointer函数

glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据,该函数默认是关闭的,需要通过glEnableVertexAttribArray来开启,其参数对应关系如下:

glEnableVertexAttribArray(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0)

第一个参数

0

指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0

第二个参数

3

指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
第三个参数 GL_FLOAT 指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
第四个参数 GL_FALSE 定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。

第五个参数

3 *sizeof(float)

步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。
第六个参数 (void*)0 它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。

EBO/IBO的含义及创建流程

索引数组对象是实际开发中,我们经常使用的对象,顶点坐标数据可能有几万,或者几十万,这仅仅指的是各个独立点的个数,实际绘制的时候,我们可能需要连接第1、2、3这三个点,然后连接1、2、4这三个点,在代码中我们使用了glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);来告诉opengl我们的顶点数据三个一组,进行后文的glDrawElements中的GL_TRIANGLES以三角形绘制,这里有个问题,opengl仅知道使用三个点,以三角形绘制,那么当绘制有重复点的三角形的时候,它需要重复输入这些顶点数据,我们的顶点数据例如:(0.1f, 0.2f, 0.3f),每多输入一个顶点,就需要多一组顶点数据,在几十万个点的模型中,可能导致多上十几万个点的数据,这将导致内存空间膨胀,和计算过程复杂,所以我们需要引入索引数组对象,它能够告知opengl如何重复利用顶点数组对象里的数据。

其创建流程与VBO类似:通过glGenBuffers创建EBO及获取id,通过glBindBuffer和glBufferData,复制索引数组到缓冲中供OpenGL使用,使用glDrawElements,告知opengl以索引绘制图像。


总结:本文是对opengl整体流程的简单介绍,通过代码来描述,并具体介绍opengl的相关图形知识,便于初学者对opengl整体上有大概印象,后面的opengl知识会在此基础中进行深度和广度的扩展。

Opengl入门基础-基础知识相关推荐

  1. python基础一入门必备知识-Python数据分析入门必备基础知识

    今天,老师要带大家解数据分析的定义.核心思路.应用领域以及开发流程,向大家全方位展示数据分析入门必备基础知识,全都是干货哦!虽然看完本文,不能让大家立马变身为一名数据分析师,但是能让大家对数据分析有一 ...

  2. java入门基础重要知识必考考点

    java入门基础重要知识 一. Java概述 二. Java语言基础 1. 标识符的命名规则: 2. 关键字 3. 基本数据类型(基本.引用) 4. 基本数据类型转换 5. 定义变量的语法: 6. 运 ...

  3. 电脑基础操作_电脑基础操作常识入门必学知识

    很多人觉得自己都是电脑高手了,只是很多最基础的知识我们都知道吗? 下面就来看看,这些很简单的计算机入门知识,或许很多操作你都不是很懂哦! 1.重命名文件或者文件夹的方法: 1)选中--右键--重命名 ...

  4. 视频教程-快速入门Python基础教程_Python基础知识大全-Python

    快速入门Python基础教程_Python基础知识大全 十余年计算机技术领域从业经验,在中国电信.盛大游戏等多家五百强企业任职技术开发指导顾问,国内IT技术发展奠基人之一. 杨千锋 ¥99.00 立即 ...

  5. Lwip从入门到放弃之(一)---基础网络知识扫盲

    Lwip从入门到放弃之-基础网络知识扫盲(一) 由于工作中用到了有关Lwip的有关知识,本人作为一个网络通信协议的门外汉,打算系统的学习一下以太网通讯的有关知识.而Lwip作为一款开源的轻量级TCP/ ...

  6. 【网络入门】详解常用的基础网络知识(面试笔试常考内容)

    目录 1.概述 2.OSI七层模型和TCP/IP四层模型 3.数据进入TCP/IP协议栈时的封装过程 4.端口的概念 5.TCP建立连接时的三次握手 5.1.TCP头的构成 5.2.三次握手的流程说明 ...

  7. GPS 入门 1 —— 基础知识[转]

    GPS 入门 1 -- 基础知识 [转] (2008-10-11 18:14:57) <script> var $tag='gps,杂谈'; var $tag_code='b7179ced ...

  8. 小白入门SQL基础知识汇总

    小白入门SQL基础知识汇总 课程链接:link

  9. javaee入门基础重要知识

    ** javaee入门基础重要知识 ** 一.Java概述: 操作系统:pc端: Windows系统, Linux系统 Unix系统 Mac系统 4种 手机端: IOS 安卓 黑莓 鸿蒙 4种 框架: ...

  10. 炒股入门初学者基础知识讲解,如何掌握新手炒股入门基础知识

    对一些刚入股市的新手来讲,要学习的股票术语和一些图标实在是太多了,很多的新手在炒股的时候不知道炒股的技巧是什么,毕竟万事开头难,所以大家要多了解一些基础的知识.下面为大家介绍下QR技术分析社区中关于炒 ...

最新文章

  1. 《Cisco ASA设备使用指南》一2.8 Cisco ASA吉比特以太网模块
  2. jQuery常用方法(二)-事件
  3. android kernel控制台初始化过程
  4. 逆向工程核心原理学习笔记(十四):栈帧1
  5. 异步通信在生活中的例子_聊聊工作中经常遇到的“异步”,你掌握了多少
  6. SAP UI5 that.getView().bindElement(that.Context)
  7. oracle读取表空间物理文件中数据,shell脚本读取oracle数据库数据写入到文件中
  8. 设计模式三(工厂方法模式)学习笔记
  9. 力改变物体形状举例_对旋转问题的思考-在离心力确定的情况下,物体的旋转情况如何通过宇宙中的相对运动情况和质量分布确定?...
  10. html5调用静态库,浅谈C++ 动态库与静态库的调用
  11. hzwer模拟赛 感冒病毒
  12. AD库转allegro步骤
  13. xshell4 选中复制,右键粘贴
  14. FPGA零基础学习:数字电路中的组合逻辑
  15. 动态链接库(.dll) 动态导入库(.lib) 静态链接库(.lib)
  16. 功能测试与项目实战之测试计划(精辟干货)
  17. css样式 向下补白,CSS尺寸与补白
  18. clojure实现邮箱发送
  19. OpenGLES学习(一)图片显示
  20. 高效的磁力搜索引擎 -_高效的企业测试-结论(6/6)

热门文章

  1. 一周项目实战系列--SpringBoot实现微信点餐系统(1)
  2. CAD控件Aspose.CAD V17.4发布 | 支持DWF和DWG格式
  3. 数据库设计--企业人事管理系统(有关数据库的课程设计)
  4. 能破解百度网盘提取码,云盘万能钥匙宣布关闭!
  5. 初探腾讯云物联网开发平台loT
  6. 免费的微信编辑器插件调用
  7. SWOT分析法 (SWOT Analysis)
  8. phpeclipse
  9. uniapp 微信小程序 生成海报
  10. 盐城机电高等职业技术学校计算机专业,盐城机电高等职业技术学校