Untuk efek pengguliran vertikal di bagian pertama "The Legend of Zelda", manipulasi "perangkat keras" grafis NES digunakan, kemungkinan besar tidak disediakan oleh pengembang konsol.
Saya tidak memiliki akses ke dokumentasi resmi dari Unit Pemrosesan Gambar (PPU - chip grafis) konsol NES, sehingga pernyataan saya tentang "perilaku tidak terbatas" lebih cenderung menjadi dugaan. Saya mengambil spesifikasi perangkat keras grafis dari
NesDev Wiki . PPU dikendalikan dengan menulis ke register dengan pemetaan memori. Jika Anda menggunakan register ini dengan cara yang (tampaknya) dikandung oleh para desainer, maka tidak mungkin untuk mencapai efek ini:
Saat menggulir layar secara vertikal, seluruh layar harus menggulir sekaligus. GIF sebelumnya menunjukkan contoh pengguliran vertikal parsial. Sebagian layar tetap stasioner (elemen antarmuka), dan bagian lainnya (area permainan) bergulir secara vertikal. Pengguliran vertikal sebagian tidak mungkin diterapkan dengan kerja "standar" dengan PPU.
Sebaliknya, pengguliran
horizontal parsial sepenuhnya ditentukan dan dimungkinkan.
Menulis ke register PPU terpisah pada saat bingkai dibuat dapat menyebabkan artefak grafis. The Legend of Zelda sengaja menyebabkan artefak yang memanifestasikan dirinya sebagai pengguliran vertikal parsial. Dalam posting ini, saya akan berbicara sedikit tentang perangkat keras grafis NES dan menjelaskan cara kerja trik gulir vertikal.
Jenis grafik
Konsol NES memiliki dua jenis grafis:
- Sprite adalah ubin yang dapat ditempatkan di tempat sewenang-wenang di layar dan dipindahkan secara independen satu sama lain.
- Latar Belakang - kotak ubin yang dapat digulir dengan lancar sebagai satu gambar.
Untuk menunjukkan perbedaan antara keduanya, saya akan menunjukkan adegan yang terdiri dari sprite dan latar belakang:
Dan di sini adalah adegan yang sama di mana hanya sprite yang terlihat:
Dan di sini adalah adegan di mana hanya latar belakang yang terlihat:
Bergulir
Prosesor gambar (NES Picture Processor) mendukung pengguliran gambar latar belakang. Dalam memori grafis, grafik latar belakang disimpan sebagai kotak ubin dua dimensi yang meliputi area dua kali lebar dan tinggi layar.
"Jendela" ditampilkan pada layar dalam ukuran kotak ini, dan posisi jendela ini dapat dikontrol dengan tepat. Dengan secara bertahap menggerakkan jendela yang terlihat di sepanjang kisi, efek gulir yang halus dibuat.
Sinyal video keluaran NES memiliki ukuran 256x240 piksel. Kotak ubin di dalam memori direpresentasikan sebagai area 512x480 piksel dan dibagi menjadi empat persegi panjang berukuran layar yang disebut "tabel nama". Game dapat mengonfigurasi Picture Processing Unit (PPU) dengan menunjukkan posisi jendela yang terlihat dengan memilih koordinat piksel dalam kisi tabel nama.
Saat Anda memilih koordinat (0, 0), seluruh nama tabel kiri atas akan ditampilkan di layar:
Pindah ke (125, 181), kita akan melihat sedikit dari setiap daftar nama:
Jendela yang terlihat meminimalkan ke bagian belakang kotak ubin di memori. Pindah ke (342, 290), kami menempatkan sudut kiri atas layar yang terlihat di dalam tabel nama kanan bawah, dan berkat pelipatan, bagian dari masing-masing tabel nama akan terlihat:
Memori tidak cukup!
Setiap tabel nama memiliki ukuran 1 KB, tetapi NES hanya mengalokasikan 2 KB memori videonya ke tabel ini, sehingga hanya dua tabel nama yang dapat masuk ke memori sekaligus.
Bagaimana bisa ada empat tabel nama?
Tabel Nama Mirroring
Memori video terhubung ke PPU sedemikian rupa sehingga ketika PPU membuat ubin salah satu dari empat tabel nama nyata, sebenarnya salah satu dari dua tabel nyata dipilih, dan bacaan berasal dari sana. Pada dasarnya, ini berarti bahwa empat tabel nama yang terlihat sebenarnya terdiri dari dua pasang tabel yang identik.
Gambar ini menunjukkan snapshot dari isi keempat tabel. Kiri atas dan kanan atas sama dengan dua yang lebih rendah.
Lalu mengapa tidak menyimpan dua tabel nama?
Untungnya, pengikatan yang tepat antara tabel nyata dan nyata dapat dikonfigurasi saat runtime. Jika permainan ingin melakukan pengguliran horizontal, maka itu menyesuaikan peralatan grafik sehingga tabel kiri atas dan kanan atas berbeda, dan mereka dapat digulir tanpa duplikasi yang nyata. Dalam konfigurasi ini, tabel kiri atas dan kiri bawah akan merujuk ke tabel nama asli yang sama; sama untuk dua tabel kanan. Konfigurasi ini disebut Pencerminan Vertikal.
Ada juga konfigurasi lain yang mungkin - "Horizontal Mirroring", yang digunakan game untuk pengguliran vertikal.
Biasanya, gim tidak menggulir secara diagonal, karena gim ini menciptakan artefak di sekitar tepi layar karena mirroring dari tabel nama.
Kartrid
Setiap kartrij permainan memiliki perangkat keras yang memungkinkan Anda mengonfigurasi pencerminan tabel.
Beberapa gim tidak perlu mengganti mirroring sama sekali, jadi mirroring horizontal atau vertikal sulit dikodekan dalam kartrijnya. Game lain secara dinamis beralih di antara kedua mode ini, jadi mirroring dalam kartrijnya dikonfigurasi secara terprogram. Legenda Zelda termasuk dalam kategori kedua. Akhirnya, kartrij dari beberapa gim yang benar-benar kompleks memiliki memori video tambahan, yaitu, gim-gim ini tidak perlu dicerminkan sama sekali: gim-gim tersebut dapat secara simultan bergulir secara vertikal dan horizontal tanpa artefak duplikasi yang terlihat.
Contoh nyata
Contoh pengguliran vertikal yang ditampilkan di layar.Ini menunjukkan catatan tabel nama dengan mirroring horizontal. Jendela yang saat ini terlihat disorot.Ingatlah bahwa pengguliran paling vertikal bukanlah hal yang tidak biasa - yang tidak biasa adalah pengguliran vertikal dengan
layar terbagi .
Layar terpisah
Setiap bingkai sinyal video yang dihasilkan oleh NES dirender dari atas ke bawah, satu baris piksel setiap kali. Di setiap baris, piksel digambar satu per satu, dari kiri ke kanan. Di tengah jalan saat merender bingkai, gim dapat mengkonfigurasi ulang PPU, yang memengaruhi tampilan piksel yang belum dirender. Salah satu perubahan paling umum di tengah frame adalah memperbarui posisi gulir horizontal.
Saat menggulir secara horizontal di antara kamar, The Legend of Zelda selalu dimulai dari posisi menggulir (0, 0) dan merender elemen antarmuka di bagian atas layar. Setelah menggambar garis terakhir piksel antarmuka pada layar, pengguliran horizontal berubah dengan nilai yang meningkat pada setiap frame, sehingga kamera bergerak dengan lancar.
Animasi tampilan tabel nama menunjukkan bagaimana permainan beralih dari mirroring horizontal ke vertikal sebelum menggulir, dan kemudian kembali ke horizontal setelah transisi selesai. Selain itu, saat pengguliran berlanjut, tabel nama kiri atas (dan kiri bawah) diperbarui, dan salinan ruangan yang dimasukkan pemain dicatat di dalamnya. Setelah pengguliran selesai, permainan berhenti membagi layar dan sekali lagi sepenuhnya ditampilkan dari tabel kiri atas.
Pengukuran Rendering
Untuk membagi layar pada posisi yang diinginkan, gim perlu mencari tahu bagian mana dari frame saat ini yang diambil. String piksel diberikan pada frekuensi yang diketahui, sehingga jumlah string piksel yang diberikan dapat ditentukan dengan menghitung jumlah siklus prosesor yang telah berlalu sejak awal frame.
Ada teknik lain yang lebih akurat yang disebut Sprite Zero Hit.
SEN dapat membuat hingga 64 sprite sekaligus. Sprite pertama dalam memori video disebut Sprite Zero (zero sprite). Di setiap frame, segera setelah piksel buram dari nol sprite ditumpangkan pada piksel latar buram, acara Sprite Zero Hit terjadi. Ini menetapkan sedikit di salah satu register PPU dengan pemetaan memori, yang dapat diperiksa oleh prosesor.
Untuk menggunakan Sprite Zero Hit untuk membagi layar, permainan letakkan nol sprite dalam posisi vertikal di dekat perbatasan split, dan selama rendering mereka terus-menerus memeriksa untuk melihat apakah acara Sprite Zero Hit telah terjadi. Jika demikian, gim beralih dari pengguliran horizontal ke penerapan pemisahan.
Transisi horizontal antara kamar dengan dan tanpa latar belakang ditunjukkan di bawah ini.
Lingkaran cokelat yang muncul di awal transisi dan menghilang pada akhirnya adalah nol sprite. Kami akan melihat lebih dekat pada antarmuka dengan dan tanpa latar belakang:
Sprite nol adalah sprite bom yang diputihkan yang sangat cocok dengan sprite bom biasa dari antarmuka game. Sprite nol dikonfigurasikan untuk muncul di bawah latar belakang, tetapi karena piksel hitam antarmuka dianggap transparan, bom sprite nol akan terlihat jika tidak tersembunyi secara strategis di belakang bom dari antarmuka.
Perhatikan bahwa Sprite Zero Hit terjadi beberapa baris piksel sebelum garis bawah antarmuka. Ini terjadi pada piksel atas sekering bom, yaitu 16 piksel dari bagian bawah antarmuka. Ketika Sprite Zero Hit terjadi, permainan mulai menghitung siklus prosesor, dan setelah menyelesaikan jumlah siklus yang diperlukan, mengatur pengguliran horizontal.
Pengosongan balok
Sebagian besar waktu, PPU konsol menarik piksel ke layar. Ada waktu henti singkat di antara frame saat rendering tidak dilakukan. Fenomena ini disebut blanking (Vertical Blank, atau vblank). Beberapa jenis perubahan konfigurasi PPU hanya dapat dilakukan selama vblank.
Daftar gulir
Game mengubah posisi gulir dengan menulis ke register PPU yang disebut
PPUSCROLL
, yang memetakan ke alamat memori
0x2005
. Operasi penulisan pertama dalam
PPUSCROLL
mendefinisikan komponen X dari posisi gulir, dan operasi kedua mengatur komponen Y. Demikian pula, perekaman alternatif dilakukan lebih lanjut.
Berikut ini menunjukkan semua operasi penulisan bukan-nol dalam
PPUSCROLL
selama pemutaran ini (dalam gerakan lambat) 16 frame layar dengan alur permainan. Komponen posisi gulir Y bertambah setiap dua frame. Semua operasi penulisan dalam
PPUSCROLL
dalam contoh ini dilakukan selama vblank, yang menyebabkan seluruh latar belakang bergeser bersamanya.
Scrolling Screen Split
Operasi tulis ke
PPUSCROLL
selama vblank berlaku pada awal frame yang diambil segera setelah vblank. Jika posisi gulir berubah selama rendering frame (mis., Tidak selama vblank), maka perubahan ini berlaku ketika gambar mencapai baris piksel berikutnya. Pengguliran horizontal sebagian diimplementasikan dengan menulis ke
PPUSCROLL
sementara PPU menggambar baris piksel terakhir sebelum menggulir.
Saat memperbarui posisi gulir di tengah bingkai, hanya posisi X posisi gulir yang diterapkan. Yaitu, komponen posisi gulir Y dibuang. Dengan demikian, jika game ingin membagi layar dan mengubah posisi menggulir bagian dari frame, itu hanya dapat menggulir secara horizontal.
Namun demikian:
Percaya atau tidak, nilai register
PPUSCROLL
tidak berubah selama transisi ini.
Anda dapat melihat artefak grafis satu piksel tinggi di bawah antarmuka. Ini adalah bug dari emulator saya yang disebabkan oleh kurangnya sinkronisasi siklus jam prosesor dengan rendering piksel-demi-piksel.
Intervensi dalam register lain
Register kedua, disebut
PPUADDR
, dipetakan ke alamat memori
0x2006
, digunakan untuk mengatur alamat memori video saat ini. Ketika sebuah game, misalnya, ingin mengubah salah satu ubin di tabel nama, pertama-tama ia menulis alamat memori video
PPUADDR
ke
PPUADDR
, dan kemudian menulis nilai baru
PPUDATA
ke
PPUDATA
- ini adalah register ketiga yang dipetakan ke alamat
0x2007
.
Menulis ke
PPUADDR
tidak selama vblank (mis. Saat merender bingkai) dapat menyebabkan artefak grafis. Ini karena rantai PPU, yang dipengaruhi oleh penulisan ke
PPUADDR
, juga secara langsung dikendalikan oleh perangkat PPU dalam proses mendapatkan ubin dari memori video untuk menggambar mereka. Karena proses rendering ke layar dilakukan dari atas ke bawah, dan dari kiri ke kanan dalam garis, PPU pada dasarnya memberikan
PPUADDR
nilai alamat dari
PPUADDR
saat ini
PPUADDR
ditarik. Saat merender perpindahan dari satu ubin ke ubin lain,
PPUADDR
bertambah dengan nilai saat ini.
Dengan demikian, menulis ke
PPUADDR
di tengah bingkai dapat mengubah ubin yang diterima oleh PPU dari memori selama bingkai saat ini.
Mari kita
PPUADDR
operasi penulisan ke
PPUADDR
selama lompatan vertikal. Karena tabel nama juga diperbarui selama transisi, output dari
semua operasi penulisan ke
PPUADDR
akan terlalu luas. Dengan transisi horizontal, pengguliran diatur selama rendering deretan piksel 63, oleh karena itu, kami akan mempertimbangkan operasi penulisan dalam
PPUADDR
hanya selama baris ini.
Polanya terlihat jelas. Setiap dua frame, alamat yang direkam dalam garis piksel 63 dikurangi sebesar 32 (0x20). Tetapi bagaimana hal ini menyebabkan pembaruan dalam posisi gulir yang sebenarnya?
Daftar Scrolling Nyata
Di dalam PPU ada register 15-bit yang tidak dipetakan ke CPU. Ini digunakan baik sebagai alamat saat ini untuk mengakses memori video, dan sebagai konfigurasi pengguliran latar belakang.
Ketika bekerja dengan nilai ini sebagai alamat, bit 14 diabaikan, dan bit 0-13 diperlakukan sebagai alamat dalam memori video.
Ketika bekerja dengan nilai ini sebagai konfigurasi pengguliran, bagian-bagiannya yang berbeda memiliki arti yang berbeda:
Memilih tabel nama adalah nilai dari 0 hingga 3 yang menentukan tabel nama saat ini dari mana gambar dibuat.
Pengguliran kasar di X dan
Pengguliran kasar di Y menentukan koordinat ubin di dalam tabel nama yang dipilih. Ini adalah ubin saat ini untuk menggambar.
Pengguliran yang tepat di sepanjang Y berisi nilai dari 0 hingga 7, yang menentukan offset vertikal saat ini dari garis piksel di dalam ubin saat ini. Ubin adalah bujur sangkar dengan sisi 8 piksel.
Pengguliran yang tepat pada X tidak ada dalam daftar ini. Ada register terpisah yang hanya berisi offset horizontal piksel saat ini, tetapi tidak penting untuk menjelaskan bagaimana pengguliran vertikal dilakukan dalam The Legend of Zelda.
Apa yang terjadi pada register ini ketika sebuah game menulis ke
PPUADDR
? Berikut adalah tiga operasi penulisan pertama dari demo yang ditunjukkan di atas.
Dengan memecah entri di alamat menjadi komponen gulir, Anda dapat dengan jelas memahami apa yang terjadi di sini. Setiap dua frame, nilai
scrolling kasar di Y berkurang, yang mengarah ke scrolling vertikal dengan satu ubin atau 8 piksel.
Sepanjang setiap frame, offset gulir awal adalah 0,0, setelah itu perekaman pada garis piksel 63 dilakukan pada alamat. Ini berarti bahwa 63 garis piksel pertama diambil dari bagian atas tabel nama yang dipilih yang berisi latar belakang antarmuka. Namun, baris piksel ke-64 lebih lanjut diberikan dengan pengguliran vertikal yang diterapkan dari alamat ini. Karena pengguliran vertikal berkurang setiap dua frame, itu memberi perasaan pengguliran vertikal dari bagian layar.
Gulir ke bawah untuk menggulir ke atas
The Legend of Zelda tidak dapat menyembunyikan trik ini dari pemain sepenuhnya. Ini menciptakan artefak yang terlihat pada transisi vertikal layar, yang terlihat jika Anda melihat dari dekat. Saat bergerak di antara kamar, bingkai pertama dari animasi yang bergulir akan bergulir ke bawah. Inilah animasi dalam gerakan yang sangat lambat.
Dalam daftar nama, Anda dapat melihat apa yang sebenarnya terjadi. Meskipun tampaknya bagi para pemain bahwa area yang terlihat akan menggulir ke atas dengan lancar, transisi gulir dimulai dengan memindahkan area yang terlihat dari tabel kiri atas nama ke tabel kiri bawah, yang berisi salinan latar belakang ruangan. Ini diperlukan karena antarmuka di bagian atas layar juga merupakan bagian dari tabel nama, dan jika area yang terlihat digulir ke atas dari posisi aslinya, ia akan melewati antarmuka.
Pengguliran vertikal dilakukan dengan menulis ke register
PPUADDR
di tengah bingkai. Nilai pertama yang ditulis adalah
0x2800
. Dua frame kemudian,
0x23A0
direkam, dan kemudian nilainya mulai berkurang 32 setiap frame kedua.
Menulis nilai
0x2800
ke register
0x2800
PPUADDR
Tabel PPUADDR
menjadi 2, yang menjadikan tabel nama kiri bawah. Karena kedua nilai gulir adalah 0, itu akan mulai dari ubin kiri atas tabel nama ini. Namun,
Penggulungan yang tepat dalam Y adalah 2, sehingga ada offset vertikal dua-piksel dari bagian atas tabel nama kiri bawah. Itu sebabnya dalam frame transisi pertama, kita melihat bar hitam 2 piksel tinggi di bagian bawah layar. Nilai gulir awal untuk animasi transisi digeser 2 piksel ke bawah untuk membuat transisi mulus.
Dua frame kemudian,
PPUADDR
ditulis ke
0x23A0
. Ini membawa kita kembali ke tabel kiri atas nama, dan kita membuat dari baris ubin ke 29, yaitu bagian bawah.
Pengguliran yang tepat di Y masih berisi 2.
Mengapa perlu
mengatur Exact Scrolling di Y ke 2? Mengapa permainan tidak menulis
0x0800
dan
0x03A0
agar tidak menderita offset dua piksel?
Empat tabel nama menempati area 4 KB di ruang alamat PPU, dari
0x2000
hingga
0x2FFF
. Setiap ubin dalam tabel menempati satu byte memori video (pada kenyataannya, mereka hanya indeks di tabel lain), dan urutan ubin dan tabel nama dalam memori video sedemikian rupa sehingga
Memilih tabel nama ,
Pengguliran kasar oleh Y dan
Pengguliran kasar oleh X membuat ubin diimbangi di dalam area memori dengan tabel nama. Artinya, dengan mengambil 12 bit yang lebih rendah dari register PPU internal dan menambahkannya ke
0x2000
, Anda dapat menemukan alamat
0x2000
di memori video. Dan ini bukan kebetulan! Inilah tepatnya bagaimana register harus ditangani: baik sebagai register alamat maupun sebagai register gulir.
Tapi ada satu kekurangan.
Saat memproses sebagai register alamat, bit 12 dan 13 dianggap sebagai bagian dari alamat. Selama rendering, PPU terus-menerus menimpa register dengan alamat ubin yang diberikan saat ini. Karena
0x2FFF
terletak di tabel nama, dan tabel terletak di area memori dari
0x2000
hingga
0x2FFF
, PPU memberikan nilai dari interval ini ke register.
Ketika permainan menulis ke
PPUADDR
di tengah bingkai, jika tidak menuliskan alamat ubin di tabel nama, maka PPU akan mencoba membaca
dari tempat lain di memori video. Setiap byte yang ia hitung akan dianggap sebagai ubin, yang cenderung mengarah pada hasil yang tidak diinginkan. Oleh karena itu, semua nilai yang direkam di tengah bingkai di
PPUADDR
harus berada dalam kisaran
0x2000
hingga
0x2FFF
. Dengan mengambil setiap angka dalam interval ini dan memperhitungkan komponen gulirnya, nilai
Pengguliran yang tepat dalam Y harus selalu sama dengan 2.
Batasan ini berarti bahwa kita tidak dapat mengubah
Pengguliran yang tepat ke arah
Y di tengah bingkai, yaitu, ketika menggunakan trik ini untuk menerapkan pengguliran vertikal pemisahan layar, kita dibatasi untuk menggulir 8 piksel sekaligus dan selalu memiliki offset vertikal dua piksel dari batas ubin. The Legend of Zelda bergerak 4 piksel per bingkai saat menggulir secara horizontal, tetapi 8 piksel per bingkai saat menggulir secara vertikal, dan sekarang kita tahu sebabnya.
Artefak ini juga terlihat ketika menggulir di antara ruang-ruang di bawah, tetapi dalam hal ini terjadi pada akhir animasi.
Bacaan tambahan
- Wiki NesDev adalah sumber yang sangat berharga untuk mempelajari perangkat keras NES. Secara khusus, subjek posting ini adalah halaman tentang menggulir PPU
dan register PPU . - Emulator NES saya yang masih sangat belum selesai tersedia di sini .
Catatan
Sampai saya mengetahui tentang register internal PPU, emulator saya menunjukkan efek menghapus selama transisi vertikal layar The Legend of Zelda.
Sprite tautan bergerak ke bawah layar, sebagaimana mestinya, tetapi latar belakangnya tidak bergulir. Penghapusan ini disebabkan oleh kenyataan bahwa permainan secara bertahap memperbarui daftar nama sehingga berisi grafik ruang baru, tetapi tidak memperbarui gulir untuk menjaga pembaruan di luar layar.