博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OpenGL知识点梳理1
阅读量:6637 次
发布时间:2019-06-25

本文共 7349 字,大约阅读时间需要 24 分钟。

一、环境配置和头文件

 

1.每个OpenGL程序中都需要先进行GL库文件的导入,在项目属性的VC++目录中添加OpenGL\include和OpenGL\Libs。在链接器的输入中添加附加依赖项opengl32.lib与glfw3.lib。

倘若程序中引用了GLM库等,还需在C/C++的输入中的附加包含目录中添加文件路径。并将glad.c文件加入到项目中。

 

2.使用#include<stb_image.h>来进行图片加载时需要在前面加上#define STB_IMAGE_IMPLEMENTATION,否则会出现编译错误。

#define STB_IMAGE_IMPLEMENTATION

#include<stb_image.h>

 

二、视口与渲染循环

1.视口

通过视口(viewport)告诉OpenGL渲染窗口的尺寸大小,这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。

 

当用户改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用。

glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。

//---------------------------------------------------------------------------------------------------------------------------------------

void framebuffer_size_callback(GLFWwindow* window, int width, int height)  

{

  glViewport(0, 0, width, height);  

}

 

我们还需要注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:

glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

当窗口被第一次显示的时候framebuffer_size_callback也会被调用。因为注册时已经传入window对象,所以window的尺寸一并传入回调函数中,而第一次

显示时回调函数会调用,所以渲染的尺寸也就直接确定了。

 

2.渲染循环

我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),它能在我们让GLFW退出前一直保持运行。

//----------------------------------------------------------------------------------------------------------

 while(!glfwWindowShouldClose(window))

{

  glfwSwapBuffers(window);

  glfwPollEvents();  //检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)

}

 双缓冲(Double Buffer)

应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。

 

三、输入与颜色清屏

1.输入

在GLFW中添加一些控制,并将所有控制都写在processInput函数中方便管理。

void processInput(GLFWwindow *window)

{

  if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)    //glfwGetKey函数将会返回这个按键是否正在被按下

    glfwSetWindowShouldClose(window, true);

}

 

2.颜色清屏

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);      //设置清空屏幕所用的颜色

glClear(GL_COLOR_BUFFER_BIT);      //清除颜色缓冲,整个颜色缓冲都会被填充为glClearColor里所设置的颜色

 

可以使用 | 运算符组合不同的缓冲标志位,表明需要清除的缓冲,例如glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)表示要清除颜色缓冲以及深度缓冲。

glClearColor函数是一个状态设置函数,而glClear函数则是一个状态使用的函数,它使用了当前的状态来获取应该清除为的颜色。

 

四、图形渲染管线(graphic pipeline)

1.介绍

提供3D坐标转为2D坐标的处理过程 。图形渲染管线可以被划分为两个主要部分:第一部分把3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制

 

2.处理流程

 

顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。

图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并将所有的点装配成指定图元的形状。

图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。

几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。(OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据

片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

 

五、VBO(vertex buffer object)

1.介绍

VBO将顶点信息放到GPU中,GPU在渲染时去缓存中取数据,二者中间的桥梁是GL-Context。GL-Context整个程序一般只有一个,所以如果一个渲染流程里有两份不同的绘制代码,GL-context就负责在他们之间进行切换。这也是为什么要在渲染过程中,在每份绘制代码之中会有glBindbuffer、glEnableVertexAttribArray、glVertexAttribPointer。优化的方法就是把这些都放到初始化时候完成。VAO记录该次绘制所需要的所有VBO所需信息,把它保存到VAO特定位置,绘制的时候直接在这个位置取信息绘制。

 

2.流程

unsigned int VBO;        //缓冲ID

glGenBuffers(1, &VBO);    //创建缓存对象并且返回缓存对象的标示符。它需要2个参数:第一个为需要创建的缓存数量,第二个为用于存储单一ID或多个ID的变量或数组的地址。

     ↓

 glBindBuffer(GL_ARRAY_BUFFER, VBO);

//target告诉VBO该缓存对象将保存顶点数组数据还是索引数组数据:GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY。任何顶点属性,如顶点坐标、纹理坐标、法线与颜色分量数组都使用GL_ARRAY_BUFFER。用于glDraw[Range]Elements()的索引数据需要使用GL_ELEMENT_ARRAY绑定。

     ↓

 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

 它的第一个参数是目标缓冲的类型。第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。第四个参数指定了我们希望显卡如何管理给定的数据。

三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW。如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,这样就能确保显卡把数据放在能够高速写入的内存部分。

 

到此顶点数据已经储存在显卡的内存中了,用VBO这个顶点缓冲对象管理。

总结:VBO只是把数据传到GPU的一种方式,切勿被其对象等概念干扰。后续操作也是需要对绑定的类型操作,从而间接使用VBO。并非直接使用VBO对象。

 

六、VAO(vertex array object)

1.介绍

首先,VAO不是Buffer-Object,所以不用作存储数据;其次,它针对“顶点”而言,也就是说它跟“顶点的绘制”息息相关 和 顶点数组没有任何关系,VAO保存的是VBO的绘制状态。VAO将其对应的VBO的状态及信息直接存储在GPU的内存中。VAO中并没有VBO的相关属性数据,只是存储了VBO的当前状态及索引信息,通过VAO就能快速访问到VBO的状态及VBO的数据。

VAO记录的是一次绘制中所需要的信息,这包括“数据在哪里glBindBuffer”、“数据的格式是怎么样的glVertexAttribPointer”、shader-attribute的location的启用glEnableVertexAttribArray。VAO可以理解为一个状态容器,记录VBO的状态。

使用VAO后GPU在绘制时就可以直接找到其记录的VBO的状态及对应的VBO的数据能实现快速绘制并且避免了数据的频繁操作从而提高性能。

 

2.使用流程

产生VAO

 ↓

绑定VAO

   ↓

绑定VBO对应的VBO handle(VBO 数据句柄)(Bind Vertex Buffer Object)

   ↓

启用shader中对应的属性信息,glEnableVertexAttribArray()以顶点属性位置值作为参数启用顶点属性,顶点属性默认是禁用的。因此需要打开这些(CPU到GPU)通信端口。

   ↓

指定了渲染时的顶点属性数组的数据格式和位置使用glVertexAttribPointer函数指定

   ↓

绑定下一个VBO

   ↓

VAO保存结束

 

示例:

// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: .. //

1. 绑定VAO

glBindVertexArray(VAO);

// 2. 把顶点数组复制到缓冲中供OpenGL使用

glBindBuffer(GL_ARRAY_BUFFER, VBO);

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 3. 设置顶点属性指针

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: .. //

4. 绘制物体,渲染时使用VAO渲染

glUseProgram(shaderProgram);

glBindVertexArray(VAO);

someOpenGLFunctionThatDrawsOurTriangle();

 

七、EBO/IBO(索引缓冲对象)

1.介绍

和顶点缓冲对象一样,EBO也是一个缓冲,它专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点。用于索引绘制。

 

2.使用流程

除顶点数组外再定义一个索引数组,存放需要绘制的顶点的下标

float vertices[] = {                        unsigned int indices[] = { // 注意索引从0开始!

  0.5f, 0.5f, 0.0f, // 右上角                         0, 1, 3, // 第一个三角形

  0.5f, -0.5f, 0.0f, // 右下角                          1, 2, 3 // 第二个三角形

  -0.5f, -0.5f, 0.0f, // 左下角                     };

  -0.5f, 0.5f, 0.0f // 左上角

};

    ↓

创建索引缓冲对象

unsigned int EBO;

glGenBuffers(1, &EBO);

    ↓

与VBO类似,我们先绑定EBO然后用glBufferData把索引复制到缓冲里。同样,和VBO类似,我们会把这些函数调用放在绑定和解绑函数调用之间,只不过这次我们把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    ↓

 要注意的是,传递了GL_ELEMENT_ARRAY_BUFFER当作缓冲目标。最后一件要做的事是用glDrawElements来替换glDrawArrays函数,来指明从索引缓冲渲染。使用glDrawElements时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)

glDrawElements函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取索引。这意味着必须在每次要用索引渲染一个物体时就要绑定相应的EBO,过程很繁琐。不过VAO同样可以保存EBO的绑定状态。VAO绑定时,正在绑定的EBO会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。

 

3.示例

最后的初始化和绘制代码现在看起来像这样:

 // ..:: 初始化代码 :: .. //

1.绑定顶点数组对象

glBindVertexArray(VAO);

// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用

glBindBuffer(GL_ARRAY_BUFFER, VBO);

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// 4. 设定顶点属性指针

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..

glUseProgram(shaderProgram);

glBindVertexArray(VAO);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

glBindVertexArray(0);  //通常此行代码不需要放在渲染循环中,因为没必要每次循环都绑定再解绑。此处只为规范化,所以加上此行代码

 

转载于:https://www.cnblogs.com/jingrui/p/9598299.html

你可能感兴趣的文章
RDS Best Practices — Fast and Stable Migration to RDS
查看>>
C# 进程间通信(共享内存)
查看>>
jvm(13)-线程安全与锁优化(转)
查看>>
thinkphp学习笔记4—眼花缭乱的配置
查看>>
ibwebrtc-audio-processing-devel
查看>>
密码复杂度检查的正则表达式
查看>>
设置 CxImage 的 Alpha 透明度
查看>>
“先体检,再治病“ 迪普科技为某金融企业量身定制安全方案
查看>>
UIT创新科:大力护盘自主可控高效存储
查看>>
为什么数据中心需要使用VMware NSX?
查看>>
hashCode()方法的性能优化
查看>>
Spark高级数据分析· 3推荐引擎
查看>>
Docker集群轻松部署Apache Storm
查看>>
ReportEngineService
查看>>
疯狂Java程序员的基本修养
查看>>
关于OSPF NSSA区域metric计算细节
查看>>
MS12 020补丁下载for sever 2003_x86_chs
查看>>
css的postion属性
查看>>
HSRP自己做的实验
查看>>
Installing Oracle JDK for linux
查看>>