从0开始学OpenGL

首先引入两个问题

一、OpenGL是什么
二、OpenGL有什么用

一、OpenGL是什么?

OpenGL(Open Graphics Library 开放式图形库),是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(Application Programming Interface),它主要为我们定义了一系列用来操作图形和图像的函数。需要注意的是OpenGL本身并不是一个API,它仅仅是一个由Khronos组织制定并维护的规范(Specification)。

OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。至于内部具体每个函数是如何实现(Implement)的,将由OpenGL库的开发者自行决定(译注:这里开发者是指编写OpenGL库的人)。因为OpenGL规范并没有规定实现的细节,具体的OpenGL库允许使用不同的实现,只要其功能和结果与规范相匹配(亦即,作为用户不会感受到功能上的差异)。

实际的OpenGL库的开发者通常是显卡的生产商,具体的实现是由驱动开发商针对特定显卡实现的。你购买的显卡所支持的OpenGL版本都为这个系列的显卡专门开发的。当你使用Apple系统的时候,OpenGL库是由Apple自身维护的。在Linux下,有显卡生产商提供的OpenGL库,也有一些爱好者改编的版本。这也意味着任何时候OpenGL库表现的行为与规范规定的不一致时,基本都是库的开发者留下的bug。

  • GPU的硬件开发商需要提供满足OpenGL规范的实现,这些实现通常被称为“驱动”,它们负责将OpenGL定义的API命令翻译为GPU指令,通过调用各个厂商提供的驱动函数,就可以实现同样的功能。
  • 由于OpenGL的大多数实现都是由显卡厂商编写的,当产生一个bug时通常可以通过升级显卡驱动来解决。这些驱动会包括你的显卡能支持的最新版本的OpenGL,这也是为什么总是建议你偶尔更新一下显卡驱动。
    通俗的一句话来讲:

OpenGL是GPU功能的调用规范

适用范围 (PC端)

来看一下其他图形API

OpenGL ES (OpenGL for Embedded Systems) 是OpenGL的子集,针对手机、Pad和游戏主机等嵌入式设备而设计,去除了许多不必要和性能较低的API接口。

适用范围(嵌入式设备)

DirectX(Direct eXtension,简称DX) 是由微软公司创建的多媒体编程接口(并不是一个单纯的图形API),仅支持windows(不是跨平台框架),DirectX可以让以windows为平台的游戏或多媒体程序获得更高的执行效率,加强3D图形和声音效果,并提供设计人员一个共同的硬件驱动标准,让游戏开发者不必为每一品牌的硬件来写不同的驱动程序,也降低用户安装及设置硬件的复杂度。

适用范围(windows平台)

Metal 在 WWDC2014 上,Apple为游戏开发者推出了新的平台技术 Metal,该技术能够为 3D 图像提高 10 倍的渲染性能,并支持大家熟悉的游戏引擎及公司。

Metal是一种低层次的渲染应用程序编程接口,提供了软件所需的最低层,保证软件可以运行在不同的图形芯片上。Metal 提升了 A7 与 A8 处理器效能,让其性能完全发挥

适用范围(iOS平台)

二、OpenGL有什么用呢?

OpenGL是一个仅仅关注图形渲染的图像接口库,它所完成的工作也仅仅是计算机图形学的渲染部分。

简单来说 就是实现图形的底层渲染

  • 比如在游戏开放中,对于游戏场景/人物的渲染
  • 比如在音视频开发中,对于视频的解码后的数据渲染
  • 比如在地图引擎,对于地图数据的渲染
  • 比如在动画中,实现动画的绘制
  • 比如在视频处理中,对于视频添加滤镜等效果

OpenGL /OpenGL ES/ Metal 在任何项目中解决问题的本质
就是利用GPU芯片来⾼效渲染图形图像.

而图形API是开发者唯一接近GPU的方式。

三、OpenGL专业名词解析

对于刚刚接触OpenGL的同学来说,如果不能够很好的了解OpenGL基本的工作机制,那么即使能够在一个窗口中绘制一个三角形,也无法理清代码的运行原理,当遇到更加复杂的工程时,整个思绪就会更加混乱。要想理解OpenGL的工作机制,我们先对专业名词进行解析,以方便以后的学习,下面我们OpenGL的专业名词开始一一学习。

  • 状态机

状态机,不是指一台实际机器,而是指一个数学模型

状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。

先来解释什么是“状态”( State )。现实事物是有不同状态的,例如一个自动门,就有 open 和 closed 两种状态。我们通常所说的状态机是有限状态机,也就是被描述的事物的状态的数量是有限个,例如自动门的状态就是两个 open 和 closed 。

状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。例如,根据自动门的运行规则,我们可以抽象出下面这么一个图。

从0开始学OpenGL_第1张图片
自动门状态转换图

自动门有两个状态,open 和 closed ,closed 状态下,如果读取开门信号,那么状态就会切换为 open 。open 状态下如果读取关门信号,状态就会切换为 closed 。

状态机的全称是有限状态自动机,自动两个字也是包含重要含义的。给定一个状态机,同时给定它的当前状态以及输入,那么输出状态时可以明确的运算出来的。例如对于自动门,给定初始状态 closed ,给定输入“开门”,那么下一个状态时可以运算出来的。

下面来给出状态机的四大概念。

第一个是 State ,状态(现态:当前状态)。一个状态机至少要包含两个状态。例如上面自动门的例子,有 open 和 closed 两个状态。

第二个是 Event ,事件(也称条件,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。)。事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。

第三个是 Action ,动作。事件发生以后要执行动作(条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。)。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action 一般就对应一个函数。

第四个是 Transition ,变换(次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了)。也就是从一个状态变化为另一个状态。例如“开门过程”就是一个变换。

因此状态机具有以下特点:

  • 有记忆功能,能记住其当前状态
  • 可以接收输入,根据输入内容和当前状态,修改当前状态,并且可以有对应的输出
  • 当进入特殊状态(停机状态)时,不再接收输入,停止工作。

OpenGL就是一个状态机,它拥有非常多的状态变量,并且每个状态变量都有默认值,来描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。在程序中,我们可以设置它的各种状态,然后这些状态就会一直生效,直到我们再次修改它们。

假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。

比如我们使用glClearColor()函数设置了清空窗口的颜色以后,那么,之后清空窗口都会使用该函数设置的这个颜色,直到我们再次调用这个函数进行修改。

但是,我们可以对状态变量进行启用和禁用,比如,如果我们画一个2D三角形,就不需要深度测试,就可以通过glDisable(GL_DEPTH_TEST)关闭深度测试,它默认是关闭的,如果需要使用,可以通过glEnable(GL_DEPTH_TEST)启用该状态变量,针对该状态变量,还有一系列的值,包括一个默认值,一般深度测试的值都是一些测试函数。

获取当前状态变量的值:如果需要获取当前状态变量的值,可以使用OpenGL提供的一些获取value的函数,函数细节请参考:https://www.khronos.org/registry/OpenGL-Refpages/gl4/

我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。

  • 上下文(Context )

先来理解通俗的Context,大部分情况下把它理解成环境/背景,更好懂一些

context是environment的snapshot

上下文不是一个具体的东西,在不同的地方表示不同的含义,要感性理解。之所以翻译成上下文,和读一篇文章很相似,如下:

...
...
小明看了看大明,哭了
...

问:小明为什么哭?
显然我们是不知道的,一篇文章给你摘录某一句话,无前言,无后语,你读不懂。因为要结合语境,一段话表达了什么意思,要通过上下文来推断。

那么子程序之于程序,进程之于操作系统,甚至app的一屏之于app,都是一个道理。

程序执行了部分到达子程序,子程序要获得结果,要用到程序之前的一些结果(包括但不限于外部变量值,外部对象等等);

app点击一个按钮进入一个新的界面,也要保存你是在哪个界面跳过来的等等信息,以便你点击返回的时候能正确跳回,如果不存肯定就无法正确跳回了。

这些都是上下文的典型例子,所谓上下文,就是与当前操作相关的前一步状态和下一步状态!在很多情况下,要执行某一操作,需要做很多的准备工作!而这一操作前必须满足的状态,就是当前操作的上文。由此及彼,当前操作之后要达到的状态就是当前操作的下文!

在C或者C++中,context一般就是一个结构体,用来存储一些关键信息,比如切换上下文时,要保存切换之前的状态和数据,这需要一个结构体来承担,然后将contex中的状态和数据重新赋值为新的。

对于OpenGL,在应用程序调用任何OpenGL的指令之前,都需要首先创建(指定)⼀个OpenGL的 上下文,它会将各种状态信息保存在上下文中,包括顶点信息、纹理信息、编译好的着色器信息等等,之后的绘制工作就要基于这些信息来完成,是OpenGL执行的基础,当一个上下文被销毁的时候,它所对应的OpenGL渲染工作也将结束。

OpenGL的函数不管在哪个语言中,都是类似C语言⼀样的⾯向过程的函数,本质上都是对OpenGL上下文这个庞大的状态机中的某个状态或者对象进行操作,当然你得首先把这个对象设置为当前对象。因此,通过对 OpenGL指令的封装,是可以将OpenGL的相关调用封装成为一个面向对象的图形API的。

OpenGL采用了客户端-服务器模式,很抽象,我们可以认为每一个硬件GPU是个服务器,每一个绘制上下文对应于申请的一个客户端,一个客户端维护着一套状态机,如果两个窗口分别对应两个不同的绘制上下文,则两个窗口彼此状态独立。申请绘制上下文,意味着系统资源的申请,每个绘制上下文还是需要不少资源的。

切换上下文往往会产⽣较⼤的开销,但是不同的绘制模块,可能需要使⽤完全独⽴的状态管理。因此,可以在应⽤程序中分别创建多个不同的上下文,在不同线程中使用不同的上下文,上下文之间共享纹理、缓冲区等资源。这样的⽅案,会比反复切换上下文,或者⼤量修改渲染状态,更加合理高效的(先创建上下文A,再以A为输入,创建上下文B,则B可访问在A上下文下创建的纹理资源。纹理、shader、Buffer等资源是可以共享的,但Frame Buffer Object(FBO)、Vertex Array Object(VAO)等容器对象不可共享,但可将共享的纹理和VBO绑定到各自上下文的容器对象上。)。

  • 渲染

将图形/图像数据转换成2D空间图像操作叫做渲染(Rendering),当这个术语作为动词使用时,指的是计算机创建三维图像时所经历的过程。它也可以作为名词使用,指的仅仅是最终的图像作品。

  • 管线/图形渲染管线(Graphics Pipeline)

在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线)管理的。实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

而这样的操作可以理解为渲染流水线,每个任务类似流水线般执行,任务之间有先后顺序。管线是一个抽象的概念,之所以称为管线是因为显卡在处理数据时按照一个固定的顺序执行的,而且严格按照这个顺序执行,这个顺序是不可打破的。

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

图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)

  • 固定管线(存储着色器)

在早期的OpenGL版本,封装了很多种着色器程序块内置了一段包含光照、坐标转换、裁剪等等诸多功能的固定Shader程序,来帮助开发者完成图形的渲染,开发者只需要传入相应的参数,就能快速的完成图形的渲染。

可以简单理解为渲染图像的这个过程,我们只能通过调用GLShaderManager类的固定管线效果实现我们一系列的着色器处理。

  • 可编程管线

随着OpenGL的使用场景越来越丰富,固定管线(存储着色器)已经无法完成每一个业务,所以将部分开发成为可编程的,从OpenGL3.3开始,OpenGL的渲染方式就从固定功能管线转向了可编程功能管线,而可编程的意思,就是可以通过许许多多的着色器来实现多种多样的效果,这些效果要比固定的效果好的多。开发者可以使用用GLSL自行编写着色器程序

  • 着色器程序(Shader)

  • 一小段运行在图形卡(显卡)上的程序。
  • 运行在GPU上的小程序

OpenGL在处理shader时,和其他编译器一样。通过编译、链接等步骤,生成了着⾊器程序(glProgram),着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。把所有的着色器对象链接为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序,我们就能使用这个着色器程序对象了。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。

当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种各自独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。

着⾊器程序同时包含了顶点着色器和⽚段着色器器的运算逻辑。在OpenGL进行绘制的时候,首先由顶点着色器对传⼊的顶点数据进行运算。再通过图元装配,将所有顶点装配成指定图元的形状,传递给几何着色器。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。几何着色器的输出会被传入光栅化阶段,把图元映射为最终屏幕上相应的像素,生成供片段着色器使用的片段。⽚段着⾊器会对栅格化数据中的每一个像素进行运算,并决定像素的颜⾊。(具体图形渲染流程见)

常见的着色器主要有:

  • 顶点着色器(Vertex Shader)
  • 片段/片元着色器(Fragment Shader)/ 像素着色器(Pixel Shader)
  • 几何着色器(Geometry Shader)
  • 曲面细分着色器(Tessellation Shader)
    着色器就是控制GPU来进行计算的
    直到OpenGLES 3.0,依然只支持了顶点着色器和片元着色器这两个最基础的着色器可编程
  • GLSL(OpenGL Shading Language)

OpenGL着⾊器语言(OpenGL Shading Language)是⽤来在OpenGL中着色编程的语言,也即开发人员写的短小的⾃定义程序,他们是在图形卡(显卡)的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性。⽐如:视图转换、投影转换等。GLSL的着⾊器代码分成2个部分: Vertex Shader(顶点着色器)和Fragment(⽚元着色器)

  • 顶点数组(Vertex Array)和 顶点缓冲区(Vertex Buffer)

画图一般是先画好图像的骨架,然后再往骨架里填充颜色。顶点数据就类似于图像的骨架,和现实中不同的是,OpenGL中的图像都是由图元组成,有3种类型的图元:点、线、三角形。任何图形都是由这3种图元组成。

那这些定点数据最终存储在哪里呢?开发者可以选择设定函数指针,在调用绘制方法的时候,直接由内存传入顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组。而性能更高的做法是,提前分配一块显存,将顶点数据预先传入到显存当中,这部分的显存,就被称为顶点缓冲区

顶点指的是我们在绘制一个图形时,它的顶点位置数据,而这个数据可以直接存储在数组中或者将其缓存到GPU内存中。

顶点数组指定每个顶点的属性,是保存应用程序地址空间的缓冲区。他们作为顶点缓冲对象的基础,提供指定顶点属性数据的一个高效、灵活的手段

顶点数组与顶点缓冲区的区别在于顶点数据的存储方式不同
顶点数据存储在内存中->顶点数组
顶点数据存储在GPU提前分配的显存中->顶点缓冲区

  • 顶点着色器( Vertex Shader)

顶点着⾊器是OpenGL中⽤于计算顶点属性的程序。顶点着色器是逐顶点运算的程序,也就是说每个顶点数据都会执⾏⼀次顶点着色器,执行顺序是按照存储在顶点数组的顺序依次处理(一般是逆时针)。当然这是并行的,并且顶点着⾊器运算过程中无法访问其他顶点的数据。

顶点着色器对顶点实现了一种通用的可编程方法,一般用来处理图形每个顶点的变换(旋转、平移、投影等)

一般来说典型的需要计算的顶点属性主要包括顶点坐标变换、逐顶点光照运算等等。顶点坐标由自身坐标系转换到归一化坐标系的运算,就是在这里发⽣的。

  • 光栅化(Rasterization)

把物体的数学描述以及与物体相关的颜色信息转换为屏幕上⽤于对应位置的像素及⽤于填充像素的颜色,这个过程称为光栅化。

具体描述

  • 是把顶点数据转换为⽚元的过程,
  • 是一种将几何图元变为二维图像的过程
  • 是一个将模拟信号转化为离散信号的过程

作用:

具有将图转化为一个个栅格组成的图象的作⽤,特点是每个元素对应帧缓冲区中的一像素。

执行的操作:

第一部分工作:确定图形的像素范围(决定窗口坐标中的哪些整型栅格区域被基本图元占用);
第二部分⼯作: 将颜色附着上去(分配一个颜色值和一个深度值到各个区域)。

光栅化过程产生的是片元。

  • 片元着色器( Fragment Shader)

⽚段着⾊器是OpenGL中⽤于计算片段(像素)颜色的程序。⽚段着色器是逐像素运算的程序,也就是说每个像素都会执⾏一次片段着色器,当然也是并⾏的.

片元着色器主要是对光栅化处理后生成的片元逐个进行处理(并行)。接收顶点着色器输出的值,需要传入的数据,以及它经过变换矩阵后输出值存储位置。一般⽤来处理图形中每个像素点颜色计算和填充。

  • Uniform

Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。

uniform对于设置一个在渲染迭代中会改变的属性是一个非常有用的工具,它也是一个在程序和着色器间数据交互的很好工具

  • 纹理(Texture)

我们通常说的纹理,指的是一张二维的图片,把它像贴纸一样贴在什么东西上面,让那个东西看起来像我们贴纸所要表现的东西那样,用于模拟物体的细节。

我们可以为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像。但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。这将会产生很多额外开销,因为每个模型都会需求更多的顶点,每个顶点又需求一个颜色属性。使用纹理,我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点。

  • 混合(Blending)

Blend 混合是将源色和目标色以某种方式混合生成特效的技术。混合常用来绘制透明或半透明的物体。

在混合中起关键作用的α值实际上是将源色和目标色按给定比率进行混合,以达到不同程度的透明。α值为0则完全透明,α值为1则完全不透明。混合操作只能在RGBA模式下进行,颜色索引模式下无法指定α值。物体的绘制顺序会影响到OpenGL的混合处理。

在测试阶段之后,如果像素依然没有被剔除,那么像素的颜色将会和帧缓冲区中颜色附着上的颜色进⾏混合,混合的算法可以通过OpenGL的函数进行指定。但是OpenGL提供的混合算法是有限的,如果需要更加复杂的混合算法,一般可以通过像素着色器进行实现,当然性能会⽐原生的混合算法差⼀些。

  • 变换矩阵(Transformation)

例如图形想发生平移、缩放、旋转变换,就需要使用变换矩阵。

  • 投影矩阵(Projection)

⽤于将3D坐标转换为⼆维屏幕坐标,实际线条也将在二维坐标下进行绘制。

最后大致说一下过程 此处有个基本印象即可 以后还会学

过程:顶点—> 图元 —> 片元—> 像素

顶点—> 图元:

几何顶点被组合为图元(点,线段或多边形),然后图元被合成片元,最后片元被转换为帧缓存中的象素数据。

图元 —> 片元:

图元被分几步转换为片元:图元被适当的裁剪,颜色和纹理数据也相应作出必要的调整,相关的坐标被转换为窗口坐标。最后,光栅化将裁剪好的图元转换为片元。
1) 裁剪
2) 转换到窗口坐标
3) 光栅化

光栅化是将一个图元转变为一个二维图象(其实只是布满平面,没有真正的替换帧缓存区)的过程。二维图象上每个点都包含了颜色、深度和纹理数据。将该点和相关信息叫做一个 片元(fragment)。(这就是片元和像素之间的关键区别,虽然两者的直观印象都是的像素,但是片元比像素多了许多信息,在光栅化中纹理映射之后图元信息转化为了像素)

片元—> 像素:

1)象素所有权(ownership)检测
2)裁剪检测
3)Alpha检测
4)模版检测
5)深度检测
6)融合
7)抖动
8)逻辑操作

从0开始学OpenGL_第2张图片
裁剪投影光栅化

你可能感兴趣的:(从0开始学OpenGL)