Nintendo DS Console GPU和有趣的功能


我想向您介绍Nintendo DS GPU控制台的操作,它与现代GPU的区别,并就为什么在模拟器中使用Vulkan代替OpenGL不会带来任何优势表达我的看法。

我不是很了解Vulkan,但据我所知,我很清楚Vulkan与OpenGL的不同之处在于它的工作级别较低,从而允许程序员管理GPU内存和类似的东西。 这对于模拟使用专有图形API的更现代的控制台很有用,这些图形API提供了OpenGL中不可用的控制级别。

例如,blargSNES硬件渲染器-其诀窍之一是在使用不同颜色缓冲区进行的某些操作期间,使用了一个深度/模板缓冲区。 在OpenGL中,这是不可能的。

此外,在应用程序和GPU之间保留的垃圾更少,这意味着如果正确实现,性能将更高。 尽管OpenGL驱动程序对标准用例甚至特定游戏都进行了优化,但在Vulkan中,应用程序本身首先应该写得很好。

就是说,实质上,“更大的责任伴随着巨大的力量”。

我不是3D API专家,所以让我们回到这一点。 我所知道的是:GPU控制台DS。

已经写了几篇有关其各个部分的文章( 关于其复杂的四边形关于视口的废话关于光栅化器的有趣功能以及关于抗锯齿的惊人实现 ),但是在本文中,我们将把设备作为一个整体来考虑,但要考虑所有细节。 至少这就是我们所知道的。

GPU本身是一个相当古老且过时的硬件。 每帧限制为2048个多边形和/或6144个顶点。 分辨率为256x192。 即使将其增加三倍,性能也不会成为问题。 在最佳条件下,DS每秒可以输出多达122880个多边形,这对于现代GPU的标准而言是荒谬的。

现在,让我们继续介绍GPU的细节。 从表面上看,它看起来很标准,但是其工作的深层内容与现代GPU的工作截然不同,这使得某些功能的仿真更加复杂。

GPU分为两部分:几何引擎和渲染引擎。 几何引擎处理生成的顶点,构建多边形并对其进行转换,以便您可以将它们传递给渲染引擎,渲染引擎(您猜对了)在屏幕上绘制所有内容。

几何引擎


相当标准的几何输送机。

值得一提的是,所有算法都以定点整数执行,因为DS不支持浮点数。

几何引擎完全以编程方式进行仿真(GPU3D.cpp),也就是说,它不适用于我们用于渲染图形的内容,但是无论如何我都会告诉您更多信息。

1.改造和照明。 使用4x4矩阵集转换所得的顶点和纹理坐标。 除顶点颜色外,还应用照明。 这里的一切都非常标准,唯一的非标准是纹理坐标的工作方式(1.0 =一个DS纹素)。 还值得一提的是整个矩阵堆栈系统,它们在某种程度上是glPushMatrix()的硬件实现。

2.配置多边形。 转换后的顶点被组装成多边形,可以是三角形,四边形(四边形),三角形的条纹或四边形的条纹。 四边形是本地处理的,不会转换为三角形,这是一个很大的问题,因为现代GPU仅支持三角形。 但是,似乎有人想出了我需要测试的解决方案

3.放下。 可以根据屏幕上的方向和所选的剔除模式来处理多边形。 也很标准的方案。 但是,我需要弄清楚四边形的工作原理。

4.截断。 排除可见范围之外的多边形。 部分超出此区域的多边形将被截断。 此步骤不会创建新的多边形,而是将顶点添加到现有的多边形。 实际上,这6个截断平面中的每一个都可以为多边形添加一个顶点,也就是说,我们最多可以得到10个顶点。 在渲染引擎部分,我将告诉您我们如何处理此问题。

5.转换为视口。 X / Y坐标将转换为屏幕坐标。 Z坐标将转换为适合24位深度缓冲区的间隔。

有趣的是W坐标的处理方式:将它们“规格化”为适合16位间隔。 为此,将获取多边形的每个W坐标,如果它大于0xFFFF,则将其向右移动4个位置以适合16位。 相反,如果坐标小于0x1000,则它将向左移动,直到落入间隔中。 我想这是获得良好间隔的必要条件,这意味着在插值过程中具有更高的精度。

6.排序。 对多边形进行排序,以便首先绘制半透明的多边形。 然后根据其Y坐标(是)对它们进行排序,这对于不透明和可选的半透明多边形是必需的。

此外,这也是限制2048个多边形的原因:为了进行排序,它们需要存储在某个位置。 有两个内部存储库分配用于存储多边形和顶点。 甚至还有一个寄存器,用于报告存储了多少个多边形和顶点。

渲染引擎


从这里开始乐趣!

配置完所有多边形并对其进行排序后,渲染引擎将开始工作。

第一件有趣的事情是它如何填充多边形。 这完全不同于现代GPU的工作,现代GPU执行图块填充并使用三角优化算法。 我不知道它们是如何工作的,但是我看到了在3DS控制台GPU中是如何完成的,并且一切都基于其中的图块。

尽管如此,在DS上,渲染是在栅格字符串中完成的。 开发人员必须这样做,以便可以与老式的二维平铺引擎并行执行渲染,而老式的二维平铺引擎可以在栅格线上进行绘制。 有一个带有48条栅格线的小缓冲区,可用于调整某些栅格线。

栅格化器是基于栅格字符串的凸多边形的渲染器。 它可以处理任意数量的顶点。 如果将非凸面或具有相交边的多边形传递给它,则它可能会渲染不正确,例如:


多边形是蝴蝶。 一切都是正确和宏伟的。

但是,如果我们把它转过来呢?


哎呀

这是什么错误? 让我们绘制原始多边形的轮廓以找出:


渲染器只能填充每条光栅线一个间隙。 它定义了从最高峰开始的左右边缘,并跟随这些边缘直到遇到新的峰。

在上面显示的图像中,他从最上层的顶点开始,即左上角,然后继续填充,直到到达左边缘的末端(左下顶点)。 他不知道边缘相交。

此时,他在左边缘搜索下一个顶点。 有趣的是,他知道他不需要采用高于当前顶点的顶点,并且知道左右边缘已互换。 因此,它会继续填充直到填埋场结束。

我将再添加一些非凸多边形的示例,但是我们将偏离本主题。

让我们更好地了解Gouraud底纹和纹理如何在任意数量的顶点上工作。 有重心算法用于沿三角形插值数据,但是...在我们的情况下,它们不适合。

DS渲染器在这里也有自己的实现。 一些更有趣的图像。


多边形的顶点是点1、2、3和4。数字与实际遍历顺序不对应,但是您可以理解其含义。

在当前栅格线中,渲染器定义直接围绕边缘的顶点(如上所述,它从最顶部的顶点开始,然后经过边缘直到它们完成为止)。 在我们的例子中,左边的顶点是1和2,右边的顶点是3和4。

边缘的斜率用于确定间隙的极限,即点5和6。在这些点上,基于边缘中的垂直位置(或边缘的水平位置,其斜率主要沿着X轴)来插入顶点的属性。

然后,对于间隙中的每个像素(例如,对于点7),从先前在点5和6处计算出的属性中插入基于间隙内X位置的属性。

在此,为了简化工作,所有使用的系数都等于50%,但是含义很清楚。

我将不讨论属性插值的细节,尽管对此进行编写也将很有趣。 实际上,从角度来看,这是正确的插值方法,但是它具有有趣的简化和功能。

现在,让我们谈谈DS如何填充多边形。

他使用什么填充规则? 这里还有很多有趣的东西!

首先,对于不透明和半透明多边形有不同的填充规则。 但最重要的是,这些规则逐像素应用 。 半透明多边形可以具有不透明像素,并且它们将遵循与不透明多边形相同的规则。 您可以猜测,要在现代GPU上模拟这些技巧,需要多次渲染。

另外,不同的多边形属性可以各种有趣的方式影响渲染。 除了相当标准的颜色和深度缓冲区外,渲染器还具有跟踪各种有趣事物的属性缓冲区 。 即:多边形ID(分别用于不透明和半透明的多边形),像素的半透明性,是否需要应用雾,是否将此多边形直接指向或从相机射向(是的,也是如此)以及像素是否位于多边形的边缘上。 也许还有其他东西。

模拟这样一个系统的任务将是不容易的。 普通的现代GPU的模板缓冲区限制为8位,对于存储属性缓冲区的所有内容而言,这还远远不够。 我们需要提出一个棘手的解决方法。

让我们弄清楚:

*深度缓冲区更新:对于不透明像素是必需的,对于半透明像素是可选的。

*多边形ID:为多边形分配了6位ID,可用于多种用途。 不透明的多边形ID用于标记边缘。 半透明多边形的ID可用于控制将在何处绘制:如果多边形ID与属性缓冲区中已有的半透明多边形的ID相匹配,则不会绘制半透明像素。 同样,两个多边形ID同样用于控制阴影渲染。 例如,您可以创建覆盖地板但不覆盖角色的阴影。

(注意:阴影只是模板缓冲区的一种实现,在这里没有什么可怕的。)

值得注意的是,渲染半透明像素时,将保存不透明多边形的现有ID以及最后一个不透明多边形的边缘标记。

*雾标志:确定是否对此像素应用雾遍。 更新它的过程取决于传入的像素是不透明还是半透明。

*前线标志:这里有问题。 看一下屏幕截图:


毁灭之沙,这个游戏的画面是一套技巧。 它们不仅更改了Y坐标以影响Y排序。 此屏幕快照中显示的屏幕可能是最糟糕的。

它使用深度测试的边界情况:如果游戏在朝向远离相机的多边形的不透明像素之上绘制一个注视着相机的多边形,则比较函数“小于” 采用相等的值 。 是的,完全正确。 并且所有多边形的Z值为零。 如果您不模拟此功能,则屏幕上将缺少某些元素。

我认为这样做是为了使对象的正面始终在背面可见,即使它们是如此平坦以至于Z值相同。 通过所有这些技巧,DS渲染器类似于DOS时代渲染器的硬件版本。

尽管如此,通过GPU模拟这种行为非常困难。 但是,还有其他类似的深度测试边界情况,也需要进行测试和记录。

*肋骨标志:渲染器跟踪多边形边缘的位置。 它们用于最后一遍,即在标记边缘和抗锯齿时。 对于禁用抗锯齿的不透明多边形填充,还有一些特殊规则。 下图说明了这些规则:


注意:线框仅通过填充边缘来渲染! 非常聪明的举动。

关于深度缓冲的另一个有趣说明:

DS上有两种可能的深度缓冲模式:Z缓冲和W缓冲。 这似乎是很标准的,但前提是您不赘述。

* Z缓冲使用转换为适合24位深度缓冲间隔的Z坐标。 Z坐标线性插值在多边形上(有一些奇数,但不是特别重要)。 这里也没有非标准的东西。

*在W缓冲中,“原样”使用W坐标。 现代GPU通常使用1 / W,但DS仅使用定点算法,因此使用倒数并不十分方便。 尽管如此,在此模式下,使用透视校正对W坐标进行插值。

这是最终渲染过程的样子:

*边缘标记:已设置边缘标记的像素将分配给表格中的颜色,并基于不透明多边形的ID进行确定。

它们将是多边形的彩色边缘。 值得注意的是,如果在不透明多边形的顶部绘制了半透明多边形,则多边形的边缘仍将被着色。

截断原理的副作用:多边形与屏幕边界相交的边界也将被着色。 例如,您可以在Picross 3D的屏幕截图中注意到这一点。

* fog:根据用于索引雾密度表的深度值将其应用于每个像素。 您可能会猜到,它适用于在属性缓冲区中设置了雾标志的那些像素。

*抗锯齿(平滑):应用于(不透明)多边形的边缘。 根据渲染多边形时边缘的斜率,计算像素覆盖率值。 在上一遍中,使用我在上一篇文章中描述的棘手机制将这些像素与下方的像素混合。

抗锯齿不应该(也不能)以这种方式在GPU上进行仿真,因此这在这里并不重要。

除非将边缘标记和抗锯齿应用于相同的像素,否则它们仅获得边缘大小,但具有50%的不透明度。

我似乎已经或多或少地描述了渲染过程。 我们没有深入研究混合纹理(将顶点和纹理颜色组合),但是可以在片段着色器中对其进行仿真。 如果我们找到一种通过属性缓冲区围绕整个系统的方法,则同样适用于边缘标记和雾化。

但总的来说,我想传达以下内容:OpenGL或Vulkan(以及Direct3D,Glide或其他任何东西)在这里无济于事。 我们现代的GPU具有足够的能力来处理原始多边形。 问题是栅格化的细节和特征。 而且甚至不关乎像素的理想性,例如,仅查看DeSmuME仿真器的问题跟踪器即可了解开发人员在通过OpenGL渲染时遇到的问题。 我们还必须以某种方式处理这些相同的问题。

我还注意到,使用OpenGL将允许我们将仿真器移植到Switch上(因为名为Hydr8gon的Github用户开始在Switch上为我们的仿真器创建端口 )。

所以...祝我好运。

Source: https://habr.com/ru/post/zh-CN429160/


All Articles