赞
踩
1、OpenGL是什么
OpenGL 是由 Khronos 组织制定并维护的一套图形渲染API规范合集,具体的功能实现是由各个显卡的开发厂商根据规范实现对应的功能。
由于OpenGL 是运行在 GPU(显卡)上的,而我们编写的程序都是运行在CPU上的,所以OpenGL的绘制过程就是在CPU操作GPU提供的API控制显卡这个状态机绘制图形的过程。这个操作过程就是不断设置状态然后调用绘制图元的命令绘制,这个过程一次就称之为一次DrawCall。整个渲染过程就是很多DrawCall过程组成。
2、OpenGL 渲染流程
OpenG渲染的原理是将输入的3d坐标的顶点数据结合纹理信息,和各种渲染状态绘制成屏幕上的2D像素片段的过程。通过这句话我们可以分解出如下几个点:
2.1: 3d 坐标如何转换成 2D的屏幕坐标?
2.2:将点如何绘制成图形?
2.3:点如何渲染成像素片段?
2.4:如何决定像素片段该如何着色的?
2.5:因为图形是3D 那肯定存在遮挡问题,如何决定谁将是最终渲染的片段,以及透明的物体如何透过前面的物体看到后面的物体?
2.6:同一个像素多个片段着色器输出的颜色是如何混合的
2.7:延申问题,如何提高效率,避免不必要的开销?
2.7.1: 避免过多重绘(OverDraw)因为如果是不透明的颜色,同一个像素只会去 z 深度值最小,离我们最近的那个片段颜色,丢弃了看不到的片段,但是这些片段还是走了片段着色这个耗时过程无疑是很浪费的?
上面的问题代入下面这张图
2.1:3d 坐标如何转换成 2D的屏幕坐标?
3d顶点转换成2d屏幕上的点,这个过程是在上面第一个方框的顶点着色器来实现,这个阶段我们我们。通过将3d顶点和模型矩阵(将模型坐标位置转换成世界坐标位置)、视图矩阵(将世界坐标位置转换成相对摄像机坐标系统得位置)、投影矩阵(将摄像机坐标转做投影变换并且映射到屏幕坐标也就是屏幕可视范围内得坐标都在 -1 到 1 得范围,超出得一定在这个范围之外,也就方便了后期得裁剪了), 相互运算最终转换成渲染在2d 屏幕上的坐标然后输出gl_position 变量给OpenGL管线的下一个阶段。顶点着色器OpenGL是暴露给我们可编程的。所以通常会在顶点着色器中通过一定的规律变化顶点的位置实现静态模型的意思动态变化效果。
2.2:将点如何绘制成图形?
如何将点转绘制成图形,这个过程就是 图形装配和几何着色器处理的了。我们在调用OpenGL API 绘制物体的时候会指定顶点组合成什么形状来绘制。我们常用的方案是将顶点组成一个一个的三角形绘制图形。根据api指定的图形,在图元装配阶段生成一系列的图元(比如三角形)传递给几何着色器,几何着色器可以将输入的图元做一系列的调整输出新的图元,实现图形的另外一种变化。默认我们不设置几何着色器的情况下的几何着色器是直接将图元原样输出的。如果有需求可以定制自定义的几何着色器做一些图元的变化(比如让顶点沿着三角形所在面的法线方向移动实现模型炸开效果),或者增加一些图元实现各种效果(比如给每个三角形面增加一个法向量的线段实现毛发效果)。
2.3:如何生成像素片段?
由于显示器显示的原理是通过一个一个的像素矩形显示不同颜色来显示图形的。而我们我们绘制的图形是光滑的线段。所以就需要光栅化将图形所坐落的像素格子找出来。形成像素片段给片段着色器去上色输出。所以光栅化的过程就是将图元所在的像素格子一一找出来。在光栅化之后进入片段着色器之前还会有一个裁剪过程,会将超出屏幕(因为做了投影变换其实超出屏幕的坐标就是不在-1到1的坐标值)的片段裁剪掉减少片段着色器需要处理的片段数量提升效率。找出图元所在像素格子是顶点组成图元变成像素的一部分。另一部分就是顶点上设置的数据比如pos,uv,normal 这些数据和这些像素片段是如何映射的呢?这个过程我们称之为一个插值过程。在三个顶点组成的三角形中间的每个像素片段他们的顶点数据并不是顶点着色器那一个相同的位置,而是GPU通过一个线性插值的过程计算出来的一个值,这样也是为什么我们要在片段着色器做很多着色,而不是在顶点着色阶段去做,因为片段着色器的顶点和法线数据更加细致,效果会更好。当然在有些项目中当对效果要求不高的时候也有人在顶点阶段直接计算光照颜色(顶点光照着色),这种光照信息最终到了像素片段的时候颜色会由顶点上计算的颜色线性插值计算得到的颜色组成所以会有点感觉颜色存在模糊的感觉,但是追求性能肯定是由一定的缺点要接受。需要特别注意的是不是所有情况下顶点着色就一定比片段着色要性能高。因为当顶点数量相当密集的时候可能顶点数量超过了像素片段数量就会比像素片段着色还要多。所以顶点数量达到一定数量的时候顶点着色的效果也能做到片段着色差不多的效果。具体情况得看具体得模型顶点数量规模来看了。
2.4:如何决定像素片段该如何着色的?
像素片段如何着色,在片段着色器中,将会给像素片段上色。这个阶段颜色如何生成是一个开放性的问题,因为这个阶段是可以给用户自定义编程的。最简单的是按照顶点设置的颜色指输出,或者按照绑定的纹理图片和顶点对应的纹理坐标从纹理中采样输出。甚至在之前颜色输出的结果上再结合光照颜色影响将物体的颜色加上光照颜色输出等等情况,片段着色器需要我们特别注意的就是前一点提到的片段数据是根据顶点着色器输出数据经过线性变化过来的值,不管是顶点位置,法线向量,纹理坐标。因为不了解这个点就会有和我一样开始存在困扰为什么顶点着色器是效率最高的并行处理阶段为什么不在顶点阶段做光照处理而要在片段着色器来做。因为片段着色器的数据把每个像素偏度的值都线性光滑处理过了计算光照的结果会更加丝滑一些(当然如果顶点密集的模型这种极端情况不考虑)。
2.5:如何解决遮挡和颜色混合的问题?
这个就是我们测试和混合阶段的任务。测试分为深度测试、模板测试、Alpha测试,遮挡问题就是深度测试解决的。由于3d坐标都有 z 值,代表了我们离摄像机的远近。那么距离越远的深度值将会越高。那么片段的遮挡就是通过深度测试过程中将距离远的片段丢弃绘制深度值小的片段实现遮挡。颜色混合的问题,就是通过我们设置的混合方案,将两个片段根据透明度结合运算出一个混合的颜色。由于颜色混合过程没有深度信息的处理,是通过后渲染的片段去和之前渲染的片段融合,所以为了正常的混合结果,我们渲染的时候会先渲染不透明的物品,然后透明的物体安装由远及近的顺序绘制。
2.6:同一个像素多个片段着色器输出的颜色是如何混合的?
我们绘制绘制物体的时候,是安装先绘制不透明物体,然后再绘制透明物体的,在绘制不透明物体阶段我们是关闭颜色混合的。颜色的选取是按照谁的深度值最小(就是离摄像头最近)就保留谁的颜色值(这个过程是GPU动态帮助我们去控制的)。而在透明绘制阶段就需要我们在CPU阶段先把透明物体由远到近的顺序来绘制,然后每次绘制都会存在一个颜色混合的过程,最终这个像素输出的颜色值将会是所有混合结束后输出的颜色值。而且每个物体在绘制时我们都能自定义这个混合的方式。接下来我们来解析下如何混合的。
颜色混合的公式如下:
其中混合因子可以通过 glBlendFunc或glBlendFuncSeparate来设置,后者比前者不同在于可以分别设置RGB 和 Alpha 的混合因子参数。
而其中的混合因子取值如下
需要注意的是如果混合因子是GL_SRC_COLOR 这种向量而非alpha 这种单独一个值的,其实就是去rgb每个分量分别和颜色的rgb分别计算因子得到结果。
另外我么甚至可以控制原颜色和目标颜色的运算方式,如下
到目前位置,可能看了这些内容会觉得有点懵,我们开始举例说明下:
假设当前我们
已经在像素上面绘制了 红色 (1.0, 0.0, 0.0, 1.0)
当前片段输出了 绿色 (0.0, 1.0, 0.0, 0.6)
我们设置混合因子为 原因子GL_SRC_ALPHA 目标因子GL_ONE_MINUS_SRC_ALPHA
那我们输出的颜色为
=(0.4, 0.6, 0.0, 0.76)
2.7.1 关于OverDraw 这个问题。
可能一般不熟悉渲染过程会不理解OverDraw到底是一个什么情况。因为我们最终只看到了一个颜色片段为啥会存在多次渲染得问题呢?这是因为在渲染过程中GPU也不知道在这些像素片段到底要显示那些。那只能将每个图元都先计算出颜色然后经过深度测试丢弃不要得像素片段,然后给我们呈现最终得颜色。也就是我们看到得颜色是GPU帮我们做了很多片段着色后再选出来得。这时候懂行得就要说了,为啥不先更具深度测试把不需要的片段直接丢弃不要走片段着色阶段浪费时间呢。这时候你回去看前面渲染流程图你就会发现深度测试是在片段着色之后的,所以一般情况下这个方案是不行的。为啥不行呢?因为片段着色器阶段是可能改变深度值得,或者会做discard这种操作丢弃片段,这样就导致深度值在片段着色阶段是会改变得。如果在他之前做深度测试那就会由问题。所以GPU就在内部增加了一些提前深度剔除得加速方法,early-z, z-culling 这些优化方案。这样在我们没有在片段着色器使用改变深度值或者discard片段得时候就能走提前深度测试丢弃不必要得片段着色换得效率了。那基于这种原理,一般游戏引擎为了发挥GPU这一个加速优势,就有了将不透明物体由近到远绘制得方法。因为先绘制了近得后面远点物体被遮挡的片段可以直接提前深度测试丢弃不走片段着色浪费性能了。所以前面我们说的一般情况的特殊情况就是在我们没有在片段着色器做特殊的处理影响深度值就会由GPU优化减少了大量不必要得片段计算。
待续,后续发现渲染流程中的一些细节内容还会继续补充。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。