NES图形如何排列?

图片

Nintendo Entertainment System(NES)家用控制台于1983年推出,是一款廉价但功能强大的机器,取得了惊人的成功。 使用图像处理单元(PPU),该系统可以为那些时代创建令人印象深刻的图形,即使在今天,在适当的环境下看起来也相当不错。 最重要的方面是内存效率-创建图形时,我们必须以尽可能少的字节进行管理。 但是,与此同时,NES为开发人员提供了功能强大且易于使用的功能,使它在较旧的家用游戏机中脱颖而出。 了解了创建NES图形的原理后,您可以感受到系统的技术完善性,并意识到现代游戏开发人员的工作有多容易。

NES背景图形由四个独立的组件组成,它们的组合形成了我们在屏幕上看到的图像。 每个部分负责一个单独的方面。 颜色,布局,原始像素图形等。 这样的系统看起来不必要地复杂和麻烦,但是最终它更有效地使用了内存,并允许用少量的代码创建简单的效果。 如果您想了解NES图形,那么这四个组成部分将是关键信息。

本文假定您熟悉计算机数学,尤其是8位= 1个字节,而8位可以表示256个值。 还需要了解十六进制表示法的工作原理。 但是即使没有这些技术知识,这篇文章也可能看起来很有趣。

简短评论



上图是《恶魔城》(Castlevania)的第一幕(1986年)的图像:通往城堡的大门,游戏将在此举行。 该图像尺寸为256×240像素,并使用10种不同的颜色。 要在内存中描述此图像,我们必须利用有限的调色板,并通过仅存储少量信息来节省空间。 一种简单的方法是使用索引调色板,其中每个像素的体积为4位,即将2个像素放在一个字节中。 这将需要256 * 240/2 = 30720字节,但是正如我们将很快看到的那样,NES可以更有效地应对这项任务。

NES图形主题中的主要概念是图块和块[1]。 图块是8×8像素的区域,块是16×16像素的区域,并且每个块都绑定到具有相同像元大小的网格。 添加这些网格后,我们可以看到图形的结构。 这是城堡的入口,带有双倍网格。


在此网格中,块以浅绿色显示,而图块以深绿色显示。 沿轴的标尺具有十六进制值,可以添加该十六进制值来查找位置。 例如,状态栏中的心脏为$ 15 + $ 60 = $ 75,十进制为117。每个屏幕包含16×15块(240)和32×30块(960)。 现在,让我们看看如何描述该图像并从原始像素图形开始。

CHR


CHR结构描述没有颜色和位置的“原始”像素图形,并设置在图块中。 整个存储器页面包含256个CHR磁贴,每个磁贴的深度为2位。 以下是心脏图形:


以下是CHR [2]中的描述:

出现心跳

这样的描述每个像素占用2位,即大小为8×8时,结果为8 * 8 * 2 = 128位= 16字节。 然后整个页面占用16 * 256 = 4096字节。 这是图像中来自恶魔城的所有CHR。


回想一下,填写一张图像需要960个图块,但CHR仅允许256个图块。这意味着大多数图块平均重复进行3.75次,但更经常的是仅使用其中的少量图块(例如,空背景,单色图块)或重复模式)。 来自恶魔城的图像使用了许多空瓦以及纯蓝色。 要查看如何分配图块,我们使用名称表。

命名表


名称表为屏幕上的每个位置分配一个CHR文件,总共960个,每个位置以一个字节指定,即整个名称表最多960个字节。 从左到右,从上到下按顺序分配图块,并对应于通过添加上面所示标尺的值而找到的计算位置。 也就是说,左上角的位置是$ 0,右边的位置是$ 1,下面的位置是$ 20。

命名表中的值取决于CHR的填充顺序。 这是选项之一[3]:


在这种情况下,心脏(位置$ 75)的值为$ 13。

接下来,要添加颜色,我们需要选择一个调色板。

调色板


NES有64种颜色的系统调色板[4],从中我们选择将在渲染中使用的调色板。 每个调色板包含3种独特的颜色以及整个背景颜色。 该图像最多包含4个调色板,总共占16个字节。 以下是来自恶魔城的图像的调色板:

恶魔城

调色板不能随意使用。 每个块仅应用一个调色板。 正因为如此,需要根据游戏的调色板将每个16×16区域分开,让NES具有这种“块状”外观。 可以通过在块的边缘混合颜色来避免例如从恶魔城初始屏幕上熟练执行的图形,这会掩盖网格的存在。

使用最后一个组件-属性为每个块选择一个调色板。

属性


属性每个块占用2位。 他们确定要使用4个调色板中的哪个。 此图显示了由属性定义的调色板使用不同的块[5]:


如您所见,调色板分为多个部分,但是由于在不同区域使用相同的颜色,所以这很棘手。 大门中间的红色与周围的墙壁融为一体,黑色背景模糊了城堡之间的界限
和盖茨。

每块2位或每字节4个块,图像属性仅占据240/4 = 60字节,但是由于它们的编码方式,浪费了另外4个字节,也就是说,总共获得了64个字节。 这意味着整个图像(包括CHR,命名表,调色板和属性)占用4096 + 960 + 16 + 64 = 5136字节-比上述30720更好。

迈克


与使用常规位图API相比,为NES图形创建这四个组件要困难得多,但是可以使用工具。 NES开发人员可能拥有某种工具链,但是不管它是什么,故事都没有保存下来。 如今,开发人员通常会编写用于将图形转换为所需NES格式的程序。

这篇文章中的所有图像都是使用makechr创建的, makechrStar Versus使用的重写工具。 这是一个专为自动构建而设计的命令行工具,主要针对速度,质量错误消息,可移植性和可理解性。 他还创建了一些有趣的可视化效果,如本文所用。

参考文献


我主要从以下来源获得有关NES编程的知识,尤其是有关创建图形的知识:


注意事项


[1]术语-在某些文档中,块称为“元拼贴”,对我个人而言,它似乎没什么用。

[2] CHR编码-每个像素2位不相邻存储。 完整图像首先仅以低位保存,然后再次仅以高位保存。

也就是说,心脏将像这样存储:

意味着低表现心高

每行是一个字节。 也就是说,01100110是$ 66,01111111是$ 7f。 总的来说,心脏的字节看起来像这样:

$ 66 $ 7f $ ff $ ff $ ff $ ff $ 7e $ 3c $ 18 $ 66 $ 5f $ bf $ bf $ ff $ ff $ 7e $ 3c $ 18

[3]名称表-在此游戏内图表中,名称表的使用方式有所不同。 通常,字母的字母会保留在附近的记忆中,包括恶魔城。

[4]系统调色板-NES不使用RGB调色板,其呈现的实际颜色取决于特定的电视。 仿真器通常使用完全不同的RGB调色板。 本文中的颜色与makechr中阐明的调色板相对应。

[5]属性编码-属性以奇怪的顺序存储。 它们不会从左到右,从上到下移动-2×2块区域以字母Z的形式用一个字节编码。这就是为什么要浪费4个字节的原因。 最底行是完整的8个字节。

朋友块组

例如,$ 308的块存储有$ 30a,$ 348和$ 34a。 它们的调色板值分别为1、2、3和3,并从最低位置到最高位置依次存储,或者11 :: 11 :: 10 :: 01 =11111001。因此,这些属性的字节值为$ f9。

第二部分


在第一部分中,我们讨论了NES背景图形的组成部分-CHR,名称表,调色板和属性。 但这只是故事的一半。

首先,实际上有两个名称表[6]。 它们每个都有自己的颜色设置属性,但它们具有相同的CHR。 墨盒设备确定其位置:彼此相邻,或彼此叠置。 以下是两种不同类型位置的示例-Lode Runner(1984)和Bubble Bobble(1988)。


气泡滚动

卷动


为了利用两个名称表的存在,PPU支持沿X和Y轴一次滚动像素的功能,由一个带有内存显示的寄存器控制,价格为$ 2005:在该地址仅写入两个字节会将整个屏幕移动到所需的像素数[7]。 。 在发布NES时,这是与其他家用游戏机相比的主要优势,在这些家用游戏机中,滚动操作通常不得不重写整个视频内存。 这种易于使用的方案导致大量平台游戏和射击游戏的出现,并成为该系统如此成功的主要原因。

对于一个只有两个屏幕宽的简单游戏(例如Load Runner),仅填写两个名称表并相应地更改滚动就足够了。 但是在大多数滚动游戏中,关卡具有任意宽度。 要实现它们,游戏必须先更新名称表的屏幕外部分,然后它们才能出现在屏幕上。 滚动值是循环的,但是由于名称表会不断更新,因此会产生无限大小的错觉。


精灵


除了在名称表中滚动外,NES还具有图形的完全不同的方面:精灵。 与需要在网格中对齐的名称表不同,可以随意放置子画面,因此可以将它们用于显示玩家角色,障碍物,射弹以及任何具有复杂运动的物体。 例如,在《上等的人》(1987)中的上述场景中,用于显示玩家的角色。 点和能量条用于精灵,当滚动屏幕时,它们可以脱离名称表的网格。

精灵有自己的CHR页面[8]和一组4个调色板。 此外,它们占用256字节的内存页。 其中列出了每个子画面的位置和外观(事实证明,NES视频存储器比本文第一部分提到的大两倍半)。 这些记录的格式非常不寻常-它们首先包含Y中的位置,然后是图块编号,然后是属性,然后是X中的位置[9]。 由于每条记录占用4个字节,因此有严格的限制:在屏幕上一次最多只能包含256/4 = 64个精灵。

字节Y和X指定绘制的Sprite的左上像素。 因此,可以在屏幕的右侧裁剪精灵,但在左侧保留空白空间。 磁贴的字节类似于名称表中的值,仅对于这些磁贴而言,子画面使用其自己的CHR。 属性字节是一组位,执行三个任务:将两位分配给选项板,两位用于水平或垂直镜像子画面,一位确定是否在名称表下渲染子画面[10]。

局限性


现代系统允许使用任意大小的sprite,但是在NES上,由于CHR限制,sprite的大小必须为8×8 [11]。 较大的对象由数个精灵组成,程序应确保将所有各个部分都相邻放置。 例如,一个Megaman角色的大小可以达到10个精灵,这还使您可以使用更多的颜色,尤其是他的白眼睛和肤色。


与子画面的使用相关的主要限制是,每条栅格线最多只能有8个子画面。 如果在屏幕的任何水平线上出现8个以上的精灵,则稍后出现的精灵将不会被渲染。 这就是在带有大量精灵的游戏中闪烁的原因。 程序会交换内存中子画面的地址,以便至少偶尔渲染一次。

超人闪烁

最后,滚动不影响子画面:子画面在屏幕上的位置由其Y和X值确定,而与滚动位置无关。 例如,当关卡相对于玩家移动或界面保持固定位置时,这有时是一个加号。 但是,在其他情况下,这是一个负号-您需要移动移动的对象,然后通过滚动更改量来更改其位置。

注意事项


[6]理论上,实际上有四个名称表,但是它们以这样一种方式进行镜像,即其中只有两个包含唯一的图形。 并排放置时,这称为垂直镜像,而名称表一个位于另一个上方,则称为水平镜像。

[7]还有一个寄存器可以选择从哪个名称表开始渲染,也就是说,考虑到镜像,滚动实际上是一个10位值或9位。

[8]并非总是如此。 可以将PPU配置为使用与精灵相同的CHR页面用于名称表。

[9]之所以使用此顺序,是因为它对应于PPU需要进行处理以进行有效渲染的数据。

[10]该位用于各种效果,例如,在超级马里奥兄弟3中将马里奥移动到白色方块下,或在恶魔城3中在子画面上渲染雾。

[11] PPU还具有启用8×16 Sprite的选项,该选项用于诸如Contra之类的游戏中,那里的角色很高。 但是,所有其他限制都适用。

第三部分


在前面的部分中,我们讨论了CHR数据,基于名称表的背景,子画面和滚动。 这几乎是一个简单的NES墨盒无需其他硬件即可完成的所有工作。 但是,为了进一步讲解,我们需要详细解释渲染的工作原理。

渲染图



栅格渲染带有vblank的暂停

像其他旧计算机一样,NES被设计用于CRT电视。 他们使用电子枪在屏幕上绘制扫描线,一次从左到右,从上到下,一次物理移动到绘制这些线的屏幕上的点。 到达下角之后,进入一段称为“垂直空白”(或vblank)的时间:电子枪返回左上角以准备绘制下一帧。 在NES内部,PPU(图片处理单元)在每一帧中自动执行栅格渲染,并且CPU中运行的代码完成游戏应执行的所有任务。 Vblank允许程序替换PPU存储器中的数据,因为否则该数据将用于呈现。 通常,在此小窗口期间会更改名称表和PPU调色板。

但是,可以在屏幕渲染期间对PPU的状态进行一些更改。 它们被称为“光栅效果”。 屏幕渲染期间执行的最常见操作是设置滚动位置。 因此,图像的一部分保持静态(例如游戏界面),其他所有内容继续滚动。 为了实现该效果,必须精确选择用于改变滚动值的时间,以使其出现在所需的光栅线上。 有许多技术可以在游戏代码和PPU之间实现这种同步。

分屏



级别滚动,并且屏幕顶部的界面保持固定

首先,PPU具有内置硬件,可以以特殊方式处理零存储位置中的子画面。 渲染此精灵时,如果其像素之一与背景的可见部分重叠,则会设置一个称为“ sprite0标志”的位。 游戏代码可以首先将该精灵放置在应该发生屏幕拆分的位置,然后循环等待,检查sprite0标志的值。 因此,当退出循环时,游戏将确定当前正在渲染哪条光栅线。 该技术用于在许多NES游戏中实现简单的屏幕共享,包括Ninja Gaiden(1989),如上所示[12]。

忍者哈德

Sprite0位于Y $ 26,X $ a0。 渲染其底部像素行时,将设置sprite0标志

在某些游戏中,sprite0标志与另一种技术结合使用-可预测的定时循环(“具有可预测的定时的循环”):程序等待,直到渲染了几行以将屏幕分成更多部分。例如,此技术用于许多Ninja Gaiden屏幕保护程序中以创建戏剧性效果,例如,风场或远处的城堡图像。游戏开始执行诸如播放音乐和等待玩家进入之类的任务,在渲染帧的开始,然后使用sprite0搜索第一个分割,对于所有其他分割,它使用定时循环。

忍者在现场

城堡景观

但是,大多数游戏无法花时间在周期中等待,尤其是在CPU值得花费其宝贵时间的活跃场景中。在这种情况下,使用安装在墨盒中的特殊设备(称为映射器,因为它使用自己的内存映射(内存映射)),它可以接收有关渲染特定栅格线的时刻的通知[13],这完全不需要等待周期。游戏代码可以在任何期望的时间执行其任何任务,因此可以更优化地使用处理器。 NES的大多数较现代的游戏(具有很多屏幕划分)都以这种方式使用映射器。

火车级

这是《忍者外传2》中的一个示例,尽管关卡是静态的,但它使用映射器执行多个分区并模拟视差滚动,从而产生了极快的感觉。请注意,所有单个运动部件都严格占据水平条纹;也就是说,任何一个背景层都不能相互重叠。这是因为实际上是通过更改单个栅格线的滚动来实现分隔的。

银行转帐


映射器可以执行许多其他功能,但是最常见的功能是切换库。这是重新分配整个地址空间块以指向存储器[14]另一部分的操作。切换库可以使用程序代码(允许您在游戏中创建许多关卡和音乐)以及CHR数据执行,因此您可以立即替换名称表或sprite中引用的图块。如果在帧之间使用切换库,则可以一次对整个背景进行动画处理。但是,当用作光栅效果时,这使您可以在屏幕的不同部分绘制完全不同的图形。在《忍者外传》系列游戏中,在游戏过程中以及屏幕保护程序期间,都会使用这种方法来呈现与关卡分开的界面,这样您就可以将文本和视觉场景存储在不同的银行CHR中。

goofall-bg

,

堕落

,


CHR. ,

谁是他们的最低银行

在底部,使用了另一个CHR库。切换库时,滚动值也将被重置,

库切换也可以以有限(但仍然令人印象深刻)的形式用于视差滚动。如果场景的背景部分由短的重复图案组成,则可以将相同的图案包含在具有不同数量偏移量的多个库中。然后,通过切换到具有相应偏移量的存储体,可以将该模式滚动到某个值。即使由于背景的重叠而不受内存切换的影响,这种技术也可以用于视差滚动,即使背景重叠[15]。这种方法的缺点是,所有银行总共需要占用大量CHR空间。

金属风暴

《金属风暴》(Metal Storm,1991年)使用库切换进行逐层滚动

金属风暴

重复名称表可让您创建

带有切换库的CHR 效果 -这是一个非常强大的工具,但有其局限性。尽管它对整个屏幕动画化很有用,但是这种技术并不是非常适合仅替换屏幕的一小部分。这也需要更改名称表。此外,盒式磁带中的CHR数量受到限制,并且为了切换到数据,必须首先存在它们。最后,除了基于滚动的光栅效果外,游戏始终具有严格的名称表网格,这限制了图形效果的动态范围。

其他例子


副火

游戏Vice:Project Doom(1991)通过重复设置每个栅格线中的滚动位置来创建这种火焰效果。前景中的字符是由不受滚动影响的精灵创建的。

剑术大师

Sword Master(1990)使用银行切换在远处滚动山脉,并在界面和前景的草地上拆分屏幕。

致谢


如果没有FCEUX模拟器提供的强大调试功能,我将无法为文章生成所有这些图形。此外,NesDev网站Wiki已成为有关sprite0的有用信息来源:


注意事项


[12]实际上,忍者外传的情况要复杂一些。游戏使用8×16子画面sprites-PPU提供的一种特殊模式,可将子画面渲染为垂直叠加的对。也就是说,sprite0是完全透明的,而sprite1在最底部有一行像素。他还设置了这些精灵的z层,以便在界面的黑色后面渲染它们,从而使所有内容都不可见。

[13]实现起来非常棘手。游戏代码将所需的光栅行写入映射器的地址空间。然后,映射器将拦截PPU内存访问请求,并在渲染新的光栅行时进行计数。到达所需的栅格线后,它会生成程序中断(IRQ),在此期间执行游戏代码,并执行此特定栅格线所需的操作。

[14]交换是通过将设备映射到内存,拦截内存访问操作以及重新定义从中获取数据的物理位置来执行的。结果是瞬时的,但是它具有很大的分数,因此地址间隔相差4 KB或8 KB。

[15]在不影响每个图块的情况下切换CHR库的唯一方法如下:要么在库之间复制图块数据,要么使用纹理较少的映射器。使用此映射器,您可以切换银行的较小部分,例如一次仅切换1 KB,其他所有内容都将保持不变。

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


All Articles