游戏引擎学习第92天

news/2025/2/9 6:02:32 标签: 游戏引擎, 学习, python

回顾大家鼓励做一些奇怪的正弦-余弦-角度的事情

首先提到目前正在处理一些图形编程的部分,尤其是涉及旋转的内容。虽然有一些跑题的情况,但今天将回归正轨,集中精力处理实际的任务。

黑板:介绍效果

当前的目标是实现旋转和缩放功能,之前已经有了一个基本的渲染系统,能够将艺术家绘制的位图显示在屏幕上,但只能以原始的方式显示,并无法对其进行任何操作。当前的渲染功能仅实现了透明度混合(alpha blending),即允许位图中的透明部分消失,显示背后的物体。

然而,为了更好地使用艺术作品,接下来的需求是能够对这些位图进行旋转和缩放等几何操作。这些操作对动画效果至关重要,特别是当需要动态变化时。例如,如果艺术家绘制了一些烟雾图案,希望它随着时间变大,这就需要使用缩放功能;如果绘制了一个投掷物并希望它在屏幕上旋转飞行,则需要旋转功能。除此之外,还可以做一些其他像是颜色变化的操作,但重点是几何变换,尤其是旋转和缩放。

对于这些图形操作,目标是尽可能高效地利用现有的艺术素材,因为艺术创作是一个耗时且成本高昂的过程。无论是支付给艺术家的费用,还是自己作为艺术家的时间,这些图形都需要被最大化利用。通过编程实现更高效的图形处理,可以让游戏制作在已有资源下更好地进行,提升整体效果。

黑板:我们如何做这个旋转和缩放?

接下来的步骤是讨论如何实现旋转和缩放。首先,从最基础的内容入手,关注“像素填充”这一问题。

前一天的工作重点是理解如何在坐标系统中进行旋转。我们已经实现了一个简单的系统,它允许我们绘制小方块,并能够围绕一个点进行旋转。为了演示这一点,可以运行程序,看它如何运行,实际上并不需要过于严肃,保持轻松愉快的态度。通过这个示例,展示了如何处理旋转,但接下来需要继续扩展和优化这些基础功能。

运行游戏

我们之前做的那个东西有点疯狂,原本是相对正常的,但因为有些人不断鼓励添加新功能,它就逐渐变得复杂起来。最初的代码其实是比较简单的,但随着不断的修改和添加,最终变得有些难以控制。这个过程虽然有些混乱,但也从中学到了很多。

去除这些荒谬的部分

先去掉所有混乱的部分,将其裁剪掉,以便回到最初的状态,避免那些奇怪的摆动和混乱的情况。目标是让系统恢复到一个合理的状态,而不是继续在复杂的修改上纠缠。调整后,可以看到当前的状态,其中负 x 轴、y 轴和 x 轴的计算仍然存在问题。发现 y 轴的长度似乎不对,可能是因为进行了不必要的缩放,实际上它已经被正确缩放了,因此不需要额外的调整。
在这里插入图片描述

看看坐标系统

当前的系统建立了一个坐标系,并可以观察到其旋转的效果。在这个坐标系内,放置了一个 4x4 的点阵,可以看到这些点随着坐标系一起旋转。这一过程的目的在于演示如何创建一个能够进行旋转和缩放的坐标系。

此外,还可以对其进行缩放操作,例如通过乘法调整其大小,使其变大或变小。然而,这种方法只解决了如何计算屏幕上的点的位置,使其在旋转和缩放时仍然保持正确的关系。

但当前的目标并不仅仅是处理单个点,而是希望能够操作整个位图,将其旋转并缩放后正确地渲染到屏幕上。因此,需要进一步研究如何对位图应用类似的转换,而不仅仅是坐标点的计算。

黑板:我们需要知道旋转矩形后哪些像素会被包含

当前的目标是进一步改进旋转和缩放的处理方式,使其能够适用于整个矩形区域,而不仅仅是计算单个点的位置。

在屏幕上,每个像素都对应一个具体的位置,而当前的方法仅能计算旋转和缩放后的一组坐标点,但无法确定当对整个矩形应用旋转和缩放后,哪些像素会被填充。因此,需要一个方法来确定旋转和缩放后的矩形在屏幕上的覆盖范围,并找出所有应该被填充的像素。

下一步的目标是构建一个能够绘制旋转和缩放后矩形的系统,并确保该矩形是实体填充的,而不仅仅是轮廓。这一过程分为两个主要阶段:首先,绘制一个可以旋转和缩放的实体矩形;其次,在该矩形内部填充正确的颜色,使其能够显示出预期的纹理。

在实现这一基础功能后,还需要进行进一步优化,以提高绘制效率和精度,但目前的重点是完成旋转和缩放矩形的基本渲染逻辑。

黑板:碰撞检测系统可能与像素填充相关

为了实现旋转和缩放后的矩形填充,可以借鉴碰撞检测的思路。碰撞检测与渲染在本质上存在诸多相似之处,二者都涉及对像素的判定和填充操作。

如果将屏幕上的像素视为一个个点,并判断某个像素是否应当被点亮,以形成旋转和缩放后的矩形,那么这个问题实际上就变成了一个“点是否落在旋转缩放矩形内”的测试,而这正是碰撞检测的一种常见形式。因此,可以利用碰撞检测的原理,判断屏幕上的像素是否属于旋转后的矩形区域。

如果此前的碰撞检测系统已经考虑了旋转,那么现在已经具备了所有需要的知识来完成这项任务。但由于当时的实现尚未涉及旋转,因此现在需要扩展对碰撞检测的理解,使其能够适用于旋转后的形状填充。一旦完成这一部分,未来还可以回过头来改进碰撞检测,使其能够从当前的实现中受益。

黑板:暂时忽略旋转和缩放

为了判断屏幕上的某个像素是否位于一个矩形内部,可以使用简单的边界检测方法。首先,不考虑旋转和缩放的情况,仅考虑与屏幕对齐的矩形。

假设矩形的边界由最小 x 值(min_x)、最大 x 值(max_x)、最小 y 值(min_y)和最大 y 值(max_y)定义,那么对于给定的像素点 (x, y),可以通过以下条件判断其是否位于矩形内部:

  • x 需要满足:min_x ≤ x < max_x
  • y 需要满足:min_y ≤ y < max_y

如果同时满足这两个条件,则说明该像素位于矩形内部,否则位于矩形外部。这种方法可以有效区分哪些像素属于矩形区域,哪些不属于。

这一逻辑基于包含规则的设定,确保边界上的点能够正确处理,例如左上角的点被包含在内,而右下角的点不包含,以防止像素重叠问题。

这个方法非常直观,只需要对每个像素执行简单的比较运算,因此高效且易于实现。在此基础上,可以进一步扩展以支持旋转和缩放的矩形判定。

黑板:遍历像素

为了填充未旋转和缩放的矩形,可以遍历屏幕上的所有像素,并对每个像素执行边界检测,以确定其是否位于矩形内部。然而,这种方法效率较低,因为会对所有像素执行冗余计算,即使许多像素不在矩形范围内。

为提高效率,可以采取更保守的方法,仅遍历大致包含矩形的区域,从而减少不必要的像素检查。进一步优化时,可以直接遍历矩形所覆盖的像素,而不是执行边界检测,从而避免额外的判断操作。这种方法能够精确确定矩形填充的像素,并显著提高绘制效率。

看一下DrawRectangle例程

之前编写的填充矩形的例程已经实现了优化,避免了不必要的像素检查。具体而言,该例程预先计算出矩形的边界,并仅遍历确定在矩形范围内的像素,而不是遍历整个屏幕并对每个像素执行额外的边界检测。

即便无法确定最小边界,也可以采用更宽泛的区域遍历,比如略大于矩形的区域,甚至遍历整个屏幕,在循环内部执行边界检测,仅填充符合条件的像素。这种方法虽然会增加一定的计算量,但仍然可以正确绘制矩形。

在这里插入图片描述

写一个函数,DrawRectangleSlowly,以最不高效的方式执行DrawRectangle

为了验证之前的假设,实现了一个极其低效的矩形绘制函数。该函数遍历屏幕上的所有像素,而不是仅限于矩形范围内的像素,并对每个像素执行边界检查,以确定是否需要填充颜色。

首先,该函数的参数与之前优化的版本保持一致,包括缓冲区、最小和最大坐标以及颜色信息。在实现过程中,不进行任何优化,也不提前调整缓冲区指针位置,而是从屏幕的起始位置开始遍历所有像素。

在遍历过程中,依次检查每个像素的 x 坐标是否在最小和最大 x 坐标范围内,以及 y 坐标是否在最小和最大 y 坐标范围内。如果满足条件,则填充该像素的颜色,否则保持不变。

此外,使用 32 位颜色填充像素,以符合当前代码框架,并且在最终版本中增加了一些现代化的代码风格调整,使其更加规范。
在这里插入图片描述

绘制一个位于坐标系统原点的矩形

该方法用于绘制矩形,但以极其低效的方式执行。通过遍历整个屏幕的像素,而不是仅限于矩形的区域,每个像素都进行边界检查,以决定是否需要填充颜色。这种方法的执行速度非常慢。

在应用该方法时,可以结合之前建立的坐标系统,其中包括 y 轴的处理方式等。在实际绘制时,不再使用网格模式,而是直接绘制一个矩形,并利用 x 轴和 y 轴的偏移量计算矩形的最小和最大坐标。

具体实现时,调用 DrawRectangleSlowly,提供目标缓冲区和颜色信息,同时根据 entry.originentry.x_axisentry.y_axis 计算最大坐标。为了确保计算正确,还需要添加 entry.origin 以获取矩形的完整边界范围。

在最终代码中,需要传递输出目标、颜色以及计算出的最小和最大坐标,以确保绘制逻辑正确无误。
在这里插入图片描述

在游戏中查看结果

理论上,这种方法应该可以正常工作,但仍然存在一些未处理的细节。

暂时停止旋转

需要先停止旋转,因为在旋转时并没有正确处理最小值和最大值。所以必须确保当前角度保持不变,保持稳定。
在这里插入图片描述

在这里插入图片描述

在事后用不同颜色绘制矩形

为了更清楚地区分和查看坐标点,决定将这些点绘制在矩形的上方,使用不同的颜色。这将帮助区分矩形本身与其他绘制的元素,以便更方便地进行调试和观察。
在这里插入图片描述

在这里插入图片描述

我们正在正确填充,但效率低下

通过遍历整个屏幕并检查每个像素是否位于矩形内部,成功实现了矩形的填充。尽管这种方法效率较低,因为需要检查所有像素,但它确保了矩形正确填充,保证了正确性。通过这种方式,矩形被准确地绘制在屏幕上,即使没有优化性能。

考虑限制DrawRectangleSlowly遍历的区域

可以通过限制遍历的区域,使得需要检查的像素数量减少,从而提高效率。这意味着只测试那些需要测试的像素,而不是遍历整个屏幕,最终达到优化性能的效果,同时保持矩形填充的正确性。

黑板:这如何帮助我们进行旋转和缩放?

对于每个屏幕上的像素,进行四个边界测试,确保每个像素都位于矩形的内部。这些测试分别检查像素是否在矩形的四个边界内,包括最小和最大x值,以及最小和最大y值。当需要旋转矩形时,理论上可以使用相同的方法,进行相应的旋转处理后,继续执行边界测试,从而确定像素是否在旋转后的矩形内。

黑板:我们不能只测试矩形的四个角吗?

可以通过测试矩形的四个边来判断一个像素是否在矩形内部,如果所有边的测试结果为真,就可以确定像素在矩形内部。然而,对于旋转和缩放后的矩形,仅测试四个角是不够的。为了处理旋转和缩放后的矩形,需要一种方法来生成旋转和缩放后的矩形的边界测试。这样,使用这些边界测试进行检测,就可以确认像素是否在旋转和缩放后的矩形内部。

黑板:如果没有帮助,我们本可以怎么做

要进行矩形的边界测试,只需要针对一个边进行测试,然后重复四次就可以了。首先,考虑一个边,例如从点P0到点P1。为了判断某个点是否在这个边的一侧,可以使用垂直向量与点的点积来实现。点积的结果决定了点P相对于边的位置。如果点积为正,表示点在边的某一侧;如果点积为负,则表示点在另一侧。这个过程可以通过计算点与边之间的点积来完成。

实际上,点积就是计算两个向量的内积,使用点P减去原点得到向量,再与边的垂直向量进行点积。对于不同的边,可以使用不同的垂直向量(例如X轴的垂直向量是Y轴,Y轴的垂直向量是X轴),通过简单的点积运算即可得出结论。只要检查点积是否为正,就可以确定点是否在边的正确一侧。

为了实现这一点,只需为每个边进行一次测试,四个边的测试就可以判断点是否在矩形内部。这是进行边界检测的基本方法,能够高效地判断点是否在矩形内,而不需要额外的计算。

实现其中一个边缘测试

在旋转矩形时,原本的填充方法只能按矩形的两个点来填充,这在矩形旋转后可能导致问题。当矩形旋转到某个角度时,原本的矩形边界测试会失败,导致无法正确填充矩形。为了解决这个问题,只需将原来的边界测试修改为基于实际矩形四个角点的边界测试。这样,旋转后的矩形也能通过正确的边界测试来判断是否在矩形内部,避免之前的问题。

这种方法是针对矩形的处理,目的是确保无论矩形如何旋转,都能准确地进行边界检测,并正确填充矩形区域。通过调整边界测试,使其适应矩形的四个角点,可以有效解决旋转带来的问题。
在这里插入图片描述

在这里插入图片描述

让DrawRectangleSlowly接受所有坐标系统信息

在更新矩形绘制的方法时,首先需要将原本的矩形绘制方法修改为能够处理矩形的原点、x轴和y轴的方式。这意味着要通过这些信息来计算矩形的最小值和最大值,并使用这些值进行绘制。然后,将这些参数传递给绘制函数,这样可以确保矩形的绘制在旋转和缩放时也能正常进行。

通过这种方法,绘制的矩形应该表现得符合预期,即在旋转或缩放时,矩形能够正确地填充和显示,直到达到正确的位置。更新后的代码应当保持与预期一致,确保矩形能按照新的参数进行绘制,而不是仅依赖于之前固定的边界。
在这里插入图片描述

让DrawRectangleSlowly使用点积进行边缘测试

在绘制矩形时,需要根据之前描述的边缘测试方法来测试每一个边。首先,定义四个边缘(Edge0、Edge1、Edge2 和 Edge3),然后对这些边进行测试。测试的目的是确保矩形的所有边都符合预期。

为了进行这些测试,可以通过一种方式,类似于点积的方式来进行边缘检测。每个边测试的逻辑是检查给定点是否在该边的一侧,比如测试点的 x 坐标是否大于某个最小值,从而确定该点是否在边的内侧。通过这种方式,可以高效地进行边缘检测,无需额外的计算,只需要基于已知的最小值来进行判断。

这种方法在测试过程中可以通过简化的逻辑表达,比如计算点坐标与最小坐标的差值并判断其符号,从而得出该点是否在指定区域内。这是通过对每个边进行单独测试,确保矩形的四个边都满足条件。

在这里插入图片描述

黑板:重新表述这些边缘测试

可以重新表述这些边缘测试,即使在直角矩形的情况下,也能使其更接近我们期望的方式。例如,之前我们有一个最小的 x 值(xMin),和一个 x 值,我们想知道该点是否位于这一边。

之前的做法是明确地写出测试条件,要求 x 值大于等于 xMin。但这实际上等同于执行点积运算。具体来说,执行 x 减去 xMin,会得到一个正值或负值,这就告诉我们该点位于边的哪一侧。然后,我们只需测试结果是否大于等于零,以确认点是否位于边的内侧。通过这种方式,能够更加简洁和直观地进行边缘检测。

改变测试方向

可以将上述内容以不同的方式表述,尽管方法简单,但没有必要过度复杂化。可以按照如下方式进行:首先计算 x 值减去最小 x 值(vMin.x),然后将其与最大 x 值(vMax.x)进行比较,确保结果小于零。接着,对于 y 值,进行类似操作,计算 y 减去最小 y 值,确保结果大于零,同时 y 值减去最大 y 值的结果要小于零。

这种方式与之前的方法相同,只不过通过简单的数学操作表达了相同的逻辑。由于坐标并没有进行旋转,因此不需要进行额外的点积计算,所有操作都可以直接在原始坐标系下进行。
在这里插入图片描述

注意,因为我们没有做点积,符号没有翻转

可以注意到,由于没有使用点积计算,符号没有翻转。因此,这里使用了不同的比较方式。通常情况下,我们会测试与不同方向的法线(即垂直向量)进行比较,这样可以始终测试大于或小于。然而,由于没有进行这些点积操作,符号没有发生翻转,因此我们需要通过对比差值来处理这个问题。

通过这种方式,可以轻松实现期望的效果,避免符号翻转所带来的复杂性。

让大于符号成为标准

可以选择将“大于”作为标准,使所有的比较始终使用“大于”运算符。例如,如果将边界测试都统一为“大于”,那么所有边的比较都会按照这种方式进行。这样就能保证在所有的测试中使用一致的比较方式,从而简化代码并确保一致性。
在这里插入图片描述

黑板:考虑表面法线指向外侧

可以考虑将边界方向设定为指向外部,因为通常在处理UV表面法线时,法线是指向外的。可以约定,正值表示位于形状外部,负值表示位于形状内部。这将作为我们的约定方式,有助于简化后续的处理。

将所有边缘函数改为负数

为了确保所有的边函数返回负值,需要反转某些测试的方向。特别是对于边界函数1和3,它们的方向会与其他边相反,因此需要通过简单的取反操作来调整其方向。例如,之前X - vMin.x的测试在某一边会给出正值,而在另一边给出负值,所以需要通过取反来调整方向。其他边界函数已经能够正确返回负值,因此无需修改。通过这些调整,结果与之前一样,但更加符合预期的方向。
在这里插入图片描述

加入点积

现在,只需进一步调整,进行一次点积计算。首先进行原点的减法操作,然后将结果调整回原来的方式。接下来需要生成合成的边界值,这些值将指向正确的方向。
在这里插入图片描述

黑板:那些垂直向量是什么?

现在,已确定 vMin 以及相关的向量。对于每条边的法向量,要求它们指向外侧,确保边界垂直指向外部。在最小值处,法向量为 (-1, 0),对于最大值处,法向量为 (1, 0)。其他边界的法向量分别为 (-1, 0)、(0, -1) 和 (0, 1)。这些法向量非常直观,可以直接从图示中读取。

实现这些向量

在这个过程中,首先确定了最小值和最大值的位置,分别对应的法向量为 (-1, 0) 和 (1, 0),同样的,y轴方向上的最小值和最大值对应的法向量分别为 (0, -1) 和 (0, 1)。接着,处理像素点时,需要进行向量的减法,具体是用当前像素点的坐标减去最小值和最大值的坐标。这是为了完成整个向量的运算,确保测试能够按照预期进行。这一步完成后,接下来的步骤就非常接近了。
在这里插入图片描述

在这里插入图片描述

现在我们进行完整的测试,看看在游戏中的效果

现在,已经开始执行完整的测试。可以看到,性能变得比较慢,因为每个屏幕上的像素都需要进行更多的运算。虽然在短期内可能会变得更加缓慢,但这也是正常的,优化后的性能会在之后得到提升。现在,系统依旧在执行相同的操作,只不过这次增加了对边缘的测试。
在这里插入图片描述

考虑修改边缘测试,使用旋转坐标系统的边缘

最终,只需要将这些额外的操作改为使用旋转后的实际边缘,这一步相对简单,不需要进行太多修改。接下来,创建 x 轴和 y 轴,考虑这些边缘的具体表现形式。

黑板:如何使用这些边缘

这里是 x 轴,这里是 y 轴。如果要测试第一个边缘,就把它当作第一个边缘来处理。为了说明清楚,尽量避免在描述中反复提到 x 和 y 的位置。现在,从原点开始进行操作。
在这里插入图片描述

摆脱vMin和vMax,准备使用这些边缘

现在可以这样做:去掉之前的 vMin 和 vMax,现在只使用原点 x 轴和 y 轴的数据。
在这里插入图片描述

黑板:我们的测试

首先需要做的是确定边界。假设这是黑板上的测试,测试的边是 x 轴,从原点到右侧一端。虽然在屏幕坐标系中可能是翻转的,影响不大,空间结构依然有效。现在我们有了原点,x轴也设置好了。接下来的任务是判断某个点是否位于这个边的正确一侧。因此,我们需要使用负y轴作为垂直方向,进行第一次测试。

实现这些测试

首先,测试会使用负 y 轴作为参考轴,基点选取原点,因为这个点在边界上,确保点 p 在相同的坐标空间中进行测试。接下来,对第二个边进行测试时,基点会是原点加上 x 轴的值,然后判断 x 轴是否指向外部。之后,第三个边的测试,基点为原点加上 x 轴和 y 轴,而测试轴是 y 轴,判断是否指向外部。最后,进行第四个边的测试,基点为原点加上 x 轴和 y 轴,而测试轴是负 x 轴,指向外部。这一过程是顺时针进行的。

这个算法的目标是判断当前像素是否在一个封闭形状内,具体来说,它通过计算当前像素与形状的边之间的点积来进行判断。算法使用的是一种常见的 半平面法(Half-Plane Test),根据形状的边的法向量与像素点相对位置的关系来判断像素是否在形状内。以下是对每个步骤的详细解释:

1. PixelP - OriginPixelP - (Origin + XAxis)

PixelP 是当前像素的位置(一个二维坐标),Origin 是形状的一个参考点(通常是形状的左下角或左上角),而 XAxisYAxis 分别代表沿着 X 轴和 Y 轴的单位向量。

  • PixelP - Origin:表示从参考点(Origin)到当前像素(PixelP)的向量。
  • Origin + XAxis:表示参考点(Origin)沿 X 轴偏移一个单位的点,通常是形状的右下角。
  • Origin + XAxis + YAxis:表示参考点沿 X 轴和 Y 轴各偏移一个单位后的点,通常是形状的右上角。
  • Origin + YAxis:表示参考点沿 Y 轴偏移一个单位的点,通常是形状的左上角。

2. Inner(PixelP - Origin, -YAxis) 和其他点积

Inner 函数计算的是两个向量的点积。点积的结果可以用于判断两个向量的相对方向:

  • Inner(PixelP - Origin, -YAxis):计算当前像素相对于 Origin 的向量与负 Y 轴的点积。这个值用来判断像素相对于形状的上边界的相对位置。若点积小于零,表示像素在边的下方。
  • Inner(PixelP - (Origin + XAxis), XAxis):计算当前像素相对于形状右边的向量与 X 轴的点积,用于判断像素是否在右边界的左侧。
  • Inner(PixelP - (Origin + XAxis + YAxis), YAxis):计算当前像素相对于形状上边界的向量与 Y 轴的点积,用于判断像素是否在上边界的下方。
  • Inner(PixelP - (Origin + YAxis), -XAxis):计算当前像素相对于形状左边界的向量与负 X 轴的点积,用于判断像素是否在左边界的右侧。

3. 为什么使用负法向量和正法向量?

  • 负法向量:例如 -YAxis-XAxis 表示从形状的外部看向边的方向。若点积为负值,说明像素在该边的外部。
  • 正法向量:例如 YAxisXAxis 表示从形状的外部看向边的方向,若点积为负值,说明像素在该边的外部。

4. 判断条件

if ((Edge0 < 0) && (Edge1 < 0) && (Edge2 < 0) && (Edge3 < 0))
  • 这部分检查四个边的点积值是否都小于零。如果都小于零,说明当前像素在形状的边界内部。具体来说,每个边的点积小于零表示该像素在该边的内侧。
  • 如果条件成立,表示该像素完全位于形状的内部,可以将该像素的颜色设为所需的颜色(例如白色)。

总结

这个算法通过半平面测试(利用边的法向量和点积)来判断一个像素是否位于一个封闭形状的内部。如果该像素位于所有边的内侧,则返回该像素位于形状内部。在形状的每一条边上,通过对像素和边的法向量的点积结果进行检查,决定该像素是否位于边的外部。

在这里插入图片描述

编译并查看游戏中的结果

通过按照步骤逐步进行,代码编译后,可以看到现在矩形的填充已经完成,而且支持旋转和缩放。无需改变已有的代码,只需要按照流程进行,就可以轻松实现这些功能。在代码中加入缩放时,矩形依然能够正确地旋转和缩放,效果完全正常。这意味着当前实现不仅支持旋转,还能够处理缩放,并且能够轻松扩展进行更多的操作。
在这里插入图片描述

改变缩放

如果想要引入缩放变化,可以采取类似前一天的方法,设置一个随时间变化的缩放因子。这样就可以在代码中看到随着时间变化,形状的缩放效果逐渐发生。通过这种方式,缩放的变化可以被动态地添加到已有的实现中。
在这里插入图片描述

黑板:优化这些测试

目前的实现非常慢,因为没有做任何优化,直接在整个屏幕上填充所有像素。为了提高效率,可以通过限制填充的区域来改善性能。只需计算出旋转形状的四个点的最小和最大x、y值,这样就能确定哪些像素可能会被填充。这种方法能够大幅缩小需要处理的像素区域,避免填充整个屏幕,从而提高性能。这是一个简单而有效的优化步骤,可以限制像素填充在实际需要的区域内。

实现优化的排除测试

在这个渲染过程的优化中,首先引入了旋转形状的四个关键点,并计算它们的最小值和最大值(即 x 和 y 的边界)。这样可以限制填充区域,仅在包含形状的像素区域内进行渲染,而不需要填充整个屏幕。

接下来,通过对四个关键点进行循环,计算出 x 和 y 的最小和最大值。这些最小和最大值决定了形状实际影响的区域,只需要在这个区域内进行像素填充,从而避免对整个屏幕的填充,节省了计算资源。对于每个关键点,计算其最小值和最大值时,使用 floorceil 函数来确定最接近的整数值,确保填充区域的边界适当。

最后,为了避免超出屏幕边界,需要进行裁剪。通过对 x 和 y 的最小值和最大值进行检查,确保它们不会超出屏幕的范围。这个裁剪过程通过简单的条件语句实现,比如将最小值限制为 0,将最大值限制为屏幕宽度和高度。

通过这种方式,渲染过程中的效率大大提升,因为只处理实际需要渲染的区域,而不是整个屏幕。

检查代码

通过测试地面检测,首先将x的最小值设定为可能的最大正值。然后,逐步遍历每个索引点,首先对它们进行取整处理,得到它们的地板值和天花板值。接下来,比较当前的指数值与地板值,如果当前指数值大于地板值,则接受新的地板值。这样可以保证在处理过程中,x的最小值不断向下移动。对于y值也是类似的处理方式,当找到更低的y值时,接受更低的y值。同样地,如果x的最大值小于天花板值,则接受该天花板值。

重新启用边缘测试,并考虑进一步的工作

现在可以看到,矩形的选择已经正确进行了测试。如果重新启用测试,结果会是正确填充的区域,虽然填充速度依然不快,但相比之前已经有了显著提升。尽管仍有许多内容需要讨论,工作尚未完成,但时间紧迫,已经接近一小时的编程时间。接下来会涉及到一些像素相关的内容,比如像素中心的定义等细节问题,还有许多其他琐碎的部分需要处理。可以简化一些表达式,不必按最初的方式执行,但计划明天继续从这里开始,清理一些细节,朝着完成完整的功能迈进。
在这里插入图片描述

在这里插入图片描述

你的电脑配置是什么?

这台计算机虽然已经很老了,但在当时属于高端配置,所以性能方面有些难以衡量。它的处理器是大约五到六年前的Zon系列,型号是W55 80。虽然现在看来它的性能可能不如大多数现代计算机,但由于还未深入涉及性能优化,目前的任务对这台电脑的要求不高。即便如此,处理这些操作的计算机不需要特别强大的性能,因为目前做的只是一些简单的矩形操作,效率低也不影响整体进程。

旋转填充加速后,我是不是应该觉得很兴奋?

当看到旋转操作变得更快时感到兴奋,这其实是很正常的反应。可能这意味着应该开始优化代码了。如果对这些小细节感到兴奋,说明可能会在编程上做得很好。编程不仅仅是技术,它还涉及对这些细节的热情。如果不对这些事情感到激动,可能就不适合做程序员,因为做自己感兴趣的事是最重要的。

这就像优秀的篮球运动员,不仅仅是身体条件,更重要的是热爱投篮的感觉,因为只有喜欢这项活动,才能投入时间和精力去练习并做得更好。编程也是一样,能够喜欢并享受其中的小细节,这种热情是持续进步的动力。

如果扭曲坐标轴还有效吗?

如果对坐标轴进行倾斜,原本的坐标轴依然能起作用,但不能直接采用原本使用的简化方法。倾斜坐标轴后,它们将不再相互垂直,因此不能再简单地将坐标轴视为垂直向量。为了正确支持倾斜,必须显式计算垂直向量,而不是直接使用原本的简化方法。倾斜操作类似于将y轴进行角度变换,使得其不再是90度的角度,从而导致原本的垂直关系被打破。

虽然这样会导致不准确的测试结果,但解决方法很简单,只需要重新计算并确保坐标轴的垂直关系。这样,就可以处理坐标轴倾斜时的正确行为,确保测试正确进行。

在这里插入图片描述

在这里插入图片描述

实现一个正确的垂直向量

为了计算正确的垂直向量,已经展示了如何做到这一点,并且提到可以使用一个叫做“Perp”的操作符。接下来,我们将在代码中实际应用这个操作符。通过应用 Perp 操作符,可以从坐标轴得到其垂直向量,然后用这个垂直向量代替原来的坐标轴。

具体来说,在计算过程中,不再直接使用负的 x 轴或 y 轴,而是使用 x 轴或 y 轴的垂直向量。通过这种方式,可以实现更加准确的计算。

但在实现过程中,要特别注意负号的使用,要确保正确使用垂直向量(Perp)而不是直接使用坐标轴。需要进一步思考,确保垂直向量的方向没有出错,这样理论上就可以正确运行了。
在这里插入图片描述

黑板:我们希望Perp如何工作

在这一部分,目标是正确处理坐标轴的方向和垂直向量。首先,给定原点和 x 轴点,计算 x 轴的垂直向量。为了确保正确的方向,需要取 x 轴垂直向量的负值。

接着,对于 y 轴方向,需要计算 y 轴的垂直向量,向量的方向应该是 y 轴垂直向量的负值。通过这种方式,可以确保各个方向的垂直向量指向正确。

最终,处理完这些垂直向量后,就能确保所有的方向和计算都正确,并能按预期的方向进行。
在这里插入图片描述

在这里插入图片描述

下采样位图比上采样慢吗(更多的颜色点需要融合)?

一般来说,下采样比特图会比上采样慢,这是因为需要更多的颜色点来进行混合。然而,通常不会对比特图进行超过两倍的下采样。相反,通常使用的是多级纹理映射(mipmap)。

黑板:下采样和上采样位图

通常情况下,下采样和上采样在缩放因子介于0.5x到2x之间时是同样复杂的。下采样时,会通过取一组像素(例如四个像素)并计算它们的平均值,常用的就是盒状滤波器。而上采样时,需要对这些像素进行类似的处理,计算加权平均值来填充新的像素区域。

问题出现在当缩小因子大于2x时,比如降到0.25x或更小,这时会更复杂。为了避免处理这种极小像素的复杂性,通常会采用多级纹理映射(mipmap)等技术来简化计算。例如,如果要将图像降至仅一个像素,就需要对所有相关像素进行读取并计算出一个平均值。这会涉及到大量计算,但通过技术手段可以有效优化这一过程。

黑板:Mipmap

虽然看起来下采样和上采样需要大量的工作,但实际上并不会那么复杂,因为可以通过预计算多级纹理映射(mipmap)来优化这个过程。对于每个位图,通常会先计算出不同尺寸的版本,例如从256x256的位图开始,接着计算128x128、64x64、32x32、16x16、8x8、4x4、2x2,最后到1x1。

当进行缩放时,如果从原始尺寸缩小到一个更小的尺寸,比如30x30,就会选择一个与目标尺寸最接近的mipmap层级(例如32x32)。这样就可以确保每次采样的区域仅限于4个像素,而不是从更大的区域采样。

然而,虽然mipmap减少了计算量,通常还会使用三线性过滤来进一步优化。三线性过滤会从高一层和低一层的mipmap中各采样一些像素,并将结果进行混合,这样可以避免在不同层级之间切换时出现明显的跳跃现象,提升渲染效果的平滑度。

最终,你会在矩形渲染中实现抗锯齿吗,还是这对CPU来说太过了?

最终,是否会在矩形的渲染中实现抗锯齿,答案是会实现,但实际上并不需要对矩形本身进行抗锯齿处理。原因在于,当从mipmap(多级纹理映射)中采样时,实际上会自动获得抗锯齿效果,因此无需使用多重采样。

如果需要绘制的是实际形状且其边缘是可见的,那么这时才需要考虑抗锯齿处理。

黑板:纹理采样处理抗锯齿的Alpha位图

为了更具体地说明,若我们处理一个具有零透明边框的位图,实际上通过纹理采样,我们会自动获得抗锯齿效果。纹理采样会帮助处理抗锯齿,而无需额外的处理。抗锯齿效果由纹理采样自带,我们会在后续的阶段展示具体如何实现。

但是,如果我们绘制的是一个实心矩形且没有透明度(Alpha),那么纹理采样就无法为我们提供抗锯齿效果。为了处理这种情况,图形卡有像MSAA(多重采样抗锯齿)这样的技术。多重采样并不是用来对位图进行抗锯齿处理的,位图边缘的抗锯齿是由纹理采样自动处理的。多重采样主要用于平滑物体的边缘。通过在像素内部多次采样,计算通过边缘的样本,从而平滑边缘。

由于我们始终使用带有透明轮廓的位图进行处理,不存在需要额外抗锯齿处理的边缘。因此,不需要多重采样等技术,也无需关心抗锯齿问题。纹理采样本身就为我们提供了抗锯齿效果,这让渲染过程更加高效。

如果我们在渲染3D形状并且这些形状由多边形定义边界,那么就需要考虑多重采样等技术来处理抗锯齿问题。在我们的情况下,由于不涉及这种复杂的3D渲染工作,所以不必担心这些问题。

看看我们这个搞笑的小形状,回顾一下进展

制作的这个小形状实际上非常有趣,虽然看起来简单,但在短短两天的时间里,我们已经完成了许多基础工作。现在距离实现旋转和缩放位图并不远了。实际上,明天可能就能展示如何处理旋转和缩放位图,尽管这个过程可能会有些冗杂。之后会对所有的数学步骤进行回顾,确保操作是正确的,避免任何问题。

这一切并不复杂,真正的挑战在于如何一步步积累知识,并在合理的方式下学习这些内容。一旦理解了背后的原理,整个过程就变得相对简单了。实际操作时,过程看起来并没有想象中那么复杂。它只是通过一些小的步骤来逐步定义边缘,最终实现目标。这些步骤积累在一起,便能完成看似复杂的任务。

当然,这一切能够顺利进行,得益于多年来图形学研究人员的成果。如果没有他们的工作,现在的我们就无法这么快速地理解和应用这些技术。因此,能够直接利用他们的研究成果,我们能够快速解决问题并继续前进。

历史上,我们有没有在CPU渲染中实现抗锯齿?

关于是否曾经在计算机图形渲染中实现过抗锯齿,历史上确实有进行过抗锯齿处理,尤其是在某些特定问题领域中。然而,如果谈论的是游戏软件中的3D渲染,答案并不明确,可能没有广泛使用过抗锯齿技术。

实际上,实现抗锯齿并不是特别昂贵,特别是在软件中实现时,虽然它需要一定的计算开销,但主要集中在实际的边缘部分。对于那些没有边缘的区域,就不需要计算抗锯齿,因此抗锯齿的计算开销主要取决于场景中的边缘复杂性——即有多少边缘通过像素区域。

更为昂贵的部分通常是纹理采样,这也是大多数图形应用的性能瓶颈,尤其是在软件光栅化中,可能无法与GPU的速度匹敌。这就是为什么在2D游戏中使用GPU的原因,GPU能够更高效地处理纹理采样。尽管GPU处理纹理采样更好,但通过一些优化方法,如使用纹理平铺,也许能够在一定程度上绕过这个问题。

如果真的需要实现抗锯齿,即使我们没有使用,它也不会是最大的性能瓶颈。纹理采样可能会继续占据性能的主导地位,而抗锯齿即使需要实现,其性能开销也可能不会像纹理采样那样显著。

将更多的工作推迟到明天,可能会有一个行动计划

今天的工作进展顺利,完成了旋转和缩放形状的填充任务。明天将继续工作,首先用一个简单的位图填充并展示其效果,接下来,还会提到关于凸形状的包含测试,这种测试可以用于检查所有边缘是否都在形状内部,适用于任意凸形状。如果需要,也可以扩展到凹形状,但目前不需要,还是集中使用加速的方式进行。

整个过程的工作非常有趣,尽管碰撞检测通常是一个难题,但这次编程经历非常愉快,接下来的任务也很期待。如果有兴趣跟进,可以访问相关平台,获取源代码和预定游戏版本。


http://www.niftyadmin.cn/n/5845648.html

相关文章

MySQL数据库(七)SQL 优化

一 插入数据 采用方法 1 批量插入 2 手动提交事务 3 主键顺序插入 4* 使用load插入指令数据 二 主键优化 1 数据组织方式 在InnoDB存储引擎中&#xff0c;表中的数据都是根据主键顺序组织存放的&#xff0c;这种存储方式的表称为索引组织表 2 页分裂 页可以为空也可…

Ubuntu 多版本 gcc 配置常用命令备忘

用的频率不高&#xff0c;总忘记具体参数 1&#xff0c;安装多版本 gcc 以 gcc-11 和12 为例&#xff1a; sudo apt-get install gcc-11 gcc-12 sudo apt-get install gcc-11 gcc-12 2&#xff0c;配置多版本 gcc gcc 与 g 一起配置进数据库中&#xff1a; sudo update-a…

.NET 使用 HttpClient 从 URL 下载任何类型的文件数据

使用 HttpClient 类从 Internet URL/URI 下载文件&#xff1b;用 C# 编写。 本文与.NET Core 3.1、.NET 5、.NET 6和.NET 8兼容。此代码与ASP.NET Core Web 服务器应用程序同样有效。 以下代码将从 Internet URL 下载任何类型的数据&#xff0c;如果已压缩则解压缩&#xff0c…

如何使用Xcode进行iOS应用开发?

iOS应用开发是现代移动应用开发领域的重要组成部分&#xff0c;而Xcode作为Apple官方推荐的集成开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供了开发、调试、测试和部署iOS应用所需的一切工具。如果你是一名刚入门的iOS开发者&#xff0c;或者你准备开始开发自…

【C++】多态详细讲解

本篇来聊聊C面向对象的第三大特性-多态。 1.多态的概念 多态通俗来说就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。 编译时多态&#xff1a;主要就是我们前⾯讲的函数重载和函数模板&#xff0c;他们传不同类型的参数就可以调⽤不同的函数&#xff0c;通…

常用数据结构之String字符串

字符串 在Java编程语言中&#xff0c;字符可以使用基本数据类型char来保存&#xff0c;在 Java 中字符串属于对象&#xff0c;Java 提供了 String 类来创建和操作字符串。 操作字符串常用的有三种类&#xff1a;String、StringBuilder、StringBuffer 接下来看看这三类常见用…

基于JavaWeb的在线美食分享平台(源码+lw+部署文档+讲解),源码可白嫖!

摘要 本在线美食分享平台采用B/S架构&#xff0c;数据库是MySQL&#xff0c;网站的搭建与开发采用了先进的Java进行编写&#xff0c;使用了数据可视化技术、爬虫技术和Spring Boo框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。前台主要功能包括&…

Spring Boot:简化 Java 开发的利器

Spring Boot&#xff1a;简化 Java 开发的利器 摘要&#xff1a; Spring Boot 作为 Java 开发领域的明星框架&#xff0c;以其简化配置、快速开发的特性深受开发者喜爱。本文将带你走进 Spring Boot 的世界&#xff0c;从核心优势、常用功能、项目结构、运行原理、最佳实践等方…