303. 从调色板 RAM 得到精灵调色板

根据 PPU 调色板 RAM 字节构建精灵调色板。

303 / 356 · tests/chapter_09_sprite_rendering/test_303_sprite_palettes_from_palette_ram.py

需要更新的文件

emulator/rendering/palette_ram.py

为什么需要这一步

在渲染精灵像素之前,我们需要把精灵调色板 RAM 字节转换成 RGB 调色板。精灵调色板 RAM 位于 PPU 调色板地址范围内:

$3F10-$3F1F

这个辅助函数接收这 16 个字节,并返回四个 RGB 精灵调色板。

参考资料

https://www.nesdev.org/wiki/PPU_palettes#Palette_RAM

重要术语

CHR color index
    The 2-bit pixel value decoded from CHR tile data: 0, 1, 2, or 3.

PPU palette RAM byte
    A runtime NES color index, usually $00-$3F.

RGB color
    The emulator's display color tuple from NES_PALETTE_RGB.

精灵调色板的形状

palette 0 uses bytes 0, 1, 2, 3
palette 1 uses bytes 4, 5, 6, 7
palette 2 uses bytes 8, 9, 10, 11
palette 3 uses bytes 12, 13, 14, 15

建议的实现示例

SpritePalettes = list[list[RGBColor]]


def build_sprite_palettes_from_palette_ram(palette_ram: bytes) -> SpritePalettes:
    if len(palette_ram) != PALETTE_RAM_SIZE:
        raise ValueError(f"Sprite palette RAM must be {PALETTE_RAM_SIZE} bytes")

    sprite_palettes = []

    for palette_id in range(TOTAL_PALETTES):
        base = palette_id * COLORS_PER_PALETTE

        palette = [
            get_nes_rgb_color(palette_ram[base]),
            get_nes_rgb_color(palette_ram[base + 1]),
            get_nes_rgb_color(palette_ram[base + 2]),
            get_nes_rgb_color(palette_ram[base + 3]),
        ]

        sprite_palettes.append(palette)

    return sprite_palettes

重要的精灵透明规则

在渲染过程中,精灵 CHR 颜色索引 0 是透明的。这个辅助函数不处理透明度,它只是把调色板 RAM 字节转换成 RGB 颜色。后续的精灵渲染器将决定:

if sprite_color_index == 0:
    skip drawing this pixel

常见误解

"Sprite palette entry 0 should be removed from the palette helper."

不是。要保留四个条目。渲染器需要稳定的 0..3 索引,即便索引 0 在绘制精灵像素时表示透明。

本步骤不涉及的内容

  • 渲染精灵像素
  • 应用透明度
  • 精灵优先级
  • 精灵翻转
  • sprite 0 hit
  • sprite overflow
  • pygame

运行本课

uv run pytest tests/chapter_09_sprite_rendering/test_303_sprite_palettes_from_palette_ram.py -v