274. NES 调色板 RGB
定义一个最小化的 NES RGB 调色板近似值。
第 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 调色板。
这一步定义了一个实用的 64 色 NES RGB 调色板近似值
NES color index $00-$3F -> RGB tuple重要的硬件模型
NES 本身并不直接存储 RGB 颜色。PPU 拥有存放 NES 颜色索引的调色板 RAM,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 是一种 NTSC 制式的 NES PPU 版本。引用的 .pal 文件是针对该 PPU/输出行为的 RGB 调色板近似值,并不是 NES 硬件内部真实存储的 RGB 表。
为什么源文件的颜色数超过 64 种?有些 .pal 文件包含了 PPUMASK 强调(emphasis)变体。NES 基础颜色索引范围是 64 种颜色,但强调效果可以产生 8 种显示变体:
64 base colors * 8 emphasis states = 512 RGB entries在本教程步骤中,只使用前 64 个 RGB 条目,也就是没有强调效果的普通调色板。强调支持可以在以后渲染使用 PPUMASK 第 5-7 位时再添加。
如何从下载的 .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 信号模拟
- 在 $3F00-$3F1F 处查找 PPU 调色板 RAM
- pygame 显示
运行本课
uv run pytest tests/chapter_05_rendering_pipeline/test_274_nes_palette_rgb.py -v