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

How do fragments get generated by rasterizer in OpenGL

我看到了光栅化的描述,它基本上是说当一个对象被投影到屏幕上时,发生的事情是对 window/screen 上的所有像素进行扫描,并决定 pixel/fragment 在三角形内,因此确定 pixel/fragment 在三角形内,然后对 pixel/fragment 进行进一步处理,例如着色等

既然我正在研究 OpenGL 并且我确实知道 OpenGL 可能有自己的这个过程的实现,我想知道这是否也发生在 OpenGL 中,因为我已经阅读了 "Scan-Conversion" 顶点过程在 OpenGL 教程中

现在与此相关的另一个问题是,我知道 image/screen/window 像素是图像或二维像素数组,也称为线性默认帧缓冲区

所以我想知道的是,如果是这种情况,投影三角形的 3 个顶点如何定义它的边覆盖了哪些像素?

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

and i do know that OpenGL probably has its own implementations of this process

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

this also takes place with OpenGL since of the "Scan-Conversion" process of vertices that i have read in OpenGL tutorial

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

2d array of pixels also known as the default framebuffer that is linear

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

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

So what i am wondering is if that is the case, how would projecting the 3 vertices of a triangle define which pixels are covered in side it?

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

扫描线算法是人们在 1990 年代在现代 GPU 出现之前在软件中所做的事情。 GPU 开发人员很快发现,您用于软件渲染的算法与您在具有数十亿个晶体管的 VLSI 实现中实现的算法 截然不同。无论如何,针对硬件实现优化的算法对于任何来自软件背景的人来说往往看起来相当陌生。

我想澄清的另一件事是 OpenGL 没有说明任何关于 "how" 你渲染的事情,它只是 "what" 你渲染。 OpenGL 实现可以随心所欲地自由执行。我们可以通过阅读 OpenGL 标准找到 "what",但是 "how" 隐藏在 GPU 供应商的秘密中。

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

我们对扫描转换了解多少?

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

  • 每个三角形都必须被裁剪平面裁剪。在最坏的情况下(将其变成六边形),这最多可以为三角形添加三个额外的边。这必须在透视投影之前发生。

  • 每个图元都要经过透视投影。此过程获取具有齐次坐标 (X, Y, Z, W) 的每个顶点并将其转换为 (X/W, Y/W, Z/W).

  • 帧缓冲区通常按层次组织成图块,而不是像您在软件中那样线性组织。此外,处理可能在不止一个层次级别上完成。我们在软件中使用线性组织的原因是因为在分层布局中计算内存地址需要额外的周期。然而,VLSI 实现不会遇到这个问题,他们可以简单地将寄存器中的位连接起来,按照他们想要的方式从中创建地址。

因此您可以看到,在软件中,图块是 "complicated and slow",但在硬件中它们是 "easy and fast"。

看R5xx手册的一些笔记:

R5xx 系列非常古老(2005 年),但文档可在线获取(搜索 "R5xx_Acceleration_v1.5.pdf")。它提到了两个扫描转换器,所以管道看起来像这样:

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

粗略扫描转换器似乎可以在可配置尺寸(8x8 到 32x32)的更大图块上运行,并且有多种可选模式,"intercept based" 和 "bounding box based" 模式。

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

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

再次值得一提的是,这是一张旧的显卡。现代显卡更加复杂。例如,R5xx 甚至不允许您从顶点着色器中采样纹理。

如果您想要更完全不同的画面,请查看 PowerVR GPU 实现,它使用了一种叫做 "tile-based deferred rendering" 的东西。这些现代且功能强大的 GPU 针对低成本和低功耗进行了优化,它们挑战了您对渲染器工作方式的许多假设。

引用自 GPU Gems:Parallel Prefix Sum (Scan) with CUDA,它描述了 OpenGL 如何进行扫描 转换并将其与 CUDA 进行比较,我认为这足以回答我的问题:

Prior to the introduction of CUDA, several researchers implemented scan using graphics APIs such as OpenGL and Direct3D (see Section 39.3.4 for more). To demonstrate the advantages CUDA has over these APIs for computations like scan, in this section we briefly describe the work-efficient OpenGL inclusive-scan implementation of Sengupta et al. (2006). Their implementation is a hybrid algorithm that performs a configurable number of reduce steps as shown in Algorithm 5. It then runs the double-buffered version of the sum scan algorithm previously shown in Algorithm 2 on the result of the reduce step. Finally it performs the down-sweep as shown in Algorithm 6.

Example 5. The Reduce Step of the OpenGL Scan Algorithm

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]

The OpenGL scan computation is implemented using pixel shaders, and each a[d] array is a two-dimensional texture on the GPU. Writing to these arrays is performed using render-to-texture in OpenGL. Thus, each loop iteration in Algorithm 5 and Algorithm 2 requires reading from one texture and writing to another.

The main advantages CUDA has over OpenGL are its on-chip shared memory, thread synchronization functionality, and scatter writes to memory, which are not exposed to OpenGL pixel shaders. CUDA divides the work of a large scan into many blocks, and each block is processed entirely on-chip by a single multiprocessor before any data is written to off-chip memory. In OpenGL, all memory updates are off-chip memory updates. Thus, the bandwidth used by the OpenGL implementation is much higher and therefore performance is lower, as shown previously in Figure 39-7.