opengl流程
在OpenGL中,任何事物都在3D空间中,而屏幕和窗口确实2D像素组,这导致OpenGL大部分的工作都是关于把3D坐标转变为适应屏幕的2D像素;3D转成2D坐标的处理过程是OpenGl的图形渲染管线(Graphic Pipeline,大多译为管线,实际上指的是一堆原始图形数据途径一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的;图形渲染管线可以划分为两个主要部分,第一部分把你3D的坐标转换成2D坐标;第二部分是把2D坐标转变成实际的有颜色的像素;
1 | 2D坐标和像素不同,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到屏幕/窗口分辨率的限制 |
openGL首先接收用户提供的几何数据(顶点和几何图元),并且将它输入到一系列着色器阶段中进行处理,包括:顶点着色(vertex shader ),细分着色,以及最后的几何着色,然后被送入光栅化单元(resterizer)光栅化单元负责对所有剪切区域(clipping region)内的图元生成片元数据,然后对每个生成的片元都执行一个片元着色器;
下面有个更清晰容易理解的图~
顶点着色器(Vertex Shader),它把一个单独的顶点(Vertex)作为输入,顶点着色器主要的目的是把3D坐标转为另一种3D坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理;如:顶点变换,法向量变化和单位化,生成相应的纹理坐标,纹理坐标变换,雾坐标,光照计算; 当需要计算顶点在屏幕上的位置,就会涉及到矩阵变换
图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(r如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定的图形,如上图就是一个三角形;
几何着色器(Geometry Shader)几何图形把图元形式的一系列顶点的集合作为输入,它可以通过产生新的顶点构造出新的图元来生成其他形状;这个着色阶段是可选
之后就会传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment).在这之前还会线做下裁切(Cliping)会丢弃超出视图以外的所有像素,用来提升效率;
片段着色器的主要目的是计算一个像素的最终颜色,这也是OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照,阴影,光的颜色等等),这些数据可以用来计算最终像素的颜色;片元着色器的作用有:雾;提取纹理单元,用于纹理贴图;颜色混合,等
最后的测试和混合简单而言是根据Zorder和Alpha值进行的图形合成过程;
先有个大概流程的印像,接下来我们会通过一些例子慢慢将流程梳理清楚;先从opengl的基本库开始讲起;
Opengl相关库
opengl基本函数库用来描述图元(graphics output promitive),属性(attribute),几何变换(geometric transformation),观察变换(viewing transform)和进行其他的操作;由于OpenGl被设计成硬件无关型,因此输入和输出函数等许多操作均不包括在其基础库中;而是放在OpenGl开发的辅助库中;
Opengl基本库(也称OpenGL核心库):常见有gl开头的这些函数的写法glBegin, glClear, glCopyPixels, glPolygonMode;变量中GL_2D,GL_RGB,GL_CCW;GL_POLYGON,GL_AMBIENT_AND_DIFFUSE;
相关库:OpenGL实用函数(openGL Utility,GLU)提供了一些例程,可以设置观察和投影矩阵,利用线条和多边形近似法来描述复杂对象,使用线性近似法显示二次曲线和样条曲线,处理表面绘制操作,以及完成其他复杂任务。每一个OpenGL实现中都包括了GLU库,所有的函数名都用glu开头。窗口显示系统,opengl实用函数工具包(OpenGL Utility Toolkit,GLUT)提供了与任意屏幕窗口系统进行交互的函数库。GLUT库函数以glut为前缀,该库也包含了描述与绘制二次和样条曲线及曲面的方法;
说这么多,先来个demo~
1 |
|
显示效果如下:
通过这个demo我们大致了解到opengl的大致使用步骤
- 初始化物体渲染所对应的状态。
- 设置需要渲染的物体
接着demo下面我们引入几个概念:
MVP
demo中的init函数中glMatrixMode(GL_PROJECTION);//设置投影矩阵,这个涉及到了投影矩阵,对于opengl常见的就是要进行矩阵变换;我们这里稍微带下
- 红色坐标系位模型坐标系,指定了模型各个点的位置,每个点是(x,y,z)组成的数组;由于后面的矩阵都是4*4,所以在后面补上1;
- 模型变换,将模型坐标转化成世界坐标,把物体在世界坐标系的位置拆分成旋转,平移,缩放的表达式;
- 视图变换:指定一个相机的位置和角度,然后去观察世界坐标系下的物体;
- 投影变换:把前面三位空间的坐标系投影到二维屏幕的坐标系,除了屏幕的横纵坐标,另外一个维度就是垂直屏幕方向的坐标,就是之后可以写入深度缓冲区的值。将三维坐标转换到二维屏幕,主要分为正交投影和透视投影,都是用相似三角形算比例;
- 视口变换,这里只是一个非常简单的XoY平面上的缩放;它决定了最终渲染到平面的哪一块,所以用之前的缩放同样的处理就能得到相应矩阵;这里相当于我们经常在ui中遇到的缩放因子;
于是乎:
1 | 变换后的坐标=视口矩阵 * 投影矩阵(P) * 视图矩阵(V) * 模型矩阵(M) * 模型点坐标 |
对应的VR里面左右眼的矩阵模型公式:
1 | leftEyeMvp = projectMatrix*projRot(投影矩阵) * mHeadOrient*mleftEye(视口矩阵) * mode(模型点) |
通过这个矩阵我们就能从三维空间映射到我们的屏幕上,由于左右眼的视口矩阵不同产生的视觉差,让我们又产生了3d的效果;
这里有两个工具可以更好的理解这一概念:
MODELVIEW
http://www.songho.ca/opengl/files/matrixModelView.zip
PROJECTION
http://www.songho.ca/opengl/files/matrixProjection.zip
知道了矩阵变换后;我们还会经常遇到着色器这个名字;那这个是什么呢?
着色器
着色器(Shader)是运行在GPU上的小程序,这些小程序为图形渲染管线的某个特定部分而运行;在openGL中着色器就相当于画笔,而顶点vertices相当于图形(把一个个点按顺序用线连接起来就是一个图形),着色器OpenGL分成两个部分,一个用于绘制顶点的顶点着色器VerticesShader,一个用于顶点连线后所包围的区域填充颜色的片元着色器,可以理解为windows画图中的填充工具;
我们常见的着色器普遍认为有三种,在移动设备上使用较多的OpenGL的GLSL,在桌面使用较多微软推的DirectX的HSLS(High level shader language);还有NVIDIA公司的CG(C for Graphic);谷歌后面会推的Vulkan,着色器语言是基于GLSL为基础进行增强;由于本文基于的是OpenGL所以就是用的GLSL;
语言总是苍白的,来段代码吧~~
1 |
|
顶点着色器
1 | varying vec3 normal, lightDir; |
当我们讨论到顶点着色器的时候,每个输入变量也叫顶点属性(Vertex Attribute)。我们能声明的顶点属性是有上限的
片源着色器
1 |
|
效果图如下所示:
总结
着色器的创建流程 genShader:
- 创建一个着色器对象 glCreateShader;
- 将着色器的实现代码与创建的着色器对象绑定 glShaderSource;
- 把着色器源代码编译为目标代码 glCompileShader;
- 验证是否编译通过 glGetShaderiv;
接着,把创建好的着色器加入到着色程序中
- 创建一个着色程序 glCreateProgram;
- 把适当的着色对象连接到这个着色程序中 glAttachShader;
- 链接到这个着色器程序 glLinkProgram;
- 验证这着色器结点已经成功完成 glGetProgramiv;
- 使用着色器进行顶点或者片元处理 useProgram;
对于安卓来讲,会把这些接口都进行封装,简化使用;