NES 模擬器開(kāi)發(fā)教程 09 - PPU 背景

前一節(jié)講過(guò) PPU 分為背景和精靈兩個(gè)部分方援,這一節(jié)介紹 PPU 背景渲染方法

1. 概念

在了解渲染方式之前蕴纳,需要接觸 PPU 中的幾個(gè)概念:

  • Pattern table
    位于 PPU 總線的 0x0000 - 0x1FFF照筑,一共 8KB 的數(shù)據(jù),
    它保存了圖像數(shù)據(jù)晨雳,同時(shí)可以分為兩個(gè) 4KB 的區(qū)塊毫目,可以由程序控制 backgroud 或者 sprite 使用前 4KB 還是后 4KB
  • Name table
    位于 PPU 總線的 0x2000 - 0x2FFF,之前講過(guò)了师妙,它控制著背景圖像诵肛,一共 4KB,分 4 塊默穴,每塊 1KB怔檩。其中 2KB 由 NES 主機(jī)提供,另外 2KB 可以由卡帶提供蓄诽,或者做為主機(jī)提供 2KB RAM 的 mirror薛训。比如,垂直鏡像如圖所示:
    image.png

    Name table 數(shù)據(jù)做為 Pattern table 的索引仑氛,以此達(dá)到壓縮數(shù)據(jù)的目的
    另外需要注意的是乙埃,每一塊 Name table 只有 960 字節(jié),而非 1KB锯岖,剩下的 64 字節(jié)為下面要介紹的 Attribute table
  • Attribute table
    Attribute table 跟在每個(gè) Name table 之后介袜,每個(gè) Attribute table 有 64 字節(jié)
    Attribute table 控制著當(dāng)前圖像高 2bit 的 palette 偏移量,而 Name table 索引到 Pattern table 數(shù)據(jù)之后出吹,控制著低 2bit 的 palette 偏移量遇伞。兩個(gè)加起來(lái)剛好 4bit,前一章節(jié)講過(guò)趋箩,背景和精靈分別有 16 個(gè) palette 赃额,剛好對(duì)應(yīng)了 4bit

2. 渲染

我們之前了解過(guò)加派,PPU VRAM 一共 4KB叫确,每 1KB 就控制著一張圖像,也就是說(shuō)芍锦,另外 3KB 圖像始終是不會(huì)輸出到屏幕上的竹勉。同時(shí)這 4 張圖像以田字格的形式組合,再結(jié)合 PPU 的滾動(dòng)功能娄琉,就能實(shí)現(xiàn)背景畫(huà)面的上下左右移動(dòng)

下面針對(duì)每一幀圖像(每一塊 VRAM)次乓,看看 PPU 是如何渲染的:

NES 以 8 x 8 像素為單位,將圖片分成了 32 x 30 個(gè)小塊孽水,每一個(gè)小塊稱(chēng)為一個(gè)瓦片(tile)票腰,同時(shí),每 16 個(gè) tile 組成一個(gè)大塊女气,如下圖:

image.png

每一個(gè) tile 在 vram 中用一個(gè) byte 表示杏慰,該字節(jié)表示了當(dāng)前 tile 在 pattern table 中的偏移量,也就表示像素的數(shù)據(jù)。pattern table 以 16 bytes 為單位缘滥,每 16 bytes 中轰胁,有分前 8 bytes 和后 8 bytes。前后每 8 bytes 表示 tile 中每一行的 像素所處 palette 的低 2 bit朝扼,前 8 bytes 為 bit0赃阀,后 8 byts 為 bit 1

顏色高 2bit 由 attribute table 表示,attribute table 中每一個(gè)字節(jié)能管理 16 個(gè) tile 的顏色擎颖,如上圖中紅色的大塊榛斯。16 個(gè) tile 中,每 4 個(gè) tile 整合到一起搂捧,再和另外 12 個(gè) tile 組成田字形格局肖抱,分別為 左上,右上异旧,左下意述,右下。每個(gè)占 2 bit吮蛹,剛好 8 bit

這里也能看到 NES 畫(huà)面表現(xiàn)力不足荤崇,即每 4 個(gè) tile 組成的部分田字格中,他們只能有 4 種不同的顏色潮针,因?yàn)楦?2 bit 固定了术荤,剩下可變了只有低 2 bit

現(xiàn)在總結(jié)一下:圖像分成了 32 x 30 = 960 個(gè) tile,每個(gè) tile 在 name table 占前 960 字節(jié)每篷。同時(shí) tile 在 vram 中表示 pattern table 0 - 255 的偏移量瓣戚,pattern table 又以 16 bytes 為一個(gè)單位,那么總共需要 256 * 16 = 4KB 大小的 pattern table焦读。前面講過(guò)子库,pattern table 一共 8KB,可分為兩個(gè) 4KB 分別給 background 或者 sprite 使用矗晃,是不是剛剛好仑嗅?另外 16 個(gè) tile 組成的大的 tile 中,每個(gè)由 attribute table 的一個(gè)字節(jié)表示张症,一共需要 8 x 8 = 64 bytes仓技。加上 name table 的 960,剛好 64 + 960 = 1024 字節(jié)俗他,即 1KB VRAM

通過(guò)如此巧妙的設(shè)計(jì)脖捻,硬生生的將一個(gè) 320 x 240 的畫(huà)面壓縮到了 1KB,不得不服兆衅!

3. 舉例

前面說(shuō)這么多地沮,不如找個(gè)例子看看颜价,以 馬里奧 1 為例:
點(diǎn)開(kāi) Name table viewer:


image.png

以左上角的 'M' 為例,看看內(nèi)存中的數(shù)據(jù)

注意 fceux 默認(rèn)情況下會(huì)去除頂部和底部的 8 個(gè)像素诉濒,具體可以在顯示里面設(shè)置關(guān)掉周伦,或者直接在 Name table viewer 中查看

左上角的 'M' 數(shù)一下,大概是第 3 行第 3 列 tile未荒,那么它在 VRAM 中 offset 為 32 * 2 + 3 = 0x43专挪。并且位于左上角的 VRAM,那么 Name table 起始地址為 0x2000片排,最終 'M' 的地址為 0x2043寨腔,查看下改地址的數(shù)據(jù):

image.png

數(shù)據(jù)為 0x16,那么對(duì)應(yīng) pattern table offset 為 0x16 * 16 = 0x0160率寡。下面需要計(jì)算 pattern table 的起始地址迫卢,之前講過(guò),background 和 sprite 可以分別設(shè)置為 pattern table 的前 4k 或者后 4k冶共,這個(gè)通過(guò) PPU 寄存器 PPUCTRL(0x2000)的 bit 3-4 控制乾蛤,background 則為 bit4,具體可見(jiàn):http://wiki.nesdev.com/w/index.php/PPU_registers#PPUCTRL
捅僵。那么要知道起始地址家卖,則要先讀取 PPUCTRL 寄存器的值,切換到 NES memory庙楚,跳轉(zhuǎn)到 0x2000:
image.png

看到值是 0x90上荡,bit 4 為 1,根據(jù)寄存器定義:
image.png

得知 background 使用的是后 4KB馒闷,則 'M' 在 pattern table 中真實(shí)起始地址為:0x1000 + 0x0160 = 0x1160酪捡,切換回 PPU memory,跳轉(zhuǎn)到該地址看看:
image.png

之前說(shuō)過(guò) pattern table 以 16 bytes 為一組纳账,所以 1160 這一排都是 M 的數(shù)據(jù)逛薇,我們分為 2 個(gè) 8 byes 寫(xiě)成二進(jìn)制看看:

# 前 8 bytes
c6: 1 1 0 0 0 1 1 0
ee: 1 1 1 0 1 1 1 0
fe: 1 1 1 1 1 1 1 0
fe: 1 1 1 1 1 1 1 0
d6: 1 1 0 1 0 1 1 0
c6: 1 1 0 0 0 1 1 0
c6: 1 1 0 0 0 1 1 0

# 后 8 bytes
00: 0 0 0 0 0 0 0 0
00: 0 0 0 0 0 0 0 0
00: 0 0 0 0 0 0 0 0
00: 0 0 0 0 0 0 0 0
00: 0 0 0 0 0 0 0 0
00: 0 0 0 0 0 0 0 0
00: 0 0 0 0 0 0 0 0
00: 0 0 0 0 0 0 0 0

前面 8 bytes 的 1 組合起來(lái)是不是有點(diǎn)像 M?
之前說(shuō)過(guò)塞祈,前 8 bytes 表示 bit 0 的顏色數(shù)據(jù)金刁,后 8 bytes 表示 bit 1 的顏色數(shù)據(jù)帅涂,前 2 bit 則需要從 attribute table 中獲取议薪,第 2 行第 3 列剛好位于 attribute 0 的右下,attribute table 緊跟在 name table 的 960 bytes 之后媳友,則 attribute 0 數(shù)據(jù)為 0x2000 + 960 = 0x23c0斯议,具體數(shù)據(jù)為 0xAA:


image.png

右下角為 bit 0-1,則高 2 bit 為 10
那么整合起來(lái)醇锚,M 中的背景色在 palette 中偏移量為 0b1000 = 8哼御,前景色在 palette 中偏移量為 0b1001 = 9
之前講過(guò)坯临,background palette 位于 0x3F00

注意:palette 以 4 個(gè) bytes 為單位,0 號(hào) byte 是共用的恋昼,比如 0x3F00 為 0x12看靠,則 0x3F04, 0x3F08, 0x3F0C 都是 0x12,所以只要遇到 palette 地址 % 4 為 0 的時(shí)候液肌,直接取 0x3F00 或者 0x3F10 的值就行了挟炬。同時(shí) 0 號(hào) byte 也表示透明色,這個(gè)在后面介紹 sprite 優(yōu)先級(jí)的時(shí)候會(huì)遇到

那么對(duì)于 M 的顏色嗦哆,內(nèi)存中數(shù)據(jù)為:


image.png
  • 背景色:0x22
  • 前景色:0x30
    對(duì)照這張表看看:


    image.png

    剛好是藍(lán)色底谤祖,白色字,和 'M' 的顯示一模一樣

4. PPU 滾動(dòng)

之前介紹的都是靜態(tài)的情況老速,實(shí)際上游戲過(guò)程中畫(huà)面都是運(yùn)動(dòng)的粥喜,這就靠 PPU 滾動(dòng)來(lái)完成
之前說(shuō)過(guò),PPU 一共 4 個(gè) 1KB 的 VRAM橘券,他們組成田字布局额湘,把屏幕想像成窗口,PPU 滾動(dòng)的時(shí)候就相當(dāng)于窗口在田字格上滑動(dòng)旁舰,類(lèi)似于這種效果:


NTS_scrolling_seam.gif

以 NTFS 為例缩挑,圖像刷新率為 60fps,PPU 會(huì)在 1s 內(nèi)產(chǎn)生 60 次中斷告知 CPU 刷新圖像(這對(duì)應(yīng)了之前介紹的垂直消隱)鬓梅, CPU 會(huì)設(shè)置 PPUSCROLL 寄存器以更新圖像位置供置,一次達(dá)到圖像運(yùn)動(dòng)的目的。具體可以在 fceux 中打開(kāi) Name table viewer 很形象地看到

具體在 PPU 還有 v绽快,t芥丧,x,w 幾個(gè)內(nèi)部的寄存器用于滾動(dòng)坊罢,這里做為一個(gè)拋磚引玉续担,具體要介紹起來(lái)篇幅就太長(zhǎng)了,建議直接看 NDEDEV 上的這篇文章:http://wiki.nesdev.com/w/index.php/PPU_scrolling
里面詳細(xì)介紹了 PPU 滾動(dòng)機(jī)制活孩,甚至偽代碼都幫你寫(xiě)好了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末物遇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子憾儒,更是在濱河造成了極大的恐慌询兴,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件起趾,死亡現(xiàn)場(chǎng)離奇詭異诗舰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)训裆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)眶根,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜀铲,“玉大人,你說(shuō)我怎么就攤上這事属百〖侨埃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵族扰,是天一觀的道長(zhǎng)隆夯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)别伏,這世上最難降的妖魔是什么蹄衷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮厘肮,結(jié)果婚禮上愧口,老公的妹妹穿的比我還像新娘。我一直安慰自己类茂,他們只是感情好耍属,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著巩检,像睡著了一般厚骗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兢哭,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天领舰,我揣著相機(jī)與錄音,去河邊找鬼迟螺。 笑死冲秽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矩父。 我是一名探鬼主播锉桑,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼窍株!你這毒婦竟也來(lái)了民轴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤球订,失蹤者是張志新(化名)和其女友劉穎后裸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辙售,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轻抱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旦部。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祈搜。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖士八,靈堂內(nèi)的尸體忽然破棺而出容燕,到底是詐尸還是另有隱情,我是刑警寧澤婚度,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布蘸秘,位于F島的核電站,受9級(jí)特大地震影響蝗茁,放射性物質(zhì)發(fā)生泄漏醋虏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一哮翘、第九天 我趴在偏房一處隱蔽的房頂上張望颈嚼。 院中可真熱鬧,春花似錦饭寺、人聲如沸阻课。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)限煞。三九已至,卻和暖如春员凝,著一層夾襖步出監(jiān)牢的瞬間署驻,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工健霹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留硕舆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓骤公,卻偏偏與公主長(zhǎng)得像抚官,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阶捆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359