OpenGL ES 2.0编程指导阅读笔记(七)图元装配和光栅化

图元装配阶段在顶点着色之后,在图元装配阶段执行裁剪、透视变换和视窗变换。
光栅化是将图元转换成一系列两位片元的过程。

图元

OpenGL ES 2.0中可以绘制的图元有三角形、线、点精灵。

三角形

OpenGL ES支持的三角形图元有GL_TRIANGLES、GL_TRIANGLE_STRIPE、GL_TRIANGLE_FAN。

三角形图元

线

OpenGL ES支持的线图元有GL_LINES、GL_LINE_STRIPE、GL_LINE_LOOP。
线图元

线的宽度设置:

void glLineWidth(GLfloat width);

注意:设定的线宽会被限制到OpenGL ES 2.0实现支持的范围内,且不强制要求支持宽度大于1的线宽。
可以通过以下命令查询支持的线宽范围:

GLfloat lineWidthRange[2];
glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);

点精灵

OpenGL ES支持的点精灵图元为GL_POINTS。
点精灵是一个和屏幕对齐的具有位置和大小(radius)属性的方形。
gl_PointSize是vertex shader需要输出的一个内建变量,代表了相关的点的大小。
输出的glPointSize会被限制到OpenGL ES 2.0实现支持的范围内。
可以通过以下命令查询支持的点大小范围:

GLfloat pointSizeRange[2];
glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);

gl_PointCoord是仅在fragment shader中使用的输入内建变量,可以用作纹理坐标等。
默认情况下,OpenGL ES 2.0所述的窗口原点是窗口的左下角,但是,对于点精灵,点坐标(gl_PointCoord)原点是左上角。

绘制图元

有两个API可以用来绘制图元:

void glDrawArrays(GLenum mode, GLint first, GLsizei count);
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);

其中,mode指所绘制的图元的类型,可以为GL_POINTS、GL_LINES、GL_LINE_STRIP、GL_LINE_LOOP、GL_TIRANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN;type指索引的数据类型,可以是GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT,在实现OES_element_index_uint扩展的情况下也可以是GL_UNSIGNED_INT。

性能贴士

应用应该在一次draw call中放入尽可能多的图元。如果有许多独立的网格,可以使用退化三角形将这些独立网格连接起来放入一次draw call中。退化三角形指有两个以上顶点位置相同的三角形,GPU可以轻易地检查和去除退化三角形。
注意:这意味着在实现中,退化三角形应该是不可见的,而不是一条线。
需要注意的是,同一个triangle strip中奇数和偶数个三角形的环绕顺序是不相同的。
为了连接两个独立网格所需添加的退化三角形数量取决于第一个网格的最后一个三角形以及第二个网格的第一个三角形是顺时针还是逆时针的,连接时需要保证三角形的环绕顺序与原来一致。如果是相同的需要插入一个退化三角形,如果是相反的则需要插入两个。

另一个值得考虑的是根据post-transform vertex cache(变换后顶点缓存)的大小来决定如何为图元分配索引。
多数GPU都带有变换后顶点缓存,在顶点被vertex shader处理之前,首先检查它是否已经在变换后顶点缓存中,如果在,则不需要再被处理一次。因此,更具变换后顶点缓存的大小来决定图元索引分配的策略是有益于整体性能的。

图元装配

经过顶点染色后的顶点需要经过图元装配,即裁剪、透视除、视窗变换,之后送给光栅化。
为了理解这些操作,首先需要熟悉OpenGL ES 2.0中用到的各种坐标系。

坐标系

image
顶点输入到OpenGL ES中时处在对象或者说本地坐标空间中。这是对象被建模或保存的坐标空间。
顶点着色器被执行之后,顶点位置是在裁剪坐标空间中。(实际上顶点着色器是完成了本地坐标系->世界坐标系->观察坐标系->裁剪坐标系的转换,但是是通过乘MVP矩阵一次性完成的)

裁剪

裁剪坐标系是齐次坐标系。顶点坐标在裁剪坐标系中按照视锥体(也称为裁剪体)裁剪。
视锥体在观察坐标系中是一个椎体,在裁剪坐标系中是一个立方体,但都由近平面、远平面、左平面、右平面、上平面、下平面六个裁剪平面围成。
对于不同的图元,裁剪的操作有所不同:
三角形:完全在内则保留,完全在外则丢弃,部分在内则根据裁剪平面裁剪,这会生成新的顶点和三角形。
线:完全在内则保留,完全在外则丢弃,部分在内则根据裁剪平面裁剪,这会生成新的顶点和线。
点精灵:在内则保留,在外则丢弃
注意:裁剪操作对硬件来说成本很高。尽管部分超出近平面和远平面的图元必须裁剪,但对于其他平面并非如此。可以渲染一个比glViewPort指定的视窗稍大的视窗区域,然后使用剪刀操作完成x和y平面的裁剪,而GPU执行剪刀操作很高效。这个稍大的视窗被称作Guard Band(保护带)。尽管OpenGL ES不允许应用指定保护带,但是绝大多数OpenGL ES实现都实现了保护带。

透视除

透视除将裁剪坐标系映射到归一化的设备坐标系。通过将\((x_c,y_c,z_c)\)\(w_c\)来获得归一化的设备坐标\((x_d,y_d,z_d)\)。称之为归一化坐标是因为坐标范围在\([-1.0,1.0]\)范围中。

视窗变换

视窗变换通过以下API设置:

void glViewPort(GLint x, GLint y, GLsizei w, GLsizei h);

其中\((x,y)\)为屏幕坐标系中视窗的左下脚的像素坐标,\((w,h)\)为视窗的宽高有多少像素。
默认情况下\(x=0\),\(y=0\), w和h与窗口的大小一致。
也可以设置期望的深度范围:

void glDepthRange(GLclampf n, GLclampf f);

默认情况下\(n=0.0\), \(f=1.0\),且设置的n和f会被截取到\([0.0,1.0]\)范围内。
最终的窗口坐标\((x_w,y_w,z_w)\)通过以下变换由归一化设备坐标\((x_d,y_d,z_d)\)获得:

\[\begin{bmatrix} x_w \\ y_w \\ z_w \end{bmatrix} = \begin{bmatrix}\frac{w}{2}x_d+\frac{x+w}{2} \\ \frac{h}{2}y_d+\frac{y+h}{2} \\ \frac{f-n}{2}z_d+\frac{n+f}{2} \end{bmatrix} \]

光栅化

在图元装配之后,光栅化流水线为每个图元生成片元。
每个片元以其在屏幕空间中的整数位置\((x,y)\)表示。

消隐

在进行光栅化之前,需要确定三角形是背向还是面向观察者的。消隐操作会丢弃背向观察者的三角形。
三角形的朝向用窗口坐标系下三角形的有符号面积表示。
而三角形顶点的环绕顺序(顺时针CW或者逆时针CCW)与面积的符号的映射由以下API指定:

void glFrontFace(GLenum dir);

其中dir可以是GL_CW或者GL_CCW。
消隐哪一部分三角形由以下API指定:

void glCullFace(GLenum mode);

其中mode可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK。
需要注意,消隐仅当GL_CULL_FACE状态被使能时才执行,可以用以下API来使能或禁用:

void glEnable(GLenum cap);
void glDisable(GLenum cap);

此时,cap应为GL_CULL_FACE。

多边形偏移

当两个多边形相互重叠时,可能看到最终图像中有一些瑕疵(artifact),这种现象被称为深度冲突(Z-fighting),这是由于三角形光栅化的精度有限导致的。因为光栅化的精度终究有限,因此这个问题只能随着精度的提高改善而无法彻底解决。
image
为了避免这个问题,我们需要在深度测试和深度写回到深度缓冲之前,给计算出的深度值加上一个偏差值,如果深度测试通过,就会把原始的深度值(而非带有偏差的深度值)写回到深度缓冲中。
用以下API设置深度缓冲:

void glPolygonOffset(GLfloat factor, GLfloat units);

深度偏移的计算公式如下:

\[depth\ offset=m*factor+r*units \]

其中,m为三角形最大的深度斜率,有:

\[m=\sqrt{\frac{\partial z}{\partial x}^2+\frac{\partial z}{\partial y}^2} \]

或者

\[m=\max \left\{ \left| \frac{\partial z}{\partial x} \right|,\left| \frac{\partial z}{\partial y} \right| \right\} \]

斜率项\(\frac{\partial z}{\partial x}\)\(\frac{\partial z}{\partial y}\)由OpenGL ES实现在三角形光栅化阶段计算。
r是实现定义的常量,代表着深度值上可信差异的最小值。

多边形偏移使用glEnable(GL_POLYGON_OFFSET_FILL)和glDisable(GL_POLYGON_OFFSET_FILL)来使能和关闭。

posted @ 2023-02-15 18:05  xvsay  阅读(162)  评论(0编辑  收藏  举报