图形管线基础(二)

图形管线基础(二)



前言

上一篇文章我们分享了OpenGL大致流程,接下来我们继续分享下一步流程----帧缓存运算。也是我们最后一个阶段。


一、帧缓存运算

帧缓存是Opengl图形管线的最后一个阶段。该缓存可以表示屏幕的可见内容。以及用于存储除颜色外每个像素值的其他内存区域。帧缓存存储的状态包括片段着色器所产生的数据应该写入的位置。数据的格式等信息。状态保持在帧缓存对象中。像素执行状态也被视为帧缓存的一部分,但是不按照帧缓冲对象存储。

二、帧缓存运算流程

1.裁剪测试

片段着色器产生输出后,片段写入窗口前会经历一些步骤,如判断他是否属于窗口,我们可在应用程序中打开或者关闭这些步骤。首先是裁剪测试,即根据我们定义的矩形对片段进行测试,如果片段在举行内,则进一步处理,如果在矩形外,则被丢弃。
关于裁剪主要介绍以下几种裁剪方式:

1-1.点的剪取

P点在窗口内的条件满足下列不等式
Xmin <= x <= Xmax 且 Ymin <= y <= Ymax
否则就在窗口外

在这里插入图片描述

1-2.直线剪取

直线与窗口的关系通常有以下三种情况
1.直线全在窗口内
2.直线全在窗口外
3.直线部分在窗口内部分在窗口外
当窗口采用凸多边形,任何一条直线都至多只有一段在窗口内
1.当一条直线的两个端点全在窗口内时,该直线整个在窗口内
2.当一条直线的两个端点一个在窗内,一个在窗口外时,该直线部分在窗口内,部分在窗口外
3.当一条直线的两个端点全在窗口外时,该直线可能在整个窗口外也可能部分在窗口内,部分在窗口外。

1.Cohen-Sutherland 直线剪取算法

背景:这个一个最早最流行的线段剪取算法。该算法通过初始测试来减少计算的交点数目从而加快线段剪取算法的速度。
原理:每条线段的端点都赋以四位二进制代码,称为区域码。用来标识端点相对于剪取矩阵边界的位置。区域通过如下所示的边界设定
区域码剪取边界的关系为:上下右左 ,X , X, X, X
在这里插入图片描述
任何位赋值为1时,表示端点落在相应边界的外侧,否则该位为0,若端点在剪取矩形内区域码为0,0,0,0.如果端点落在矩形的左下角则区域码为0,1,0,1
一旦给定的所有的线段端点的区域码就可以快速判断哪条直线完全在剪取窗口内,哪条直线完全在窗口外,所以得到以下规则:
1.如果两个端点的编码均为0,0,0,0,表示该直线在窗口内
2.如果两个端点的编码相与不为0,0,0,0,表示该直线在窗口外
3.如果两个端点的编码不全为0,0,0,0,但相与为0,0,0,0则该直线部分可见,需计算与窗口的交点,确定哪部分可见。
Sohen_Sutherland算法关键在于总是先确定窗口外的一个端点,这样位于此端点至与窗口边的交点之间的线段必为不可见,故可将其抛弃,然后利用相同的方法处理剩余部分。
其延伸算法有:取中点判断法,以此剪取直线。
小结:本算法的优点在于简单,易于实现。它可以简单的描述为将直线在窗口外的部分删除,按上下左右的顺序依次进行,处理之后,将剩余部分就是可见的了。
在这个算法中求交点是很重要的,它决定了算法的速度。中点求交法对于硬件很合适,它可以用左右位移来替代乘除法,这样就大大加快可速度。

2.Cyrus_Beck直线剪取算法

矢量的点积
设两个矢量分别为P和Q,则其点积为P . Q = |P||Q|cos#
如果P . Q > 0 , 则其夹角# <90
如果P . Q = 0 , 则其夹角# = 90
如果P . Q < 0 , 则其夹角# >90
Cyrus-Beck算法的基本思想是采用法矢量的概念来判定线段的一点是在窗口之内还是在窗口边界上。对任意凸多边形,其边界上任意一处的内法矢量可用线面的矢量点积来表示:Nj . (B - A ) >= 0, a为多边形边界上一点,b为多边形边界上的另一点。Nj 与其他任一从a到b 的矢量之间的夹角一定是不大于90

在这里插入图片描述
如果f 是凸多边形边界上的一点, N是凸区域边界在f点的内法矢量,则对于线段P1, P2 上的一点P(t) ,满足下式:
1.若N*[P(t) - f] < 0 , 则表示P(t)- f 指向区域的外部
2.若N*[P(t) - f] = 0, 则表示P(t)- f 与包含f的边界重合且与法矢量N垂直。
3.若N*[P(t) - f] > 0, 则表示P(t)- f 指向区域的内部。
由此可知, P(t)满足N[ P( t ) -f ] = 0 的t值的点即为直线与边界的交点
在这里插入图片描述
Cyrus- Beck 算法中对于线段的可见性的判断如下:
设内法矢量与连接线段上一点至边界上一点的矢量点击为 : N*[P(t) - f]
设线段P1,P2 参数方程: P(t) = P1 + (P2 - P1)t
合并得 : N*[P1 + (P2 - P1)t - f]
线段上的点在边界上的条件: N*[P1 + (P2 - P1)t - f] = 0
令D = P2 - P1, w= P1 - f 可得
t = - W . N/ D . N
如果D . N = 0 ,则要么垂直N, 要么 P2 = P1
点P1 与窗口的关系可描述如下:
如果 w . n < 0 , 表示点P 位于窗口之外
如果 w . n = 0 , 表示点P 位于窗口边上
如果 w . n > 0 , 表示点P 位于窗口之内
在这里插入图片描述
对于t值的选择: 首先符合 0 <= t <= 1., 其次,对于凸多边形来说,每个线段与其至多有两个交点,即有两个相应t值,所以我们可以计算出的T 值分成两组,一组为上限组,是分布在线段起点的一侧,一组为下限组,是分布在线段终点的一侧,这样只要找出下限组中的最大值和上限组的最小值就可以确定线段了
分组的依据:
如果 w . n < 0 , 则计算出的值属于下限组
如果 w . n > 0 , 则计算出的值属于上限组
有了这些,整个算法就建立了
在这里插入图片描述
Cyrus-Beck 算法小结
该算法比Cohen-Sutherland算法使用范围更广一些,它可以对任何凸多边形适用,但对于凹多边形就失效了。以下是判定凸多边形的方法。对与凹多边形我们也可以通过多边形切割将其转化为凸多边形。
凸多边形的判定
叉积法
1.如果各叉积全部为零,则该多边形个边共线
2.如果各叉积一部分为正,一部分为负,则多边形为凹多边形
3.如果所有叉积全部大于零或者等于零,则多边形为凸多边形,此时沿着边的正向,内法矢量指向其左侧
4.如果所有叉积全部小于零或者等于零,则多边形为凸多边形,此时沿着边的正向,内法矢量指向其右侧(叉积遵循左手定则)
在这里插入图片描述

1-3.多边形剪取

多边形剪取可分解为一条一条线段进行。如考虑其封闭则一个窗口对一个多边形的剪取可产生一个或者多个边

1.Sutherland-Hodgeman 多边形剪取算法

通过对单一边或面的剪取来实现多边形剪取。即在算法中,剪取窗口的每一边将逐次对原多边形和每次剪取所生成的多边形进行剪取
算法的每一次输出(包括中间结果)都是一个多边形的顶点表,且所有顶点均位于相应窗口剪取边或者面的可见一侧。由于多边形的每一条边需要与剪取边或面分别进行比较,因此只需要讨论单条边和单个剪取边或者面之间可能的位置关系。假设S,P为多边形的两个相邻顶点,且S为该边的起点,P为该边的终点,则边SP与剪取边或面之间只有4种可能关系。
在这里插入图片描述
在这里插入图片描述
由上可见,每一次将多边形的边与剪取边或面比较之后,输出一个或者两个顶点,也可能无输出点。如果SP边完全可见,则输出P点,不必输出S点。因为顶点时按顺序处理的。S 是作为前一边的终点输出的,如果SP完全不可见,则无输出点。如果SP边部分可见,则SP边可能进入或者离开剪取边或面的可见一侧。如果SP边进入剪取边或面的可见一侧,则输出两点,一个为SP与剪取边或面的交点,一个是P点。
对于多边形的第一个顶点,只需要判断其可见性,如果可见,则输出且作为起点S,否则无输出点。但还是要保存S点,以便后续处理。
对于最后一条边PnP1,其处理方法是:标志第一个顶点为F,这样最后一条边则为PnF, 可与其他边作相同处理。
多边形剪取算法小结
对凸多边形应用本算法可以得到正确的结果。但对于凹多边形的剪取将如图所示显示一条多余直线。这种情况在剪取后的多边形有两个或者多个分离部分的时候出现。因为只有一个输出顶点表,所以表中最后一个顶点总是连着第一个顶点。
解决这个问题有多种方法,
1.把凹多边形分割成若干个凸多边形,然后分别处理各个凸多边形,
2.修改本算法,沿着任何一个剪取窗口边检查顶点表,正确的连接顶点对

2.Weiler-Atherton 多边形剪取算法

主多变形:用来被剪取的多边形,可以是凸的,凹的或者是带孔的
剪取多边形:用来剪取主多边形的多边形,可以是凸的,凹的或者是带孔的
定义多边形的方向:外多边形按顺时针方向,内多边形按逆时针方向
进点:从主多边形进入剪取多边形内部时的交点
出点:从主多边形离开剪取多边形内部时的交点
算法原理:首先从进点开始,沿着主多边形边界跟踪,知道找到他与剪取多边形交点(出点)为止,在交点处向右转,再次沿着主多边形的边界跟踪;重复执行上述过程,直至回到算法的起始交点(进点)为止
在这里插入图片描述
Weiler-Atherton多边形剪取算法主要术语及数据结构
主多边形的顶点表:用来定义主多边形
剪取多边形的顶点表:用来定义剪取多边形
进点表:包含主多边形进入剪取多边形内部时的交点。
出点表:包含主多边形离开剪取多边形内部时的交点
多边形的内表:位于剪取多边形内部的主多边形的边界以及位于主多边形内的剪取多边形的边界(其构成主多边形的孔)
多边形的为表:位于剪取多边形外部的主多边形的边界以及位于主多边形内剪取多边形的边界,但不包括位于主多边形外的剪取多边形的边界

在这里插入图片描述

2.模板测试

接下来是模板测试。这一步骤将我们的应用提供的参照值与模板缓存的内容进行比较,该缓存为每个像素存储一个值。如果开启了模板测试,GPU会首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和读取(使用读取掩码)到的参考值(reference vale)进行比较,这个比较函数是可以由开发者指定的。如果这个片元没有通过这个测试,该片元就会被舍弃,
Stencil Comparison
渲染时判断是否保留的条件。
在这里插入图片描述
Stencil Operation
渲染测试之后,无论像素最终被保留或被抛弃都会执行Stencil Operation来更新缓冲中的值stencilBufferValue,具体更新的方式包括:
在这里插入图片描述

2.深度测试(消隐)

由于投影变换失去了深度信息,往往导致图形的二义性。要消除二义性,必须在绘制时消隐实际不可见的线和面,即消隐。经过消隐的投影图称为物体的真实图形。
消隐算法的实现空间:消隐算法可以在对象空间或图像空间中实现。

1.z缓冲区算法

z缓冲器算法是所有图像空间算法中最简单的一种隐藏面消隐算法。z缓冲区算法是比较片段z坐标和深度缓存内容的操作。深度缓存是一个内存区域,与模板缓存一样都是帧缓存的一部分,为每一个像素存一个值,缓存内包含每个像素的深度。一般情况下,深度缓存值的范围从0~1,其中表示深度缓存中最近的点,1表示最远的点。为判断某个片段是否比同一位置已渲染的其他片段更近,Opengl可将该片段的窗口坐标的z分量与已在该深度缓存内片段的值进行比较。如果该值比已经在缓存内的值小,则片段可见。本测试的含义也可以改变。比如哦,我们可以命令Opengl是z坐标大于、等于或不等于深度缓存内容的片段通过。
算法分析
简单,易于处理隐藏面以及显示曲面之间的交贯线。显示的画面可以任意复杂,因为图像空间的大小是固定的,最坏情况下计算量随着画面复杂程度呈线性增长,由于显示的元素可按任意顺序写入帧缓冲器和z缓冲器,所以不需要进行深度排序,节省排序时间
缺点:需要占用大量存储空间的z缓冲器,若要被显示的画面经过变换和剪取后,z值便在一定范围内变化,则可以采用固定精度的z缓冲器,本算法结合光照,透明等相关算法后,可产生较佳效果

2.Roberts算法

Roberts消隐算法是在对象空间实现的消隐算法。数字处理严谨,计算量甚大,Roberts算法要求所有被显示的物体都是凸的,因此凹体要先分割成许多个凸体的组合。具体细节较为复杂,可以自己了解,不详细展开。
算法步骤
1.逐个的独立的考虑每个物体自身,找出其自身所遮挡的边和面
2.将每个物体上留下的边再与其他物体逐个的进行比较,以确定其是完全可见还是部分或分部遮挡
3.确定由于物体之间的相互贯穿等原因,是否需要形成新的显示边等,从而使被显示各物体更接近显示
Roberts消隐算法是在图像空间实现的消隐算法,数学处理严谨,计算量甚大,Roberts算法要求所有被显示的物体都是凸的,因此对凹体要先分割成许多个凸体的组合。

2.Warnock算法

考虑图像空间中的一个窗口,判定窗口是否为空,或者该窗口所包含的画面是否足够简单可以立即显示,否则,分割该窗口,直到子窗口内所有包含的画面相当简单以致可以立即显示或者尺寸已达到给定的分辨率为止。
画面足够简单的条件
1.窗口中仅包含一个多边形
2.窗口与一个多边形相交,且窗口内无其他多边形
3.窗口为一个多边形所包围且窗口内无其他多边形
4.窗口至少为一个多边形所包围且此多边形离视点最近
多边形与窗口之间的关系
在这里插入图片描述
Warnock算法分为5个部分
控制程序:控制检测窗口,当检测失败时,继续分割窗口,保证整个屏幕都被检测到
检测程序:检查全部对象的所有多边形,将他们分成分离,包围,内含和相交多边形,并建立三个表,包围多边形表,分离多边形表,相交以及内含多边形表,供分析程序进一步处理
分析程序:分析检查程序提供的信息,分析结果,画面足够简单,调用计算机显示,画面复杂,宣告失败,空窗口,不用处理
计算显示:经过剪取,显示窗口内可见部分
补省显示:窗口内只含有一个光栅点时显示一个点
WarNock算法最关键的部分是检查程序,决定一个多边形是否与窗口分离,包含,相交或包围,下面将详细介绍判别多边形与窗口关系的方法
多边形分离窗口的判别:对于矩形窗口,可以采用包围盒或最大最小测试方法已决定该多边形是否与窗口分离
假设Xl, Xr , Yr, Yl定义窗口的四条边,Xmin, Ymin, Xmax, Ymax定义一个多边形包围盒,当满足下列条件时,多边形与窗口分离:Xmin > Xr or Xmax < Xl or Ymin > Yl or Ymax < Yr
多边形内含于窗口的判别:当多边形包围盒内含于窗口时,即条件Xmin >= Xr and Xmax <= Xr and Ymin >= Yr and Ymax <= Yl
在这里插入图片描述
多边形与窗口相交的判别,可以采用直线方程作为判别函数来判定一个多边形是否与窗口相交
假设多边形一棱边的两个顶点为P1(X1 , Y1) P2(X2, Y2),其直线方程为 y = kx + b(x1 ! = x2)
则该直线的判别函数为 TF = y - kx - b 或 TF = X -X1 (X1 = X2)
其中,k = (y2 - y1)/(x2 - x1) , b = y1 - kx1
在这里插入图片描述
判别步骤如下
对窗口的每一个顶点代入判别函数,如果所有顶点的判别函数值的符号相同,则所有点点位于多边形棱边的同侧,即该棱边与窗口不相交,否则,该棱边所在直线与窗口相交,简单视作多边形与窗口相交。

如果多边形每一棱边均与窗口不相交,则多边形要么与窗口分离,要么包围窗口

分离和包围多边形的进一步判别可以采用转交累计检查法
按顺时针或逆时针绕多边形依次累加多边形各边起点与终点对窗口内任意一点所形成的夹角,按累计角度之和可以判定。
若角度之和等于0,则表示多边形与窗口分离
若角度之和等于±360*n,则表示多边形包围窗口(n次)
在这里插入图片描述
深度计算
对于包围多边形需要进行深度计算,比较各个多边形平面在窗口的角点处的深度(z坐标)
左图给出一种情况,延伸多边形所在平面至窗口边界处,可能导致包围多边形遮挡其他多边形的判别错误,所以,这种情况需要通过继续分割窗口作进一步判别。
右图给出另一种情况:延伸多边形所在平面至窗口的边界处,在窗口内没有与包围多边形相交,不会产生包围多边形遮挡其他多边形的判别错误,所以这种情况属于画面足够简单,不需要再分割窗口
在这里插入图片描述

3.表优先级算法

按要被显示的每一个元素离视点的距离的远近进行深度优先级表。算法执行时,先从离视点最远的元素开始,依次将每一个元素写入帧缓冲器,最终在帧缓冲器中的内容总是被离视点较近的元素所覆盖
在这里插入图片描述
先讨论在深度优先级排序中两个多边形P和Q之间的关系。多边形可以按其最大或最小z值排序,不妨假设多边形的最大z值排序Zmax§ > Zmax(Q),即在优先级中P排在Q之前
如右上图所示:P,Q两平面满足Zmax§ > Zmax(Q),则可直接按表优先级次序显示。
如右下图所示:虽然在优先级表中,P排在Q之前,但是如果按照这个顺序将P,Q分别写入帧缓冲器中显示的话,则将Q部分将遮挡P,而事实上是P的部分遮挡了Q,所以必须在优先级表中交换P,Q的值

在这里插入图片描述
在这里插入图片描述
上图给出另外几个复杂的情况,无法建立正确的深度优先表,解决方法是沿着多边形所在平面间的交线循环分割这些多边形,至最后可完全确定其优先级表(见虚线所示)

3.混合

通过了深度测试的片元进入下一阶段–混合。
对于不透明物体,开发这可以关闭混合操作。这样片元着色器计算得到的颜色值就会直接覆盖颜色缓冲区中的像素值。但对于半透明物体,我们就需要混合操作来让这个物体看起来是透明的。GPU会取出源颜色和目标颜色,将两种颜色进行混合。源颜色指的是片元着色器得到的颜色值,而目标颜色则是已经存在于颜色缓冲区中的颜色。之后,就会使用一个混合函数进行混合操作。这个混合函数通常和透明通道息息相关,例如根据透明通道的值相加,相减,相乘等。
在这里插入图片描述
常见混合类型 (Blend Type)
Blend SrcAlpha OneMinusSrcAlpha // 传统透明度
Blend One OneMinusSrcAlpha // 预乘透明度
Blend One One // 加法
Blend OneMinusDstColor One // 软加法
Blend DstColor Zero // 乘法
Blend DstColor SrcColor // 2x 乘法

4.逻辑运算

片段颜色被发送至逻辑运算阶段,最后显示在屏幕上

总结

以上就是本次图形常规管线基础大致内容。往后可能会继续写矩阵,shader数据交互的部分内容。

引用

OpenGL超级宝典
bilibili 上海交大图形学

猜你喜欢

转载自blog.csdn.net/weixin_39289457/article/details/125355980