1, OpenGL 编程模型
2, 管线流程(重点,每一个细节都要讲清楚)
渲染流水线,就是一系列有序的处理阶段的序列,用于把我们应用中的数据转化到OpenGL生成一个最终的图像。下图是OpenGL4.3使用的流水线。
上图中,蓝色的方块表示是可编程的shader阶段。
OpenGL从我们提供的几何数据(顶点和几何图元)出发,首先使用了一系列shader阶段来处理它:vertex shading,tessellation shading(它本身就包含了两种shaders),接着是geometry shading,然后再传递给光栅化程序(rasterizer);光栅化程序将会为每个在裁剪区域(clipping region)内部的图元生成fragments,然后再为每个fragment执行一个fragment shader。
如你所见,shaders真是无处不在啊!不是所有的阶段都是需要的。如上面所说,只有vertex和fragment shaders是我们必须实现的。Tessellation和geometry shaders都是可选的。
下面,我们对每个阶段进行更深入地解释。下面的内容难度系数五颗星(对新手。)!但是,请坚持看下去!它们很重要!
- Vertex Specification
准备顶点数组数据(vertex array data)
即进行顶点规格定义。应用将会建立一个有序的顶点列表,然后发送给OpenGL。这些顶点定义了图元的边界。图元是基本的绘制形状,如点、线、三角形。这些顶点列表是如何被组织成一个个图元的会在后面的阶段里进行处理。
这个阶段会处理一些顶点数组对象(Vertex Array Objects)和顶点缓存对象(Vertex Buffer Objects)。VAO定义了每个顶点包含的信息,而VBO则是顶点本身的数据所在。
一个顶点的数据就是一系列顶点属性(vertex attributes)。每一个attribute都是一个数据的集合,用于后面的阶段进行处理。虽然这些attributes定义了一个顶点,但没有要求说一个顶点的attributes集合中必须包含了位置和法线信息。实际上,attribute数据是完全任意的,它们仅仅是“数据”,在这个准备阶段不会有人在意你传递的到底是什么attribute,它们的真正含义会在顶点处理阶段进行解读。
OpenGL要求所有的数据都必须存储在缓存对象中(buffer objects)。缓存对象是OpenGL管理的一些内存空间。“想要让我办事,请先把你的东西放到我的地盘!”把数据放到这些缓存里有很多方法,但最常见的是使用glBufferData()来实现。当然这里面还有一些其他步骤,我们后面会讲到。
- 向OpenGL发送数据
在我们定义了顶点相关信息后,我们可以通过调用OpenGL的绘图操作来要求按一个个几何图元绘制到屏幕上。这些操作例如有glDrawArrays()。我们后面会讲到。这个绘制的过程意味着我们把顶点数据传递给OpenGL服务器。
- Vertex Processing
对于通过绘制命令绘制的每一个顶点,OpenGL将调用一个vertex shader来处理关于这个顶点的相关信息。Vertex shader接受从上个阶段传递来的attribute作为输入,然后把每一个输入顶点转换成一个输出顶点(这个关系是1对1的,不会多也不会少)。和输入的顶点信息不同,输出的顶点数据有一些必需的要求——vertex shader必须填充一个位置信息。
Vertex shaders的复杂性可以变化非常大,有的很简单,就是仅仅复制数据然后传递给下一个流水线阶段,我们称这种为pass-through shader;有的很复杂,会执行很多操作来计算顶点的屏幕位置(通常使用变换矩阵来完成,后面会讲到),还可能会进行光照计算来计算顶点的颜色,或者其他技术。
通常,一个应用会包含多个vertex shader,但同一时间只有一个会被激活(active)。
- Primitive Assembly
Primitive assembly就是把vertex shader输出的顶点数据集合在一起,并把它组合成一个图元的过程。用户渲染的图元的类型决定了这个过程是如何工作的。
这个过程的输出是一个有序的简单图元(点、线或者三角形)序列。
- Tessellation Shading
Tessellation,读 泰斯类什,可以翻译成曲面细分。在vertex shader处理了每一个顶点的相关信息后,如果tessellation shader阶段被激活的话,它就会继续处理这些数据。在后面我们会看到tessellation使用patchs来描述一个对象的形状,并且允许细化(tessellate)相对简单的patch集合,来增加几何图元的数量,提高模型的平滑度和真实度。Tessellation Shading阶段可以使用两个shaders来控制patch数据,以及中间一个固定函数的tessellator来生成最终的形状。
更多内容可以参见这篇文章(虽然是DirectX的。。。)
- Geometry Shading
这一阶段允许在光栅化之前处理单独的几何图元,包括创建新的图元。这一阶段同样是可选的,但是会很有用!后面会讲到。
Geometry shader会处理每一个输入的图元,然后返回0个或更多的输出图元。它的输入是primitive assembly的输出图元。因此如果我们按triangle strip看待一个图元,那么geometry shader看到的就会使一系列三角形。
然而也有一些输入的图元类型是专门为geometry shaders定义的。这些相邻的图元可以让GS了解关于相邻顶点的信息。
GS的输出可以是0个多更多的简单图元。GS可以移除图元,或者根据一个输入图元来输出更多的图元来细分(tessellate)它们。GS甚至可以改变图元的类型,比如把点图元编程三角形,把线图元变成点。
- Transform Feedback
Geometry shader或者primitive assembly的输出会被写入一系列的缓存对象。这被称为transform feedback模式。它允许我们通过vertex和geometry shaders来变换数据,然后再等待后续使用。
通过舍弃光栅化的结果,流水线可以在这步就停止了。这允许transform feedback成为渲染的唯一输出。
- 裁剪(Clipping)和剔除(Culling)
然后就进入图元裁剪和适当的剔除阶段了。
裁剪以为着,如果有图元处于视野的边界上,即一部分在内部一部分在外部,那么它就会被裁剪成一些小的图元。而且,vertex shader可以在空间内定义一些裁剪平面,这些裁剪平面又会引起额外的裁剪。
三角面的剔除同样在这一阶段完成。处于视野范围以外,或者在裁剪平面的边界内部的图元,都会被提出。
- 光栅化(Rasterization)
在裁剪完成后,更新后的图元就会被发送给光栅化程序去生成fragments。那么什么是fragment呢?一个fragment可以看成是一个“候选像素”。这类像素在帧缓存中的一块区域中。一个fragment仍可以被拒绝(reject),并且永远不会更新它的相关像素位置。
Wiki上把fragment成为是一个状态的集合,用于计算一个像素的最终数据。一个fragment的状态包含了它在屏幕空间的位置信息,样本覆盖(sample coverage,如果开启了multisampling的话),以及一些其他由vertex或者geometry shader输出的数据。这些数据集合是通过该fragment对应的顶点数据进行插值计算而得的。这个插值计算是由输出这些数据的shader定义的。
处理fragments是后面两个阶段的任务——fragment shading和per-fragment操作。
- Fragment Processing
最后一个我们可以编程控制颜色的阶段就是fragment shading。在这个阶段,我们使用一个shader来决定该fragment的最终颜色(其实下个阶段,per-fragment操作仍可以进行最后的颜色修改)和它的深度值(depth value)。在fragment shaders里我们可以进行非常强大的纹理映射的工作。如果一个fragment shader认为某个fragment不应该绘制出来,它还可以终结一个fragment的处理过程。这个过程称为fragment discard。
一个fragment shader会输出一个颜色列表、一个深度值和一个stencil值。Fragment shaders不可以为一个fragment设置stencil,但是它们可以控制颜色和深度值。
我们可以想来区分vertex shading(包括tessellation和geometry shading)和fragment shading:vertex shading决定了一个图元在屏幕上的位置,而fragment shading使用这些信息来决定该fragment的颜色。
- Per-Fragment操作
从fragment处理器输出的fragment数据会再通过一系列的步骤。
第一个步骤就是各种剔除检验(culling tests)。如果开启了stencil test,如果一个fragment没有通过检验它就会被剔除,而不会写入到帧缓存中;如果开启了depth test,如果一个fragment没有通过检验它就会被剔除,而不会写入到帧缓存中。只要没有通过任何一个检验,fragments都会被剔除,不会添加到帧缓存中。
如果一个fragment成功通过了所有的检测,它就会直接写入帧缓存中,更新它的像素颜色(也可能是深度值)。如果blending被开启了,该fragment的颜色会和当前的像素颜色进行混合去产生一个新的颜色,再写入帧缓存中。
最后,fragment数据被写入帧缓存中。Masking operation允许用户避免写入特定的值。写颜色、深度和stencil都可以被mask成on或者off;单独的颜色通道也可以。
3, 着色器编程
3, 光照
4, FBO/PBO/VBO/RBO
5, 基础概念
光栅化:
通俗点说,你告诉GL我要画条线,然后告诉他线两个端点的坐标是(0,0)和(0,10),那么GL自动脑补出中间10个点的坐标,这个过程就叫光册化,脑补的方法叫线性差值.复杂点,现在我要画个三角形,给他三个顶点的坐标,它会计算出里面所有像素的坐标.再复杂点,我不只给顶点坐标了,我告诉他(0,0)点是白色,(0,10)点是黑色,那么光册化就自动计算出中间10个点每个点的颜色,自动做过渡的效果.这个计算方法还是线性差值.继续通俗,线性差值也没啥,小学算数罢了,谁都会. 不懂就自己计算如上10个点的坐标,计算过程就是线性差值. 没错,就这么简单.着色器分两个部分,一个顶点,一个片断,中间就是光册化.也就是先顶点处理,计算出每个顶点的坐标,颜色,纹理坐标等等,也可以是任何其它千奇百怪的东西(寿命,温度,身高,婚否,饭量………). 然后经过光册化,给他们差值.最后片断阶段,你会获得每个像素的坐标,颜色,纹理,寿命,温度…… 初学阶段,你可以把片断(fragment)理解成像素,其实意思差别不大,等你GL入门了,自然就理解这俩词有啥区别了-
光栅化(Rasterize/rasteriztion)
这个词儿Adobe官方翻译成栅格化或者像素化。没错,就是把矢量图形转化成像素点儿的过程。我们屏幕上显示的画面都是由像素组成,而三维物体都是点线面构成的。要让点线面,变成能在屏幕上显示的像素,就需要Rasterize这个过程。就是从矢量的点线面的描述,变成像素的描述。如下图,这是一个放大了1200%的屏幕,前面是告诉计算机我有一个圆形,后面就是计算机把圆形转换成可以显示的像素点。这个过程就是Rasterize。
渲染管线(Pipeline)
这个翻译尤其不接地气,简直就是直译(pipe管子line线路)。Pipeline是输送管道的意思。其实是指三维渲染的过程中*显卡执行的、从几何体到最终渲染图像的、数据传输处理计算的过程。着色器(Shader)
这个翻译的挺好。画画的时候我们经常有这么一个过程:先打线稿,再上色。着色器就是用来做这个工作的。通常着色器分两种:1顶点着色器(vertex shader)这个是告诉电脑如何打线稿的——如何处理顶点、法线等的数据的小程序。2片面着色器(fragment shader)这个是告诉电脑如何上色的——如何处理光、阴影、遮挡、环境等等对物体表面的影响,最终生成一副图像的小程序。采用了这两种着色器小程序 的** 数据传输处理计算的渲染过程,称之为 可编程管线。世界矩阵/视图矩阵/投影矩阵
世界矩阵(World Matrix)、视图矩阵(View Matrix)以及投影矩阵(Projection Matirx);
世界矩阵确定一个统一的世界坐标,用于组织独立的物体形成一个完整的场景;
视图矩阵就是我们能看到的那部分场景,由虚拟摄像机负责拍摄;
投影矩阵就是3维物体的平面影射.把三维场景在一个二维的平面上显示.
- glfinish() / glflush() 的区别
OpenGL里会要用到的两个重要函数。glFlush和glFinish。
glFlush:将GL命令队列中的命令发送给显卡并清空命令队列,发送完立即返回;
glFinish:将GL命令队列中的命令发送给显卡并清空命令队列,显卡完成这些命令(也就是画完了)后返回。
- 四元素/欧拉角
- 纹理参数设置
- 数据类型/传递
- 纹理混合
- 光栅化处理的理解/渲染管线中的各个阶段的处理对应的opengl函数
- 着色器/程序 使用流程
- 裁剪/剔除
- 深度测试等各种测试
- 模板缓冲区
- 各个版本的新特性
- 纹理压缩
- 世界坐标系/相机坐标系/物体坐标系
世界坐标系是一个特殊的坐标系, 他建立了描述其他坐标系需要的参考框架,也就是说能够用世界坐标系描述其他坐标系的位置,而不能用更大的,外部的坐标系来描述世界坐标系
物体坐标系是和特定物体相关的坐标系, 每个物体都有他们独特的坐标系.
相机坐标系和观察者密切相关.
- mipmap
Mipmap是多级渐远纹理,也是目前应用最为广泛的纹理映射(map)技术之一。简单来说,就是实现 “实物(图片)看起来近大远小,近处清晰远处模糊”的效果。它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好
-
glDrawElements/ glDrawArrays 区别
比如画一个由2个3角形组成的正方形,左上角坐标是l,t,右下角坐标是r,b
使用glDrawArrays绘制时,画2个三角形,需要这样传:
(l,t),(r,t),(l,b)
(r,t),(r,b),(l,b)
而用glDrawElements画的话可以这样
float coord[4][2]={{l,t},{r,t},{r,b},{l,b}};
绘制时:
0,1,3
1,2,3
glDrawArrays传输或指定的数据是最终的真实数据,在绘制时效能更好
而glDrawElements指定的是真实数据的调用索引,在内存/显存占用上更节省 OpenGL 优化
参考:http://blog.csdn.net/chenchao868/article/details/4768716
http://www.360doc.com/content/15/1205/14/16593919_518090882.shtml
- OpenGL ES 调试