当前位置: 首页 > news >正文

vps云主机可以做网站/app关键词排名优化

vps云主机可以做网站,app关键词排名优化,答题做任务网站,幼儿做爰网站光栅化是将图元转换为二维图像的过程。 该图像的每个点都包含颜色和深度等信息。 因此,对图元进行光栅化由两部分组成。 第一个是确定窗口坐标中整数网格的哪些方格被图元占据。 第二个是为每个这样的方块分配颜色和深度值。 (OpenGL 规范) N…

光栅化是将图元转换为二维图像的过程。 该图像的每个点都包含颜色和深度等信息。 因此,对图元进行光栅化由两部分组成。 第一个是确定窗口坐标中整数网格的哪些方格被图元占据。 第二个是为每个这样的方块分配颜色和深度值。 (OpenGL 规范)

NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎

在上一章中,我们学习了如何以某种方式执行光栅化算法的第一步,即将三角形从 3D 空间投影到画布上。 这个定义实际上并不完全准确,因为我们所做的是将三角形从相机空间转换到屏幕空间,正如上一章提到的,屏幕空间也是一个三维空间。 然而,屏幕空间中顶点的 x 和 y 坐标对应于画布上三角形顶点的位置,并且通过将它们从屏幕空间转换到 NDC 空间,然后最终从 NDC 空间转换到光栅空间, 我们最终得到的是光栅空间中顶点的二维坐标。 最后,我们还知道屏幕空间中顶点的 z 坐标保留相机空间中顶点的原始 z 坐标(反转以便我们处理正数而不是负数)。

图1:通过测试,如果图像中的像素与三角形重叠,我们就可以绘制出该三角形的图像。 这就是光栅化算法的原理

接下来我们需要做的是循环图像中的像素,并找出这些像素是否与“三角形的投影图像”重叠(图 1)。 在图形 API 规范中,此测试有时称为内部-外部测试(inside-outside test)或覆盖测试(coverage test)。 如果是,我们将图像中的像素设置为三角形的颜色。 这个想法很简单,但当然,我们现在需要想出一种方法来查找给定像素是否与三角形重叠。 这本质上就是我们在本章中要研究的内容。

我们将了解光栅化中通常使用的方法来解决这个问题。 它使用了一种称为边缘函数(edge function)的技术,我们现在将描述和研究该技术。 该边缘函数还将提供有关三角形投影图像内像素位置(称为重心坐标)的有价值的信息。 重心坐标在计算像素重叠的三角形表面上的点的实际深度(或 z 坐标)中起着至关重要的作用。 我们还将在本章中解释什么是重心坐标以及它们是如何计算的。

在本章结束时,你将能够生成一个非常基本的光栅化器(rasterizer)。 在下一章中,我们将研究这种非常简单的光栅化算法实现可能存在的问题。 我们将列出这些问题是什么,并研究它们通常是如何解决的。

为了优化算法,人们进行了大量的研究。 本课程的目标不是教你如何编写或开发基于光栅化算法的优化且高效的渲染器。 本课程的目标是教授渲染技术的基本原理。 不要认为我们在这些章节中介绍的技术没有被使用。 它们在某种程度上被使用,但它们在 GPU 或生产渲染器的 CPU 版本上的实现方式很可能是同一想法的高度优化版本。 真正重要的是理解其原理及其一般工作原理。 基于此,你可以自己研究用于加速算法的不同技术。 但本课程中介绍的技术是通用的,构成了任何光栅化器的基础。

请记住,绘制三角形(因为三角形是基元,我们将在本例中使用它)是一个两步问题:

  • 我们首先需要找到哪些像素与三角形重叠。
  • 然后,我们需要定义与三角形重叠的像素应设置为哪种颜色,这个过程称为着色

光栅化阶段主要涉及第一步。 我们说本质上而不是排他的原因是,在光栅化阶段,我们还将计算称为重心坐标(barycentric  coordinates)的东西,在某种程度上,它在第二步中使用。

1、边缘函数

如上所述,有几种可能的方法来查找像素是否与三角形重叠。 最好记录较旧的技术,但在本课中,将仅介绍当今普遍使用的方法。 该方法由 Juan Pineda 在 1988 年提出,论文名为“A Parallel Algorithm for Polygon Rasterization”。

图2:Pineda方法的原理是找到一个函数,这样当我们测试给定点在这条线的哪一侧时,函数在该点位于该线的左侧时返回正数,在该线的左侧时返回负数 它位于该线的右侧,当该点恰好在线上时为零。

在我们研究Pineda的技术之前,我们首先描述一下他的方法的原理。 假设三角形的边可以看作将 2D 平面(图像平面)一分为二的线(如图 2 所示)。 Pineda方法的原理是找到一个他称之为边缘函数(edge function)的函数,这样当我们测试给定的点(图2中的点P)在这条线的哪一侧时,该函数返回一个负数。 在线左侧,当该点在线右侧时为正数,当该点恰好在线上时为零。

在图 2 中,我们将此方法应用于三角形的第一条边(由顶点 v0-v1 定义。请注意,顺序很重要)。 如果我们现在对另外两条边(v1-v2 和 v2-v0)应用相同的方法,我们可以看到有一个区域(白色三角形),其中所有点都为正(图 3):

图 3:白色区域内包含的点均位于三角形所有三个边的右侧

如果我们在这个区域内取一个点,那么我们会发现这个点位于三角形所有三个边的右侧。 如果 P 是像素中心的点,我们就可以使用此方法来查找该像素是否与三角形重叠。 如果对于这一点,我们发现边缘函数对于所有三个边缘都返回正数,则该像素包含在三角形中(或者可能位于其边缘之一)。 Pinada 使用的函数也恰好是线性的,这意味着它可以增量计算,但我们稍后会回到这一点。

明白了原理之后,我们就来看看这个函数是什么。 边函数定义为(对于由顶点 V0 和 V1 定义的边):

正如论文中提到的,该函数具有一个有用的属性,即它的值与点 (x,y) 相对于点 V0 和 V1 定义的边缘的位置有关:

  • 如果 P 位于“右侧”,则 E(P) > 0
  • 如果 P 正好在线上,则 E(P) = 0
  • 如果 P 位于“左侧”,则 E(P) < 0

该函数在数学上相当于向量 (v1-v0) 和 (P-v0) 之间叉积的大小。 我们还可以将这些向量写成矩阵形式(将其呈现为矩阵除了整齐地呈现两个向量之外没有其他兴趣):

如果我们设 A=(P-V0)且B=(V1-v0),那么也可以将向量 A 和 B 写成 2x2 矩阵:

该矩阵的行列式可以计算为:

如果现在再次用向量 (P-V0) 和 (V1-V0) 替换向量 A 和 B,将得到:

正如你所看到的,它与我们上面定义的边缘函数类似。 换句话说,边缘函数可以被视为由 2D 向量 (P-v0) 和 (v1-v0) 的分量定义的 2x2 矩阵的行列式,也可以被视为向量 ( P-V0) 和 (V1-V0)。 两个向量的叉积的行列式和大小都具有相同的几何解释。 让我们解释一下。

当我们查看两个 3D 向量之间的叉积结果时,会更容易理解正在发生的情况(图 4):

图 4:向量 B(蓝色)和 A(红色)的叉积得出向量 C(绿色),该向量垂直于 A 和 B 定义的平面(假设采用右手定则约定)。 矢量 C 的大小取决于 A 和 B 之间的角度。它可以是正值,也可以是负值

在 3D 中,叉积返回与两个原始向量垂直(或正交)的另一个 3D 向量。 但正如你在图 4 中看到的,该正交向量的大小也会随着两个向量相对于彼此的方向而变化。 在图 4 中,我们假设右手坐标系。 当两个向量 A(红色)和 B(蓝色)完全指向相同方向或相反方向时,第三个向量 C(绿色)的大小为零。 矢量 A 的坐标为 (1,0,0) 并且是固定的。 当向量B的坐标为(0,0,-1)时,则绿色向量、向量C的坐标为(0,-1,0)。 如果我们要找到它的“有符号”大小,我们会发现它等于-1。 另一方面,当向量 B 的坐标为 (0,0,1) 时,C 的坐标为 (0,1,0),且其有符号量值等于 1。在一种情况下,“有符号”量值为负,并且在第二种情况下,有符号的幅度为正。

事实上,在 3D 中,向量的大小可以解释为以 A 和 B 为边的平行四边形的面积:

如图 5 所示:

图5:平行四边形的面积是向量A和B形成的矩阵的行列式的绝对值

尽管上式的符号指示了向量 A 和 B 相对于彼此的方向,但面积应始终为正。 当相对于A,B位于向量A和与A正交的向量(我们称这个向量D;注意A和D形成二维笛卡尔坐标系)定义的半平面内时,则方程的结果为正的。 当 B 在相对的半平面内时,方程的结果为负(图 6):

图6:平行四边形的面积是向量A和B构成的矩阵的行列式的绝对值。

解释该结果的另一种方式是,当角度theta是在范围[0,pi]内时为正,在范围[pi, 2*pi]内为负。请注意,当theta正好等于 0 或pi时,那么叉积或边缘函数返回 0。

如果角度theta小于pi,那么“有符号”面积就是正数。 如果角度大于pi,那么“有符号”面积就是负数。 该角度是相对于由向量 A 和 D 定义的笛卡尔坐标计算的。可以看出它们将平面分成两半。

为了确定一个点是否在三角形内,我们真正关心的是用于计算平行四边形面积的函数的符号。 然而,区域本身在光栅化算法中也起着重要作用; 它用于计算三角形中点的重心坐标,这是我们接下来将研究的技术。 3D 和 2D 中的叉积具有相同的几何解释,因此两个 2D 向量之间的叉积也返回由这两个向量定义的平行四边形的“有符号”面积。 唯一的区别是,在 3D 中,要计算平行四边形的面积,你需要使用以下方程:

而在 2D 中,该面积由叉积本身给出(如前所述,也可以解释为 2x2 矩阵的行列式):

从实际角度来看,我们现在需要做的就是测试为三角形的每条边计算的边函数的符号以及由点和边的第一个顶点定义的另一个向量(图 7):

图 7:如果边函数对于三个指示的向量对返回正数,则 P 包含在三角形中

公式如下:

如果所有三个测试均为正或等于 0,则该点位于三角形内部(或位于三角形的一条边上)。 如果任何一项测试为负,则该点位于三角形之外。 在代码中我们得到:

bool edgeFunction(const Vec2f &a, const Vec3f &b, const Vec2f &c)
{return ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x) >= 0);
}bool inside = true;
inside &= edgeFunction(V0, V1, p);
inside &= edgeFunction(V1, V2, p);
inside &= edgeFunction(V2, V0, p);if (inside == true) {// point p is inside triangles defined by vertices v0, v1, v2...
}

2、边缘函数的替代方案

除了边缘函数方法之外,还有其他方法来查找像素是否与三角形重叠,但是正如本章介绍中提到的,我们在本课中不会研究它们。 但仅供参考,另一种常见技术称为扫描线光栅化(scanline rasterization)。 它基于通常用于画线的Brenseham算法。 GPU 使用边缘方法主要是因为它比扫描线方法更通用,扫描线方法也比边缘方法更难并行运行,但我们不会在本课中提供有关此主题的更多信息。

3、当心! 边缘顺序问题

图8:顺时针和逆时针环绕

我们一直在讨论但在 CG 中非常重要的事情之一是声明构成三角形的顶点的顺序。 它们是两种可能的约定,如图 8 所示:顺时针或逆时针排序或环绕。 顺序很重要,因为它本质上定义了三角形的一个重要属性,即法线的方向。 请记住,三角形的法线可以通过两个向量 A=(V2-V0) 和 B=(V1-V0) 的叉积来计算。 假设 V0={0,0,0}、V1={1,0,0} 且 V2={0,-1,0},则 (V1-V0)={1,0,0} 且 (V2 -V0)={0,-1,0}。 现在让我们计算这两个向量的叉积:

但是,如果按逆时针顺序声明顶点,则 V0={0,0,0}、V1={0,-1,0} 且 V2={1,0,0}、(V1-V0)= {0,-1,0} 且 (V2-V0)={1,0,0}。 让我们再次计算这两个向量的叉积:

图 9:顺序定义了法线的方向

图 10:顺序定义三角形内的点是正值还是负值

正如预期的那样,两条法线指向相反的方向。 由于许多不同的原因,法线的方向非常重要,但最重要的原因之一是面部剔除。 大多数光栅化器甚至光线追踪器都可能无法渲染法线背向相机的三角形。 这称为背面剔除(backface culling)。 大多数渲染 API(例如 OpenGL 或 DirectX)都提供关闭背面剔除的选项,但是,你仍然应该意识到顶点排序在渲染内容等方面发挥着重要作用。 毫不奇怪,边缘函数是其中之一。

在我们解释为什么它在我们的特定情况下很重要之前,我们先假设在选择顺序时没有特定的规则。 实际上,渲染器实现中的许多细节可能会改变法线的方向,因此不能假设通过按特定顺序声明顶点,你将得到法线将以某种方式定向的保证。 例如,可以使用 (V0-V1) 和 (V2-V1) 来代替叉积中的向量 (V1-V0) 和 (V2-V0)。 它会产生相同的法线,但会翻转。 即使你使用向量 (V1-V0) 和 (V2-V0),请记住叉积中向量的顺序会改变法线的符号:A x B = - B x A。因此法线的方向也取决于叉积中向量的顺序。 出于所有这些原因,不要试图假设以一种顺序而不是另一种顺序声明顶点会给你一个结果或另一个结果。 但重要的是,一旦你坚持了你所选择的约定。 通常,OpenGL 和 DirectX 等图形 API 期望以逆时针顺序声明三角形。 我们还将使用逆时针环绕。 现在让我们看看排序如何影响边缘函数。

为什么环绕书讯对于边缘函数很重要? 你可能已经注意到,从本章开始,在所有图中我们都是按顺时针顺序绘制三角形顶点的。 我们还将边缘函数定义为:

如果我们遵守这个约定,那么顶点 A 和 B 定义的线右侧的点将为正。 例如,V0V1、V1V2 或 V2V0 右侧的点将为正。 然而,如果我们以逆时针顺序声明顶点,则顶点 A 和 B 定义的边右侧的点仍为正,但它们将位于三角形之外。 换句话说,与三角形重叠的点不是正值而是负值(图 10)。 你仍然可以对边缘函数稍加修改即可使代码处理正数:

总之,根据你使用的边缘排序约定,可能需要使用边缘函数的一个或另一个版本。

4、重心坐标

图11:平行四边形的面积是三角形面积的两倍

无需计算重心坐标(barycentric coordinates)即可使光栅化算法正常工作。 对于渲染技术的简单实现,你所需要的只是投影顶点并使用我们上面描述的边缘函数之类的技术来查找像素是否在三角形内部。 这是生成图像唯一必要的两个步骤。

然而,正如我们上面所解释的,边缘函数的结果可以解释为由向量 A 和 B 定义的平行四边形的面积,可以直接用于计算这些重心坐标。 因此,同时研究边缘函数和重心坐标是有意义的。

在我们进一步讨论之前,让我们解释一下这些重心坐标是什么。 首先,它们以一组三个浮点数形式出现,在本课中,我们将表示为λ0、λ1和λ2。存在许多不同的约定,但维基百科也使用希腊字母 lambda (λ),同时也被其他作者使用(希腊字母 omega有时也使用)。 这并不重要,你可以按照你想要的方式称呼它们。 简而言之,坐标可用于按以下方式定义三角形上的任意点:

通常,V0、V1 和 V2 是三角形的顶点。 这些坐标可以采用任何值,但对于三角形内部(或位于其边缘之一)的点,它们只能在 [0,1] 范围内,并且三个坐标之和等于 1。 也就是说:

如果你愿意的话,这是一种插值形式。 它们有时也被定义为三角形顶点的权重(这就是为什么在代码中我们将用字母 w 表示它们)。 与三角形重叠的点可以定义为“一点点V0加一点点V1加一点点V2”。 请注意,当任何坐标为 1(这意味着在这种情况下其他坐标必然为 0)时,点 P 等于三角形的一个顶点。 例如如果
那么P等于V2。

图12:我们如何找到P的颜色?

插值三角形的顶点来查找三角形内点的位置并不是很有用。 但该方法也可用于在三角形表面上插值在三角形顶点处定义的任何量或变量。 例如,假设你在三角形的每个顶点定义了一种颜色。 假设 V0 为红色,V1 为绿色,V2 为蓝色(图 12)。 你想要做的是找出这三种颜色如何在三角形表面上插值。 如果你知道三角形上点 P 的重心坐标,那么它的颜色Cp(三角形顶点颜色的组合)定义为:

这是一种非常方便的技术,对于给三角形着色非常有用。 与三角形顶点相关的数据称为顶点属性(vertex attribute)。 这是CG中非常常见也非常重要的技术。 最常见的顶点属性是颜色、法线和纹理坐标。 这在实践中意味着,通常当你定义三角形时,不仅将三角形顶点传递给渲染器,而且还将其关联的顶点属性传递给渲染器。 例如,如果要对三角形进行着色,则可能需要颜色和法线顶点属性,这意味着每个三角形将由 3 个点(三角形顶点位置)、3 种颜色(三角形顶点的颜色)和 3 个点定义。 法线(三角形顶点的法线)。 法线也可以在三角形的表面上进行插值。 插值法线用于一种称为平滑着色(smooth shading)的技术,该技术由 Henri Gouraud 首次提出。 稍后我们将在讨论着色时解释这项技术。

我们如何找到这些重心坐标? 事实证明很简单。 如上所述,当我们提出边缘函数时,边缘函数的结果可以解释为由向量 A 和 B 定义的平行四边形的面积。如果你看图 8,你可以很容易地看到三角形的面积 由顶点 V0、V1 和 V2 定义的,只是由向量 A 和 B 定义的平行四边形面积的一半。因此,三角形的面积是平行四边形面积的一半,我们知道可以通过交叉计算 -两个 2D 向量 A 和 B 的乘积:

图13:将P连接到三角形的每个顶点形成三个子三角形

如果点 P 在三角形内部,那么通过图 13 可以看出,我们可以绘制三个子三角形:V0-V1-P(绿色)、V1-V2-P(洋红色)和 V2-V0 -P(青色)。 很明显,这三个子三角形面积之和等于三角形 V0-V1-V2 的面积:

图 14:λ0、λ1 和 λ2 的值取决于 P 在三角形上的位置

让我们首先尝试直观地了解它们是如何工作的。 如果你查看图 14,这将变得更容易。该系列中的每个图像都显示了当点 P(最初位于由顶点 V1-V2 定义的边缘上)向 V0 移动时,子三角形会发生什么情况。 一开始,P 正好位于边 V1-V2 上。 在某种程度上,这类似于两点之间的基本线性插值。 换句话说,我们可以这样写:

由于 λ1+λ2=1,因此 λ2=1-λ1。 在这种特殊情况下更有趣的是,如果使用重心坐标计算 P 位置的通用方程为:

因此,它清楚地表明,在这种特殊情况下,λ0 等于 0:

这很简单。 另请注意,在第一张图像中,红色三角形不可见。 另请注意,P 与 V1 的距离比与 V2 的距离更近。 因此,不知何故,λ1 必然大于 λ2。 另请注意,在第一张图像中,绿色三角形比蓝色三角形大。 因此,如果我们总结一下:当红色三角形不可见时,λ0 等于 0。λ1 大于 λ2,绿色三角形大于蓝色三角形。 因此,在某种程度上,三角形的面积和重心坐标之间似乎存在某种关系。 此外,红色三角形似乎与 λ0 相关,绿色三角形与 λ1 相关,蓝色三角形与 λ2 相关:

图 15:要计算重心坐标之一,请使用由 P 定义的三角形面积以及与需要计算重心坐标的顶点相对的边

还要注意,在这种特殊情况下,蓝色和绿色三角形消失了,并且三角形 V0-V1-V2 的面积与红色三角形的面积相同。 这证实了我们的直觉,即子三角形的面积和重心坐标之间存在关系。 最后,根据上述观察,我们还可以说每个重心坐标与由与重心坐标关联的顶点直接相对的边和点 P 定义的子三角形的面积有某种关系。换句话说( 图15):

  • λ0 与 V0 相关。 V0 相对的边是 V1-V2。 V1-V2-P 定义红色三角形。
  • λ1 与 V1 相关。 V1 相对的边是 V2-V0。 V2-V0-P 定义绿色三角形。
  • λ2 与V2 相关。 V2 相对的边是 V0-V1。 V0-V1-P 定义蓝色三角形。

如果你还没有注意到,红色、绿色和蓝色三角形的面积是由我们之前用来确定 P 是否在三角形内部的相应边缘函数给出的,除以 2(记住边缘函数 本身给出由两个向量 A 和 B 定义的平行四边形的“有符号”区域,其中 A 和 B 可以是三角形的三个边中的任何一个):

重心坐标可以计算为子三角形面积与三角形面积 V0V1V2 之间的比率:

除以三角形面积的作用本质上是标准化坐标。 例如,当 P 与 V0 位置相同时,则三角形 V2V1P(红色三角形)的面积与三角形 V0V1V2 的面积相同。 因此,1 除以 over 得到 1,即坐标 λ0 的值。 由于在这种情况下,绿色和蓝色三角形的面积为 0,因此 λ1 和 λ2 等于 0,我们得到:

这正是我们所期望的。

为了计算三角形的面积,我们可以使用前面提到的边函数。 这适用于子三角形以及主三角形 V0V1V2。 然而,边缘函数返回平行四边形的面积而不是三角形的面积(图 8),但由于重心坐标是根据子三角形面积与主三角形面积之比计算的,因此我们可以忽略除以 2 (分子和分母中的除法相互抵消):

注意:

让我们看看它在代码中的样子。 我们之前已经计算了边函数来测试点是否在三角形内。 只是,在我们之前的实现中,我们只是根据函数的结果是正还是负来返回 true 或 false。 为了计算重心坐标,我们需要边缘函数的实际结果。 我们还可以使用边函数来计算三角形的面积(乘以 2)。 下面是一个实现版本,用于测试点 P 是否在三角形内,如果是,则计算其重心坐标:

float edgeFunction(const Vec2f &a, const Vec3f &b, const Vec2f &c)
{return (c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x);
}float area = edgeFunction(v0, v1, v2); // area of the triangle multiplied by 2
float w0 = edgeFunction(v1, v2, p); // signed area of the triangle v1v2p multiplied by 2
float w1 = edgeFunction(v2, v0, p); // signed area of the triangle v2v0p multiplied by 2
float w2 = edgeFunction(v0, v1, p); // signed area of the triangle v0v1p multiplied by 2// if point p is inside triangles defined by vertices v0, v1, v2
if (w0 >= 0 && w1 >= 0 && w2 >= 0) {// barycentric coordinates are the areas of the sub-triangles divided by the area of the main trianglew0 /= area;w1 /= area;w2 /= area;
}

让我们尝试使用此代码来生成实际图像。

5、插值与外推

图 16:内插法与外推法

值得注意的一件事是,重心坐标的计算与其相对于三角形的位置无关。 换句话说,如果该点位于三角形外部,则坐标有效。 当点在内部时,使用重心坐标来评估顶点属性的值称为插值(interpolation),当点在外部时,我们称为外插(extrapolation)。 这是一个重要的细节,因为在某些情况下,我们必须评估可能不与三角形重叠的点的给定顶点属性的值。 更具体地说,例如,需要计算三角形纹理坐标的导数。 这些导数用于正确过滤纹理。 如果你有兴趣了解有关此特定主题的更多信息,可以阅读有关纹理映射的课程。 同时,你需要记住的是,即使该点没有覆盖三角形,重心坐标也是有效的。 你还需要了解顶点属性外插和插值之间的区别。

6、光栅化规则

在某些特殊情况下,一个像素可能与多个三角形重叠。 当像素恰好位于两个三角形共享的边缘上时,就会发生这种情况,如图 17 所示:

图 17:像素可能覆盖两个三角形共享的边缘

这样的像素将通过两个三角形的覆盖测试。 如果它们是半透明的,由于半透明对象的组合方式,像素与两个三角形重叠的地方可能会出现暗边缘(想象一下两个叠加的半透明塑料片。表面更加不透明,并且 看起来比单张纸更暗)。 您将得到类似于图 18 中看到的内容,这是一条较暗的线,两个三角形共享一条边:

图 18:如果几何体是半透明的,则像素与两个三角形重叠的位置可能会出现暗边

这个问题的解决方案是提出某种规则,保证一个像素永远不会与共享一条边的两个三角形重叠两次。 我们该怎么做呢? 大多数图形 API(例如 OpenGL 和 DirectX)都定义了所谓的左上角规则。 我们已经知道,如果一个点位于三角形内部或位于任何三角形边上,则覆盖测试将返回 true。 不过,左上角规则所说的是,如果像素或点位于三角形内部或位于三角形的顶边或任何被视为左边的边上,则该像素或点被视为与三角形重叠。 什么是顶边(top edge)和左边(left edge)? 如果查看图 19,你可以轻松明白顶部和左侧边缘的含义:

图 19:顶部和左侧边缘

  • 顶边是完全水平的边,其定义顶点位于第三个顶点之上。 从技术上讲,这意味着向量 V[(X+1)%3]-V[X] 的 y 坐标等于 0,并且其 x 坐标为正(大于 0)。
  • 左边缘本质上是向上的边缘。 请记住,在我们的例子中,顶点是按顺时针顺序定义的。 如果边缘各自的向量 V[(X+1)%3]-V[X](其中 X 可以是 0、1、2)具有正 y 坐标,则认为边缘向上。

在伪代码中我们有:

// Does it pass the top-left rule?
Vec2f v0 = { ... };
Vec2f v1 = { ... };
Vec2f v2 = { ... };float w0 = edgeFunction(v1, v2, p); 
float w1 = edgeFunction(v2, v0, p); 
float w2 = edgeFunction(v0, v1, p); Vec2f edge0 = v2 - v1;
Vec2f edge1 = v0 - v2;
Vec2f edge2 = v1 - v0;bool overlaps = true;// If the point is on the edge, test if it is a top or left edge, 
// otherwise test if  the edge function is positive
overlaps &= (w0 == 0 ? ((edge0.y == 0 && edge0.x > 0) ||  edge0.y > 0) : (w0 > 0));
overlaps &= (w1 == 0 ? ((edge1.y == 0 && edge1.x > 0) ||  edge1.y > 0) : (w1 > 0));
overlaps &= (w1 == 0 ? ((edge2.y == 0 && edge2.x > 0) ||  edge2.y > 0) : (w2 > 0));if (overlaps) {// pixel overlap the triangle...
}

该版本作为概念证明是有效的,但高度未优化。 关键思想是首先检查返回函数返回的值是否等于 0,这意味着该点位于边缘上。 在本例中,我们测试相关边缘是否是左上角边缘。 如果是,则返回 true。 如果edge函数返回的值不等于0,那么如果该值大于0,我们就返回true。本课提供的程序中我们不会实现左上角规则。

7、整合:查找像素是否与三角形重叠

让我们在生成实际图像的程序中测试本章中学到的不同技术。 假设我们已经投影了三角形(查看本课的最后一章以了解光栅化算法的完整实现)。 我们还将为三角形的每个顶点分配一种颜色。 以下是图像的形成方式。 我们将循环图像中的所有像素,并使用边缘函数方法测试它们是否与三角形重叠。 三角形的所有三个边都根据像素的当前位置进行测试,如果边缘函数为所有边返回正数,则像素与三角形重叠。 然后,我们可以计算像素的重心坐标,并通过对三角形每个顶点定义的颜色进行插值,使用这些坐标来对像素进行着色。 帧缓冲区的结果保存到 PPM 文件(您可以使用 Photoshop 读取该文件)。 程序的输出如图 20 所示:

图 20:使用重心坐标进行顶点属性线性插值的示例

请注意,该程序的一种可能的优化是循环遍历三角形边界框中包含的像素。 我们没有在此版本的程序中进行此优化,但如果你愿意,可以自己进行优化。

另请注意,在此版本的程序中,我们将点 P 移动到每个像素的中心。你也可以使用像素整数坐标。 我们将在下一章中介绍有关此主题的更多详细信息。

// c++ -o raster2d raster2d.cpp
// (c) www.scratchapixel.com#include <cstdio>
#include <cstdlib>
#include <fstream>typedef float Vec2[2];
typedef float Vec3[3];
typedef unsigned char Rgb[3];inline
float edgeFunction(const Vec2 &a, const Vec2 &b, const Vec2 &c)
{ return (c[0] - a[0]) * (b[1] - a[1]) - (c[1] - a[1]) * (b[0] - a[0]); }int main(int argc, char **argv)
{Vec2 v0 = {491.407, 411.407};Vec2 v1 = {148.593, 68.5928};Vec2 v2 = {148.593, 411.407};Vec3 c0 = {1, 0, 0};Vec3 c1 = {0, 1, 0};Vec3 c2 = {0, 0, 1};const uint32_t w = 512;const uint32_t h = 512;Rgb *framebuffer = new Rgb[w * h];memset(framebuffer, 0x0, w * h * 3);float area = edgeFunction(v0, v1, v2);for (uint32_t j = 0; j < h; ++j) {for (uint32_t i = 0; i < w; ++i) {Vec2 p = {i + 0.5f, j + 0.5f};float w0 = edgeFunction(v1, v2, p);float w1 = edgeFunction(v2, v0, p);float w2 = edgeFunction(v0, v1, p);if (w0 >= 0 && w1 >= 0 && w2 >= 0) {w0 /= area;w1 /= area;w2 /= area;float r = w0 * c0[0] + w1 * c1[0] + w2 * c2[0];float g = w0 * c0[1] + w1 * c1[1] + w2 * c2[1];float b = w0 * c0[2] + w1 * c1[2] + w2 * c2[2];framebuffer[j * w + i][0] = (unsigned char)(r * 255);framebuffer[j * w + i][1] = (unsigned char)(g * 255);framebuffer[j * w + i][2] = (unsigned char)(b * 255);}}}std::ofstream ofs;ofs.open("./raster2d.ppm");ofs << "P6\n" << w << " " << h << "\n255\n";ofs.write((char*)framebuffer, w * h * 3);ofs.close();delete [] framebuffer;return 0; 
}

正如你所看到的,我们可以说光栅化算法本身非常简单(并且该技术的基本实现也非常简单)。

8、下一步是什么?

有许多与重心坐标主题相关的有趣技术和琐事,但本课只是光栅化算法的介绍,因此我们不会再进一步。 不过,有一个有趣的细节是,重心坐标沿着平行于边缘的线是恒定的(如图 21 所示):

图 21:沿着平行于边缘的线,重心坐标是恒定的

在本课中,我们学习了两个重要的方法和各种概念。

首先,我们了解了边缘函数以及如何使用它来查找点 P 是否与三角形重叠。 为三角形的每条边计算边函数,并由边的第一个顶点和另一个点 P 定义第二个向量。如果对于所有三个边,函数均为正,则点 P 与三角形重叠。

此外,我们还了解到,边函数的结果还可以用于计算点 P 的重心坐标。这些坐标可用于在三角形表面上插值顶点数据或顶点属性。 它们可以被解释为各个顶点的权重。 最常见的顶点属性是颜色、法线和纹理坐标。


原文链接:光栅化算法实现 - BimAnt

相关文章:

光栅化渲染:光栅化算法实现

光栅化是将图元转换为二维图像的过程。 该图像的每个点都包含颜色和深度等信息。 因此&#xff0c;对图元进行光栅化由两部分组成。 第一个是确定窗口坐标中整数网格的哪些方格被图元占据。 第二个是为每个这样的方块分配颜色和深度值。 &#xff08;OpenGL 规范&#xff09; N…...

Python-Opencv图像处理的小坑

1.背景 最近在做一点图像处理的事情&#xff0c;在做处理时的cv2遇到一些小坑&#xff0c;希望大家遇到的相关的问题可以注意&#xff01;&#xff01; 2. cv2.imwrite保存图像 cv2.imwrite(filename, img, [params]) filename&#xff1a;需要写入的文件名&#xff0c;包括路…...

[LCTF 2018]bestphp‘s revenge

文章目录 前置知识call_user_func()函数session反序列化PHP原生类SoapClient 解题步骤 前置知识 call_user_func()函数 把第一个参数作为回调函数调用 eg:通过函数的方式回调 <?php function barber($type){echo "you wanted a $type haircut, no problem\n";}c…...

HTML中常用表单元素使用(详解!)

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍HTML中常用表单元素使用以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题可以在评论区留言 …...

掌握C++模板的艺术:类型参数、默认值和自动推导

掌握C模板的艺术:类型参数、默认值和自动推导 模板参数 类型模板参数 在 Grid 示例中&#xff0c;Grid 模板有一个模板参数&#xff1a;存储在网格中的类型。编写类模板时&#xff0c;您需要在尖括号内指定参数列表&#xff0c;例如&#xff1a; template <typename T&g…...

Unity_使用FairyGUI搭建登录页面

Unity_使用FairyGUI搭建登录页面 1. 使用FairyGUI准备一个UI界面&#xff0c;例如&#xff1a;以下登录 2. 发布导出&#xff08;发布路径设置为Unity的Asset下任何路径&#xff09; 3. Unity编辑器安装FairyGUI包资源&#xff08;在资源商店找见并存储为我的资源&#xff0c;…...

百岁时代即将来临,原知因成为消费新潮流

什么叫长寿时代?泰康保险首席执行官陈东升指出&#xff1a;长寿时代&#xff0c;就是百岁人生即将来临&#xff0c;人人带病长期生存。而在这个时代&#xff0c;人类最大的变化在于“生命尺度的改变”&#xff0c;比如过去20岁是年轻人&#xff0c;40岁中年人&#xff0c;60岁…...

16:00的面试,16:07就出来了,问的问题过于变态了。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到六月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40…...

VUE宝典之el-dialog使用

文章目录 &#x1f341;前言&#x1f341;el-dialog简介&#x1f341;el-dialog属性&#x1f341;el-dialog示例&#x1f341;父子组件值传递示例 &#x1f341;前言 el-dialog是Element UI库中的一个重要组件&#xff0c;用于在Vue应用程序中创建弹出框。它提供了一组实用的属…...

Cocos Creator:坐标系

Cocos Creator&#xff1a;坐标系 坐标系节点位置坐标转换v3.8 实现原理&#xff08;不想了解可以直接跳过&#xff09;简单示例&#xff1a;&#xff08;干货or解决方案在这里&#xff01;&#xff09; 锚点缩放和旋转 总结心得 在 Cocos Creator 3.8 中&#xff0c;节点坐标系…...

logback日志框架使用

依赖引入 <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.1.7</version> </dependency> 使用logback日志框架只需要引入以上即可&#xff0c;(我们平时使用较多的Slf4j…...

【八】python装饰器模式

文章目录 8.1 装饰器模式简介8.2 装饰器模式作用8.3 装饰器模式构成8.3.1 装饰器模式包含以下几个核心角色&#xff1a;8.3.2 UML类图 8.4 装饰器模式python代码实现8.4.1 基本装饰器的使用8.4.2 多个装饰器的执行顺序8.4.3 带返回值的装饰器的使用8.4.4 装饰器模式-关联类模式…...

Unity-小工具-LookAt

Unity-小工具-LookAt &#x1f959;介绍 &#x1f959;介绍 &#x1f4a1;通过扩展方法调用 gameObject.LookAtTarget&#xff0c;让物体转向目标位置 &#x1f4a1;gameObject.StopLookat 停止更新 &#x1f4a1;可以在调用时传入自动停止标记&#xff0c;等转向目标位置后自…...

TCP实现一对一聊天

一&#xff0c;创建类 二&#xff0c;类 1.ChatSocketServer类 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Sca…...

全面高压化与全面超快充,破解新能源汽车的时代难题

是什么让新能源车主感到疲惫与焦虑&#xff1f;是什么阻挡更多消费者选择新能源汽车&#xff1f;我们在身边进行一个简单的调查就会发现&#xff0c;问题的答案非常一致&#xff1a;充电。 充电难&#xff0c;充电慢的难题&#xff0c;始终是困扰新能源汽车产业发展&#xff0c…...

02 CSS基础入门

文章目录 一、CSS介绍1. 简介2. 相关网站3. HTML引入方式 二、选择器1. 标签选择器2. 类选择器3. ID选择器4. 群组选择器 四、样式1. 字体样式2. 文本样式3. 边框样式4. 表格样式 五、模型和布局1. 盒子模型2. 网页布局 一、CSS介绍 1. 简介 CSS主要用于控制网页的外观&#…...

MyBatis框架中的5种设计模式总结

前言 MyBatis框架中使用的5种设计模式分别是&#xff1a;1、建造者模式&#xff08;生成器模式&#xff09;。2、工厂模式。3、单例模式。4、代理模式。5、适配器模式。 1、建造者模式&#xff08;生成器模式&#xff09; 在MyBatis环境的初始化过程中&#xff0c;SqlSessio…...

ffmpeg相关命令

视频转码 dav转化为mp4格式 ffmpeg -i 2021-08-10.dav -codec copy 11.mp4二进制文件转为mp4格式 // -c:v 指定视频流编码器&#xff0c;不指定编码会默认用mp4这种容器的默认音视频编码进入编码 // copy&#xff1a;不重新编码直接copy源视频流ffmpeg -i 1701687125-4fc72a…...

锂电3V升12V1A升压芯片WT3209

锂电3V升12V1A升压芯片WT3209 WT3209是一款高功率密度全集成BOOST升压转换器&#xff0c;具备高效能解决方案。3V升12V1A,5V升12V1A WT3209内部集成的功率MOSFET管导通电阻为上管13mΩ和下管11mΩ&#xff0c;具备2A开关电流能力&#xff0c;并且能够提供高达12.6V的输出电压。…...

Unity 置顶OpenFileDialog文件选择框

置顶文件选择框 &#x1f32d;处理前&#x1f959;处理后 &#x1f32d;处理前 &#x1f959;处理后 解决方案...

oomall课堂笔记

一、项目分层结构介绍 controller层&#xff08;控制器层&#xff09;&#xff1a; 作用&#xff1a;负责输出和输入&#xff0c;接收前端数据&#xff0c;把结果返回给前端。 1.处理用户请求&#xff0c;接收用户参数 2.调用service层处理业务&#xff0c;返回响应 servi…...

Qt6.5类库实例大全:QFrame

哈喽大家好&#xff0c;我是20YC小二&#xff01;欢迎扫码关注公众号&#xff0c;现在可免费领取《C程序员》在线视频教程哦&#xff01; ~下面开始今天的分享内容~ 1. QFrame介绍 QFrame是Qt框架中的一个框架控件类&#xff0c;主要用于在图形用户界面(GUI)中创建框架&#…...

Java 数据结构篇-用数组、堆实现优先级队列

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 优先级队列说明 2.0 用数组实现优先级队列 3.0 无序数组实现优先级队列 3.1 无序数组实现优先级队列 - 入队列 offer(E value) 3.2 无序数组实现优先级队列 - 出…...

Reactor模型

目录 1.Reactor模型是什么2.Reactor 模型应用场景3.使用 Reactor 模型的软件4.Reactor 模型 与 Actor 模型 的关系 本文主要介绍Reactor模型基本概念以及应用场景。 1.Reactor模型是什么 Reactor模型是一种事件驱动的设计模式&#xff0c;用于处理服务请求&#xff0c;它是由…...

【SpringCloud】通过Redis手动更新Ribbon缓存来解决Eureka微服务架构中服务下线感知的问题

文章目录 前言1.第一次尝试1.1服务被调用方更新1.2压测第一次尝试1.3 问题分析1.4 同步的不是最新列表 2.第二次尝试2.1调用方过滤下线服务2.2压测第二次尝试2.3优化 写到最后 前言 在上文的基础上&#xff0c;通过压测的结果可以看出&#xff0c;使用DiscoveryManager下线服务…...

如何做好性能压测?压测环境设计和搭建的7个步骤你知道吗?

简介&#xff1a;一般来说&#xff0c;保证执行性能压测的环境和生产环境高度一致是执行一次有效性能压测的首要原则。有时候&#xff0c;即便是压测环境和生产环境有很细微的差别&#xff0c;都有可能导致整个压测活动评测出来的结果不准确。 1. 性能环境要考虑的要素 1.1 系…...

Qt12.13

...

目标检测YOLO系列从入门到精通技术详解100篇-【目标检测】SLAM(基础篇)(五)

目录 前言 几个相关概念 双目视惯雷达SLAM 相关工作 系统综述 视觉前端...

鸿蒙开发之页面与组件生命周期

一、页面间的跳转 创建文件的时候记得选择创建page文件&#xff0c;这样就可以在main->resources->profile->main_pages.json中自动形成页面对应的路由了。如果创建的时候你选择了ArkTS文件&#xff0c;那么需要手动修改main_pages.json文件中&#xff0c;添加相应的…...

Kotlin开发之低功耗蓝牙(引用三方库)的详解一

在我们工作中&#xff0c;如果涉及到软硬结合&#xff0c;经常会用到蓝牙&#xff0c;而蓝牙有两种&#xff1a;一种是普通的蓝牙&#xff0c;一种是低功耗的蓝牙&#xff0c;今天我们主要讲解的是低功耗蓝牙&#xff1a;主要根据第三方库进行的讲解 第一步&#xff1a;在使用…...