310. 背景不透明度掩码

根据名称表和图案表数据构建背景不透明度掩码。

310 / 356 · tests/chapter_09_sprite_rendering/test_310_background_opaque_mask.py

需要更新的文件

emulator/rendering/nametable_renderer.py

为什么需要这一步

精灵有一个优先级位,可以让它们被放置在不透明的背景像素后面。要正确实现这一点,合成器需要知道每个背景像素是否是不透明的。

仅有 RGB 帧缓冲数据是不够的,因为一旦背景像素被转换成 RGB,我们就丢失了原始的 CHR 颜色索引。

这一步添加了一个纯掩码

BackgroundOpaqueMask = list[bool]

其中

False -> background CHR color index was 0
True  -> background CHR color index was 1, 2, or 3

建议的实现示例

BackgroundOpaqueMask = list[bool]


def build_background_opaque_mask(
    pattern_table: bytes,
    nametable: bytes,
) -> BackgroundOpaqueMask:
    if len(nametable) != NAMETABLE_SIZE:
        raise ValueError("Nametable must be 960 bytes")

    decoded_tiles = decode_pattern_table(pattern_table)

    opaque_mask: BackgroundOpaqueMask = [False] * (
        BACKGROUND_WIDTH * BACKGROUND_HEIGHT
    )

    for tile_y in range(NAMETABLE_ROWS):
        for tile_x in range(NAMETABLE_TILES_PER_ROW):
            tile_index = nametable[tile_y * NAMETABLE_TILES_PER_ROW + tile_x]
            tile = decoded_tiles[tile_index]

            for pixel_y in range(CHR_TILE_HEIGHT):
                for pixel_x in range(CHR_TILE_WIDTH):
                    color_index = tile[pixel_y][pixel_x]

                    screen_x = tile_x * CHR_TILE_WIDTH + pixel_x
                    screen_y = tile_y * CHR_TILE_HEIGHT + pixel_y

                    mask_index = screen_y * BACKGROUND_WIDTH + screen_x
                    opaque_mask[mask_index] = color_index != 0

    return opaque_mask

为什么不用属性表或调色板 RAM?不透明度只取决于 CHR 颜色索引。属性表和调色板 RAM 决定显示哪些颜色,但它们不会改变背景像素的 CHR 颜色索引是否为零。

本步骤不涉及的内容

  • 应用精灵优先级
  • 修改帧缓冲合成器
  • 精灵渲染
  • sprite 0 hit
  • sprite overflow
  • pygame

运行本课

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