274. RGB-палитра NES
Определение минимальной RGB-аппроксимации палитры NES.
Урок 274 из 356 · tests/chapter_05_rendering_pipeline/test_274_nes_palette_rgb.py
Файл эталонной палитры
https://www.nesdev.org/wiki/File:2C02G_U_wiki.palФайл для создания
emulator/rendering/nes_palette.pyПочему этот шаг важен
Конвейер рендеринга теперь может преобразовывать сетки индексов цвета в данные Framebuffer, но вызывающему коду всё ещё нужно предоставлять RGB-палитру вручную.
Этот шаг определяет практичную RGB-аппроксимацию 64-цветной палитры NES
NES color index $00-$3F -> RGB tupleВажная аппаратная модель
NES не хранит RGB-цвета напрямую. PPU имеет палитровую RAM, содержащую индексы цветов NES, а аналоговая видеосхема PPU преобразует эти индексы в видеосигнал. В эмуляторе мы аппроксимируем этот вывод RGB-кортежами.
Приближённый к реальности поток
pixel palette entry
-> PPU palette RAM value $00-$3F
-> PPU hardware color generator / analog video signal
-> TV colorПоток в эмуляторе
pixel palette entry
-> PPU palette RAM value $00-$3F
-> NES_PALETTE_RGB[index]
-> Framebuffer RGB pixelЧто такое 2C02G_U_wiki.pal? 2C02G — это ревизия PPU NES для NTSC. Указанный файл .pal — это RGB-аппроксимация палитры для поведения этого PPU/выхода. Это не буквальная RGB-таблица, хранящаяся внутри аппаратуры NES.
Почему в исходном файле больше 64 цветов? Некоторые файлы .pal включают варианты с акцентированием PPUMASK. Базовый диапазон индексов цвета NES — 64 цвета, но акцентирование может создать 8 вариантов отображения:
64 base colors * 8 emphasis states = 512 RGB entriesДля этого шага курса используйте только первые 64 записи RGB: обычную палитру без акцентирования. Поддержку акцентирования можно добавить позже, когда рендеринг будет использовать биты 5-7 PPUMASK.
Как воспроизвести эту таблицу из скачанного файла .pal:
file = "2C02G_U_wiki.pal"
data = open(file, "rb").read()
for i in range(0, 64 * 3, 3):
rgb = data[i:i + 3]
if len(rgb) == 3:
r, g, b = rgb[0], rgb[1], rgb[2]
print(f"({r},{g},{b}),")Пример возможной реализации
from emulator.rendering.framebuffer import RGBColor
NES_PALETTE_SIZE = 64
NES_PALETTE_RGB: list[RGBColor] = [
(87,87,87), (0,12,142), (8,0,166), (52,0,150), (85,0,97), (99,0,21), (90,0,0), (60,14,0),
(17,40,0), (0,59,0), (0,66,0), (0,58,5), (0,38,82), (0,0,0), (0,0,0), (0,0,0),
(165,165,165), (0,65,217), (47,30,255), (103,4,242), (148,0,180), (170,0,87), (163,24,0), (128,57,0),
(75,91,0), (19,118,0), (0,129,0), (0,121,35), (0,98,136), (0,0,0), (0,0,0), (0,0,0),
(255,255,255), (74,159,255), (121,126,255), (175,99,255), (221,85,255), (247,87,194), (247,106,99), (220,136,16),
(174,169,0), (120,196,0), (74,210,17), (47,207,100), (47,189,196), (65,65,65), (0,0,0), (0,0,0),
(255,255,255), (185,221,255), (202,209,255), (222,198,255), (240,192,255), (252,192,238), (253,198,202), (245,208,170),
(228,221,149), (208,232,146), (189,238,162), (178,238,192), (176,232,227), (179,179,179), (0,0,0), (0,0,0),
]
def get_nes_rgb_color(index: int) -> RGBColor:
return NES_PALETTE_RGB[index & 0x3F]Почему маскировать значением $3F?
Индексы цвета NES — это 6-битные значения
$00-$3FМаскирование гарантирует, что индекс не выходит за пределы диапазона
index & 0x3FПримеры
get_nes_rgb_color(0x40) == get_nes_rgb_color(0x00)
get_nes_rgb_color(0x41) == get_nes_rgb_color(0x01)Вне рамок этого шага
- акцентированные цвета PPUMASK
- палитры PAL/Dendy
- симуляция сигнала CRT/NTSC
- поиск в палитровой RAM PPU по адресам $3F00-$3F1F
- отображение через pygame
Запустить этот урок
uv run pytest tests/chapter_05_rendering_pipeline/test_274_nes_palette_rgb.py -v