光栅化:一种实际的实现

Posted geniushuai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了光栅化:一种实际的实现相关的知识,希望对你有一定的参考价值。

快速复审

在上一章中,我们对光栅化渲染技术进行了高级概述。它可以分解为两个主要阶段:首先,将三角形的顶点投影到画布上,然后对三角形本身进行光栅化。在这种情况下,光栅化实际上意味着将三角形的形状“分解”成像素光栅元素正方形这就是过去调用像素的方式。在本章中,我们将回顾第一步。这种方法在前两节课中已经介绍过了,这里不再赘述。如果您对透视投影背后的原理有任何疑问,请再次查看这些课程。但是,在本章中,我们将学习一些与投影相关的新技巧,这些技巧在我们学习透视投影矩阵时会很有用。我们将学习一种将投影顶点坐标从屏幕空间重新映射到 NDC 空间的新方法。我们还将详细了解 z 坐标在光栅化算法中的作用以及在投影阶段应如何处理。

请记住,如前一章所述,光栅化渲染技术的目标是解决可见性或隐藏表面问题,即确定 3D 对象的某些部分是可见的,哪些部分是隐藏的。

投影:我们要解决什么问题?

在光栅化算法的那个阶段,我们试图在这里解决什么?如上一章所述,光栅化的原理是查找图像中的像素是否与三角形重叠。为此,我们首先需要将三角形投影到画布上,然后将它们的坐标从屏幕空间转换为光栅空间。然后在同一空间中定义像素和三角形,这意味着可以比较它们各自的坐标(我们可以检查给定像素的坐标与三角形顶点的光栅空间坐标)。

因此,此阶段的目标是将构成三角形的顶点从相机空间转换为光栅空间。

投影顶点:注意 Z 坐标!

在前两节课中,我们提到当我们计算 3D 点的光栅坐标时,最终我们真正需要的是它的 x 和 y 坐标(图像中 3D 点的位置)。快速提醒一下,这些 2D 坐标是通过将相机空间中 3D 点的 x 和 y 坐标除以该点各自的 z 坐标(我们称为透视除法),然后重新映射生成的 2D 坐标获得的从屏幕空间到 NDC 空间,然后从 NDC 空间到光栅空间。请记住,由于图像*面位于*裁剪*面,我们还需要将 x 和 y 坐标乘以*裁剪*面。同样,我们在前两节课中详细解释了这个过程。

=PXPz是的=P是的PzsCreen.X=ne一个r*C一个er一个.X-C一个er一个.zsCreen.是的=ne一个r*C一个er一个.是的-C一个er一个.z

请注意,到目前为止,我们一直将屏幕空间中的点视为本质上的 2D 点(我们不需要在透视分割后使用点的 z 坐标)。不过,从现在开始,我们将在屏幕空间中声明点为 3D 点,并将它们的 z 坐标设置为相机空间点的 z 坐标,如下所示:

=PXPz是的=P是的PzzPzsCreen.X=ne一个r*C一个er一个.X-C一个er一个.zsCreen.是的=ne一个r*C一个er一个.是的-C一个er一个.zsCreen.z=-C一个er一个.z

此时最好将投影点 z 坐标设置为原始点 z 坐标的倒数,正如您现在所知道的那样,它是负数。处理正 z 坐标将使以后的一切变得更简单(但这不是强制性的)。

图 1:当相机空间中的两个顶点具有相同的 2D 光栅坐标时,我们可以使用原始顶点的 z 坐标来找出哪个在另一个之前(从而哪个是可见的)。

需要跟踪相机空间中的顶点 z 坐标来解决可见性问题看图 1 会更容易理解为什么。想象一下两个顶点 v1 和 v2,当它们投影到画布上时,它们具有相同的光栅坐标(如图 1 所示)。如果我们在 v2 之前投影 v1,那么当它实际上应该是 v1 时,v2 将在图像中可见(v1 显然在 v2 前面)。但是,如果我们将顶点的 z 坐标与它们的 2D 栅格坐标一起存储,我们可以使用这些坐标来定义离相机最*的点,而与投影顶点的顺序无关(如下面的代码片段所示) .

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
// 项目 v2
Vec3f v2screen;
v2screen.x = 附* * v2camera.x / -v2camera.z;
v2screen.y = 附* * v2camera.y / -v2camera.z;
v2screen.z = -v2cam.z;

Vec3f v1screen;
v1screen.x = 附* * v1camera.x / -v1camera.z;
v1screen.y = 附* * v1camera.y / -v1camera.z;
v1screen.z = -v1camera.z;

// 如果两个顶点在图像中的坐标相同,则比较它们的 z 坐标
if (v1screen.x == v2screen.x && v1screen.y == v2screen.y && v1screen.z < v2screen.z)
//如果 v1.z < v2.z 然后将 v1 存储在帧缓冲区中
....

图 2:像素重叠的三角形表面上的点可以通过对构成这些三角形的顶点进行插值来计算。有关详细信息,请参阅第 4 章。

我们想要渲染的是三角形而不是顶点。那么问题来了,我们刚刚学到的方法是如何应用于三角形的呢?简而言之,我们将使用三角形顶点坐标来查找三角形上像素重叠的点的位置(以及它的 z 坐标)。这个想法如图 2 所示。如果一个像素与两个或多个三角形重叠,我们应该能够计算像素重叠的三角形上的点的位置,并使用这些点的 z 坐标,就像我们对顶点,以了解哪个三角形最靠*相机。该方法将在第 4 章(The Depth Buffer.Finding the Depth Value by Interpolation)中详细介绍。

屏幕空间也是三维的

图 3:屏幕空间是三维的(中图)。

总而言之,从相机空间到屏幕空间(这是发生透视划分的过程),我们需要:

 

  • 执行透视划分:即将相机空间 x 和 y 坐标中的点除以点 z 坐标。
    =PXPz是的=P是的PzsCreen.X=ne一个r*C一个er一个.X-C一个er一个.zsCreen.是的=ne一个r*C一个er一个.是的-C一个er一个.z
  • 但也将投影点 z 坐标设置为原始点 z 坐标(相机空间中的点)。
    zPzsCreen.z=-C一个er一个.z

实际上,这意味着我们的投影点不再是 2D 点,而是实际上是 3D 点。或者换一种说法,屏幕空间不是二维的。Ed-Catmull在他的论文中写道:

屏幕空间也是三维的,但是对象已经经历了透视变形,因此对象在 xy *面上的正交投影会产生预期的透视图像(Ed-Catmull 的论文,1974 年)。

图 4:我们可以通过投影与 xy 图像*面正交(或垂直,如果您愿意)的线来在屏幕空间中形成对象的图像。

您现在应该能够理解这句话了。该过程也如图 3 所示。首先,几何顶点在相机空间(顶部图像)中定义。然后,每个顶点经历一个透视划分。也就是顶点的 x 和 y 坐标除以其 z 坐标,但如前所述,我们还将生成的投影点 z 坐标设置为原始顶点 z 坐标的倒数。顺便说一下,这可以推断出屏幕空间坐标系的 z 轴方向的变化。如您所见,z 轴现在指向内而不是外(图 3 中的图像)。但最需要注意的是,生成的对象是原始对象的变形版本,但仍然是三维对象。此外,Ed-Catmull 在写“一个 透视 投影 , 然后 一个 正交 投影如果您不清楚透视和正交投影之间的区别,请不要担心。这是下一课的主题。但是,请尽量记住这个观察结果,因为它稍后会派上用场。

将屏幕空间坐标重新映射到 NDC 空间

在前两节课中,我们解释了一旦进入屏幕空间,投影点的 x 和 y 坐标需要重新映射到 NDC 空间。在之前的课程中,我们还解释了在 NDC 空间中,画布上的点的 x 和 y 坐标包含在 [0,1] 范围内。但在 GPU 世界中,NDC 空间中的坐标包含在 [-1,1] 范围内。可悲的是,这又是我们需要处理的这些约定之一。我们本可以保留约定 [0,1],但由于 GPU 是光栅化的参考,因此最好坚持使用 GPU 世界中定义该术语的方式。

您可能想知道为什么我们一开始就没有使用 [-1,1] 约定。有几个原因。曾经是因为在我们看来,术语“标准化”应该始终表明被标准化的值在 [0,1] 范围内。也因为很高兴知道几个渲染系统在 NDC 空间的概念方面使用不同的约定。例如,RenderMan 规范将 NDC 空间定义为在 [0,1] 范围内定义的空间。

因此,一旦点已经从相机空间转换到屏幕空间,下一步就是将它们分别从 x 和 y 坐标的范围 [l,r] 和 [b,t] 重新映射到范围 [- 1,1]。这里的术语 l, r, b, t 表示画布的左、右、下和上坐标。通过重新排列这些项,我们可以很容易地找到一个执行我们想要的重新映射的方程:

rl<X<r

这里的 x 是屏幕空间中 3D 点的 x 坐标(请记住,从现在开始,我们将假设屏幕空间中的点是三维的,如上所述)。如果我们从方程中去掉 l 项,我们得到:

l0<X-l<r-l

通过将所有项除以 (rl) 我们得到:

 

<))<))<))10<(X-l)(r-l)<(r-l)(r-l)0<(X-l)(r-l)<1

 

我们现在可以在等式中间展开项:

<X)-l)10<X(r-l)-l(r-l)<1

我们现在可以将所有项乘以 2:

*X)− *l)20<2*X(r-l)-2*l(r-l)<2

我们现在从所有术语中删除 1:

− *X)− *l)− 1-1<2*X(r-l)-2*l(r-l)-1<1

如果我们开发这些术语并重新组合它们,我们最终会得到:

− *X)− *l)-))1− *X)+r)1− *X)+r)1− <×)-l)1-1<2*X(r-l)-2*l(r-l)-(r-l)(r-l)<1-1<2*X(r-l)+-2*l+l-r(r-l)<1-1<2*X(r-l)+-l-r(r-l)<1-1<2X(r-l)-r+l(r-l)<1

这是一个非常重要的方程,因为公式中间的红色和绿色项将成为透视投影矩阵的系数。我们将在下一课中学习这个矩阵。但是现在,我们将只应用这个等式将屏幕空间中一个点的 x 坐标重新映射到 NDC 空间(当在 NDC 空间中定义时,位于画布上的任何点的坐标都包含在 [-1.1] 范围内) . 如果我们对 y 坐标应用相同的推理,我们会得到:

− <2)-b)1-1<2是的(-b)-+b(-b)<1

把事情放在一起

在本课结束时,我们现在可以执行光栅化算法的第一阶段,您可以将其分解为两个步骤:

  • 将相机空间中的一个点转换为屏幕空间。它本质上将一个点投影到画布上,但请记住,我们还需要存储原始点的 z 坐标。屏幕空间中的点是树维的,z 坐标将有助于稍后解决可见性问题。
    =PXPz是的=P是的PzzPzsCreen.X=ne一个r*C一个er一个.X-C一个er一个.zsCreen.是的=ne一个r*C一个er一个.是的-C一个er一个.zsCreen.z=-C一个er一个.z
  • 然后,我们使用以下公式将屏幕空间中这些点的 x 和 y 坐标转换为 NDC 空间:
    − <×)-l)1− <2)-b)1-1<2X(r-l)-r+l(r-l)<1-1<2是的(-b)-+b(-b)<1
    其中 l, r, b, t 表示画布的左、右、下和上坐标。

从那里,将坐标转换为栅格空间非常简单。我们只需要将 NDC 空间中的 x 和 y 坐标重新映射到范围 [0,1] 并分别将结果数乘以图像的宽度和高度(不要忘记在光栅空间中 y 轴向下而在 NDC 空间中它会上升。因此我们需要在重新映射过程中改变 y 的方向)。在代码中我们得到:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
浮动*剪裁*面 = 0.1;
// 指向相机空间
Vec3f pCamera;
worldToCamera.multVecMatrix(pWorld, pCamera);
// 转换为屏幕空间
Vec2f pScreen;
pScreen.x = nearClippingPlane * pCamera.x / -pCamera.z;
pScreen.y = nearClippingPlane * pCamera.y / -pCamera.z;
// 现在将点从屏幕空间转换为 NDC 空间(范围 [-1,1])
Vec2f pNDC;
pNDC.x = 2 * pScreen.x / (r - l) - (r + l) / (r - l);
pNDC.y = 2 * pScreen.y / (t - b) - (t + b) / (t - b);
// 转换为光栅空间并将 z 坐标设置为 -pCamera.z
Vec3f pRaster;
pRaster.x = (pScreen.x + 1) / 2 * imageWidth;
// 在栅格空间中,y 向下,因此反转方向
pRaster.y = (1 - pScreen.y) / 2 * imageHeight;
// 存储点相机空间 z 坐标(作为正值)
pRaster.z = -pCamera.z;

请注意,光栅空间中的点或顶点的坐标在这里仍然定义为浮点数,而不是整数(像素坐标就是这种情况)。

下一步是什么?

我们现在已经将三角形投影到画布上,并将这些投影顶点转换为光栅空间。三角形的顶点和像素都生活在同一个坐标系中。我们现在准备遍历图像中的所有像素,并使用一种技术来确定它们是否与三角形重叠。这是下一章的主题。

 

参考连接:

ios怎么下植物大战僵尸1:http://www.pvzbaike.com/ios-zen-me-xia-zhi-wu-da-zhan-jiang-shi-1.html

Rasterization: a Practical Implementation:https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/projection-stage

以上是关于光栅化:一种实际的实现的主要内容,如果未能解决你的问题,请参考以下文章

实验二 直线生成算法

Chromium网页GPU光栅化原理分析

光栅化插值方法

Python中具有抗锯齿的光栅化算法

光栅化算法-中点画圆算法

直线光栅化-Bresenham算法