OpenGL中光栅化器是如何生成片段的

How do fragments get generated by rasterizer in OpenGL

本文关键字:何生成 片段 OpenGL      更新时间:2023-10-16

我看到了光栅化的描述,它基本上说,当一个对象被投影到屏幕上时,会对窗口/屏幕上的所有像素进行扫描,并决定像素/片段是否在三角形内,从而确定像素/片段在三角形内像素/片段,如着色等

现在,由于我正在研究OpenGL,并且我确实知道OpenGL可能有自己的实现过程,我想知道这是否也发生在OpenGL中,因为我在OpenGL教程中阅读了顶点的"扫描转换"过程

与此相关的另一个问题是,我知道像素的图像/屏幕/窗口是一个图像或像素的2d阵列,也被称为默认帧缓冲区,它是线性

所以我想知道的是,如果是这样的话,投影三角形的3个顶点如何定义边上覆盖的像素?

光栅化器是否首先绘制三角形的边缘,然后扫描每个像素或像素的二维阵列(也称为默认帧缓冲区),并使用某种数学方法或其他更简单的过程查看点是否在线之间?

我知道OpenGL可能有自己的实现

OpenGL只是一个规范文档。在计算机上运行的是OpenGL实现,大部分时间作为GPU驱动程序的一部分。实际工作负载由GPU…执行

这也发生在OpenGL上,因为我在OpenGL教程中读到了顶点的"扫描转换"过程

很可能而不是。事实上,上周末我参加了AMD主办的Khronos(指定OpenGL的组织)活动,AMD的一位GPU工程师哀叹道,新手脑海中有OpenGL、Direct3D、Mantel、Vulkan等的扫描线算法,而GPU却做着完全不同的事情。

2d像素阵列,也称为默认帧缓冲区,为线性

实际上,GPU内部使用的像素的存储器布局不是线性的(即逐行),而是遵循提供有效的局部访问的模式。对于线性访问,GPU具有极其高效的复制引擎,允许内部格式和线性格式之间几乎零开销的转换。

不过,内部使用的确切布局只有GPU工程师才能深入了解。但内存不是线性组织的,而是以局部方式组织的,这也是GPU不使用传统扫描线算法的原因之一。

所以我想知道的是,如果是这样的话,投影三角形的3个顶点如何定义边上覆盖的像素?

任何满足OpenGL规范要求的方法都是允许的。细节是OpenGL实现的一部分,即通常是特定GPU模型和驱动程序版本的组合。

扫描线算法是20世纪90年代现代GPU之前人们在软件中所做的。GPU开发人员很快就发现,用于软件渲染的算法与使用数十亿晶体管的超大规模集成电路实现的算法有很大不同。对于任何有软件背景的人来说,为硬件实现优化的算法往往看起来相当陌生。

我想澄清的另一件事是,OpenGL并没有说"如何"渲染,只是说"渲染什么"。OpenGL实现可以随心所欲地进行。我们可以通过阅读OpenGL标准来了解"什么",但"如何"隐藏在GPU供应商的秘密中。

最后,在我们开始之前,您链接的文章是不相关的。它们是关于超声波扫描是如何工作的。

我们对扫描转换了解多少

  • 扫描转换有许多基元作为输入。为了我们的目的,让我们假设它们都是三角形(现在越来越正确)。

  • 每个三角形都必须由剪裁平面剪裁。在最坏的情况下,这可能会使三角形增加三条额外的边(将其变成六边形)。这必须在透视投影之前发生。

  • 每一个原语都必须经过透视投影。此过程获取具有齐次坐标(X、Y、Z、W)的每个顶点,并将其转换为(X/W、Y/W、Z/W)。

  • 帧缓冲区通常按层次结构组织成瓦片,而不是像软件中那样线性组织。此外,处理可以在不止一个层次级别上进行。我们在软件中使用线性组织的原因是,在分层布局中计算内存地址需要额外的周期。然而,超大规模集成电路的实现并没有遇到这个问题,他们可以简单地将寄存器中的位连接起来,以便从中生成地址

所以你可以看到,在软件中,瓦片"复杂而缓慢",但在硬件中,它们"简单而快速"。

查看R5xx手册的一些注意事项:

R5xx系列确实很古老(2005年),但文档可以在网上找到(搜索"R5xx_Acceleration_v.5.pdf")。它提到了两个扫描转换器,所以管道看起来像这样:

primitive output -> coarse scan converter -> quad scan converter -> fragment shader

粗扫描转换器似乎可以在可配置大小(8x8到32x32)的较大瓦片上运行,并具有多种可选模式,即"基于截距"answers"基于边界框"模式。

四元扫描转换器然后获取粗略扫描转换器的输出并输出单独的四元,这是四个样本的组。每个四边形的深度值可以表示为四个离散值或平面方程。如果深度缓冲器中的相应四边形也被指定为平面方程,则平面方程允许快速丢弃整个四边形。这被称为"早期Z",是一种常见的优化。

然后片段着色器一次处理一个四边形。四边形可能包含三角形之外的样本,然后这些样本将被丢弃。

值得一提的是,这是一款显卡。现代图形卡更为复杂。例如,R5xx甚至不允许从顶点着色器采样纹理。

如果你想要一张完全不同的图片,可以查找PowerVR GPU实现,它们使用了一种名为"基于瓦片的延迟渲染"的东西。这些现代而强大的GPU针对低成本和低功耗进行了优化,它们挑战了您对渲染器工作方式的许多假设。

引用GPU宝石:CUDA的并行前缀和(扫描),描述OpenGL如何进行扫描转换并将其与CUDA进行比较,我认为CUDA足以回答我的问题:

在引入CUDA之前,几位研究人员使用OpenGL和Direct3D等图形API进行扫描(请参阅第节39.3.4获取更多)。为了展示CUDA在扫描等计算方面相对于这些API的优势,我们在本节中简要介绍Sengupta et的高效OpenGL扫描实现等人(2006)。它们的实现是一种混合算法减少步骤的可配置数量,如算法5所示。然后先前运行和扫描算法的双缓冲版本如算法2中关于减少步骤的结果所示。终于执行如算法6中所示的向下扫描。

例5。OpenGL扫描算法的简化步骤

1: for d = 1 to log2 n do 
2:     for all k = 1 to n/2 d  – 1 in parallel do 
3:          a[d][k] = a[d – 1][2k] + a[d – 1][2k + 1]]
Example 6. The Down-Sweep Step of the OpenGL Scan Algorithm
1: for d = log2 n – 1 down to 0 do 
2:     for all k = 0 to n/2 d  – 1 in parallel do 
3:          if i > 0 then 
4:             if k mod 2 U2260.GIF 0 then 
5:                  a[d][k] = a[d + 1][k/2]
6:             else 
7:                  a[d][i] = a[d + 1][k/2 – 1]

OpenGL扫描计算是使用像素着色器实现的每个a[d]阵列都是GPU上的二维纹理。写信给这些阵列是使用OpenGL中的"渲染到纹理"来执行的。因此算法5和算法2中的每个循环迭代都需要读取从一种纹理和文字到另一种。

CUDA相对于OpenGL的主要优势在于其片上共享内存、线程同步功能和分散写入内存,这些内存不暴露于OpenGL像素着色器。CUDA划分将大扫描分成多个块,并对每个块进行处理的工作在写入任何数据之前,由单个多处理器完全在芯片上到片外存储器。在OpenGL中,所有内存更新都是片外内存更新。因此,OpenGL实现所使用的带宽很大越高,因此性能越低,如前面的图39-7。