温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

OpenGL进阶(十七) - 深入理解OpenGL

发布时间:2020-06-30 05:34:11 来源:网络 阅读:5917 作者:拳四郎 栏目:开发技术

翻译自《 OpenGL Programming Guide》(8th) 第一章,标题为  Introduction to OpenGL。

红宝书第八版和第七版的最大的区别就是OpenGL的版本从OpenGL2.X变成了OpenGL4.X,渲染流水线也从固定流水变化为可编程流水线,shader满天飞...

好,进入正文。


此文主要内容如下:

1.介绍OpenGL的作用,告诉你OpenGL在计算机图形学中能做什么,不能做什么;

2.介绍OpenGL程序的常用结构;

3.介绍OpenGL渲染流水线的每个阶段。


什么是OpenGL

 OpenGL进阶(十七) - 深入理解OpenGL

       OpenGL是一组应用程接口(Application programming interface),即它是一个能够操纵计算机图形硬件的程序库。OpenGL4.3版包含了3500多个函数接口,用于创建图像,操作物体等,一切都是为了创建交互性三维计算机图形程序。

       OpenGL是按照流水线型设计的,和硬件无关,这让它能够运行于各种各样的图形硬件上。同时它也是软件无关的,可以运行于不同的操作系统,而只需操作系统只需提供一个让OpenGL运行的GUI库,同样的OpenGL也还会提供描述三维模型或者读取图片文件的方法,你需要做的是将一系列三维图元(比如点,线,三角形), 来组成三维物体。

      OpenGL并不是一个新事物,1.0版本在1994年6月友硅谷图形计算机系统开发出来,后续有很多OpenGL的其它版本,也有很多基于OpenGL开发的软件库用于更简单快速地进行应用程序开发,比如游戏开发,科学或者医学可视化系统地开发,抑或仅仅是为了显示图像。越是新版本的OpenGL,和原版本的OpenGL差异就越大。

     下面的一个列表简单地描述了OpenGL渲染一张图片需要用到的操作。

 ●  初始化用于显示图元的数据;

 ●  将输入的图元作为输入,在其上执行各种Shaders,计算出图元的位置,颜色和其他的渲染属性;

 ●  将图元的数学描述转化为用于显示在屏幕上的片段(fragment),这个过程称为栅格化;

 ●  最后执行fragment shader,处理上面得到的fragments,输出的是fragments的最终颜色和位置;

 ●  还可能会执行一个额外的fragment处理,进行混合,透明之类的操作。


        OpenGL是一个client - server系统,你写的应用程序被当作client,运行子啊图形硬件上的OpenGL实现作为服务端,在一些OpenGL实现中,client和server是运行在不同的机器上的,之间用网络连接,在这种情况下,client的命令通过网络由协议进行传输,服务器收到命令之后生成最终图像。


         译者注: OpenGL 内部是一个巨大的状态机,你所做的大部分是对这个状态机进行读取和设置工作。在对物体渲染的时候, OpenGL 会根据状态机中的当前状态来进行渲染,好比我们写OpenGL 程序调用 API 实际上是在写配置文件一样。 OpenGL 的状态很多, 涵盖光照,纹理,隐藏面消除,雾等等。

        

        OpenGL程序的第一印象

        OpenGL可以用来做很多事,那么一个也可能是非常复杂的,但是OpenGL应用程序的基本结构通常是类似的:

● 初始化状态机中的各种状态变量(译者注:状态变量型别是一些C数据类型的 typedef, 有 GLfloat, GLboolean, GLint, GLuint 等等);

● 指定要渲染的物体。

        在看代码之前,我们来介绍一些图形学名词,前面我们提到的渲染,指的是计算机从一些模型创建出一张图像的过程,OpenGL只是渲染系统的一种,还有其他的方式,比如ray-tracing,但是用ray-tracing的系统也可能用OpenGL来显示图像或者用于计算。

        我们的模型或者物体,用术语来说的话,它们是由一系列图元来确定的,包括点,线,三角形,它们都是由顶点确定的。

       另一个使用OpenGL至关重要的概念是Shader,它们是在图形硬件中执行的程序,最好的理解方式是将shader当成为GPU(Graphics

Processing Unit)特别编译的一些小程序.OpenGL包含了编译shader的工具。

     在OpenGL中有四个shader处理阶段可以使用,最普遍的是vertex shaders,用于处理顶点,还有fragment shaders,用于处理栅格化时候的片段,vertex和fragment shaders在每一个OpenGL程序中都会用到。

      最终生成的图像包含了显示在屏幕上的一系列像素。一个像素是显示器上的最小显示单位。像素的值存放在 frame buffer中,然后传输至显示设备,frame buffer是由图形硬件管理的一个存储区。

      下图显示了一个简单的OpenGL程序的输出,在窗口中渲染了两个三角形,源码如下:

OpenGL进阶(十七) - 深入理解OpenGL

/////////////////////////////////////////////////////////////////////// // // triangles.cpp // /////////////////////////////////////////////////////////////////////// #include <iostream> using namespace std; #include "vgl.h" #include "LoadShaders.h" enum VAO_IDs { Triangles, NumVAOs }; enum Buffer_IDs { ArrayBuffer, NumBuffers }; enum Attrib_IDs { vPosition = 0 }; GLuint VAOs[NumVAOs]; GLuint Buffers[NumBuffers]; const GLuint NumVertices = 6; //--------------------------------------------------------------------- // // init // void init(void) {     glGenVertexArrays(NumVAOs, VAOs);     glBindVertexArray(VAOs[Triangles]);     GLfloat vertices[NumVertices][2] = {         { -0.90, -0.90 }, // Triangle 1         { 0.85, -0.90 },         { -0.90, 0.85 },         { 0.90, -0.85 }, // Triangle 2         { 0.90, 0.90 },         { -0.85, 0.90 }     };     glGenBuffers(NumBuffers, Buffers);     glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]);     glBufferData(GL_ARRAY_BUFFER, sizeof(vertices),                  vertices, GL_STATIC_DRAW);     ShaderInfo shaders[] = {         { GL_VERTEX_SHADER, "triangles.vert" },         { GL_FRAGMENT_SHADER, "triangles.frag" },         { GL_NONE, NULL }     };     GLuint program = LoadShaders(shaders);     glUseProgram(program);     glVertexAttribPointer(vPosition, 2, GL_FLOAT,                           GL_FALSE, 0, BUFFER_OFFSET(0));     glEnableVertexAttribArray(vPosition); } //--------------------------------------------------------------------- // // display // void display( void) {     glClear(GL_COLOR_BUFFER_BIT);     glBindVertexArray(VAOs[Triangles]);     glDrawArrays(GL_TRIANGLES, 0, NumVertices);     glFlush(); }  //--------------------------------------------------------------------- // // main // int main(int argc, char** argv) {     glutInit(&argc, argv);     glutInitDisplayMode(GLUT_RGBA);     glutInitWindowSize(512, 512);     glutInitContextVersion(4, 3);     glutInitContextProfile(GLUT_CORE_PROFILE);     glutCreateWindow(argv[0]);     if (glewInit()) {         cerr << "Unable to initialize GLEW ... exiting" << endl;         exit(EXIT_FAILURE);     }     init();     glutDisplayFunc(display);     glutMainLoop(); } 

        也许代码看上去有点多,但你以后会发现,几乎你写的每个OpenGL的基础框架都是如此,我们使用了OpenGL以外的一些程序库来处理创建窗口,接受鼠标键盘输入等事件,我们还需要创建一些帮助函数和小的C++类来简化我们的例子。OpenGL是C语言的库,不过我们所有的例子都是用C++来写的,这些C++都是很简单的C++,实际上大部分C++是用来实现向量类和矩阵类的。

        简单地说一下上面的例子做了哪些事,我会在后面详细地解释,所以现在看不懂也不用着急。

● 在最开始,我们添加了相应的头文件,圣米格了一些全局变量和其他的有用的结构体;

● init()是用于初始化后面程序要用到的一些数据,这些大部分是后面渲染要到的定点信息,或者是用于纹理映射要用到的图像信息,在本例的init()中中,我们首先是指定了要渲染的两个三角形的位置信息,之后初始化要使用的shader,这次我们只用到了vertex shader和 fragment shader。LoadShaders函数就是用于加载给GPU执行的Shader。init()最后部分做的事称为 shader plumbing ,在这里将数据和shader中的变量进行绑定;

● display函数是程序中真正执行渲染的部分。函数中通过调用OpenGL的函数进行渲染,基本上所有的display函数会做相同的三个步骤:
1.用glClear() 函数清理窗口;

2.调用OpenGL相应的函数来渲染;

3.将渲染出的图像用于显示。

● 最后,mian函数中做了很多的事情 - 创建窗口,调用init,进入时间循环,这里还有一些gl开头的函数,但和其他的函数又有一些不同,简单地说,它们是用于在不同的操作系统中写OpenGL的工具库:GLUT和GLEW.


OpenGL语法(略)


OpenGL渲染流水线

      OpenGL实现了通常所说的渲染流水线。这个流水线分为一系列不同的阶段,能够将应用程序提供给OpenGL的数据转化为一幅最终的渲染图。下面的图为OpenGL4.3的流水线,这个流水线从发布至今已经进化了非常多。

OpenGL进阶(十七) - 深入理解OpenGL


        OpenGL在最初将我们提供的图形数据(顶点和图元)传入到一系列的shader 阶段:vertex shading,tesselation shading,然后是geometry shading,这些都在栅格化之前做完。rasterizer会将在裁剪区域的所有图元生成fragments,然后对每一个生成的fragment执行fragment shader。

        就如你所看到的,shaders在创建OpenGL应用程序中扮演了一个非常重要的角色。  你有权利去决定去使用哪个shader 阶段,在每个阶段中做哪些事情。并不是每一个阶段都是必须;实际上,只有vertex shaders和fragment shaders才是一定要用到的。Tessellation 和 geometry shaders只是可选项。

        现在,我们对每一个阶段都做一下更加深入地了解,这样你对整体就有更好的拿捏。我知道这些东西对你来说可能有点无法理解,但现在最好是硬着头皮看一下。你最后一定会明白理解一些理论会让你在OpenGL的路上走得更远。

        准备向OpenGL发送数据

        OpenGL要求将所有的数据都存储在buffer对象中,所谓buffer对象就是OpenGL server维护的内存块。将数据存放在buffer中有很多种方法,但是最常用的一种是用 glBufferData()函数,在初始化buffer之前,还有一些额外的工作要做。

        向OpenGL发送数据

        当我们初始化好buffers之后,我们可以用OpenGL的绘制函数来绘制几何图元,比如glDrawArray().

        OpenGL中的绘制通常意味着将顶点信息传送给OpenGL server。一个顶点意味着一个信息的集合,集合中有你想要的任何信息,几乎一定会包含顶点的位置信息,其他的值(比如法线)将会决定像素的最终值。

        Vertex Shading

        对于每一个需要渲染的顶点,vertex shader都会去处理和顶点相关的数据。根据在栅格化之前要激活哪些shader,vertex shader可能会非常简单,可能仅仅是将数据拷贝传递到下一个阶段 - 我们常称为是 pass-through shader。对于一个很复杂的vertex shader ,用于计算顶点在屏幕中的位置(通常会用到矩阵变换),计算顶点光照等等。

        一个复杂的应用程序可能会有多个vertex shader,但每次只能执行一个。

       Tesselation Shading

        当vertex shader将每个相关的顶点都处理过一遍之后,如果tessellation shader 阶段被激活,tessellation shader将会继续处理这些数据,tesselation用 patchs来描述一个物体,在这个阶段可以用一些相对简单的patch图形来细分模型来提供更好的外观, Tesselation Shading 阶段可以用两个shader来处理,一个用于处理patch 数据,一个用于生成最终形状。

        Geometry Shading 

         下一个shader阶段是geometry Shading,在这个阶段可以在栅格化之前处理单个的集合图元,比如添加一些图元。这个阶段也是可选的,但是非常有用。

        图元组装

         前面的所有阶段都是在针对顶点的信息操作,图元组装阶段将顶点组装成一组相关联的几何图元,为后面的裁剪和栅格化做准备。

        裁剪

        有些点会在视口(你打算渲染的窗口)的外面,所以需要将和顶点相关的图元进行一些处理,将不在视口中的图元裁剪掉,这个过程叫做裁剪,是在OpenGL中自动处理的。

        栅格化

         裁剪之后马上要做的就是栅格化,裁剪之后的图元都传递到 raseriser 中生成fragment。可以将fragment当作是“候选像素”,这些像素存储在framebuffer中。栅格化之后得到的fragment还是能够改变颜色,处理这些fregments在下面两个阶段,fragment shading和 per fragment 操作。


Fragment Shading

         可编程的最后一个阶段是fragment shading,在这里你可以控制fragments的颜色。在这个阶段,shader可以决定fragments的最终颜色(虽然在下一个阶段,per-fragment操作会最后一次改变颜色),fragment shaders非常地有用,在这里可以处理 texture mapping。如果一个fragment不应该被绘制,fragment shader也可以停止一个fragment的处理,这个过程称为 fragment discard。

         一个很好的思考顶点shader和片段shader的不同点的方法是:vertex shading决定图元在屏幕中处于什么位置,fragment shading用前面的信息来决定fragment的颜色。

         

          Per-Fragment Operations

          这里指的是一些额外的fragment 处理,这是对每个fragment处理的最后阶段。在这里fragments的可见性由深度测试和模板测试决定。


详解例子(略)


参考

《 OpenGL Programming Guide》(8th) 下载


向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI