274. Paleta RGB de la NES
Definir una aproximación mínima de la paleta RGB de la NES.
Lección 274 de 356 · tests/chapter_05_rendering_pipeline/test_274_nes_palette_rgb.py
Archivo de paleta de referencia
https://www.nesdev.org/wiki/File:2C02G_U_wiki.palArchivo a crear
emulator/rendering/nes_palette.pyPor qué existe este paso
La canalización de renderizado ya puede convertir cuadrículas de índices de color en datos de Framebuffer, pero los llamadores todavía tienen que proporcionar manualmente una paleta RGB.
Este paso define una aproximación práctica de paleta RGB de 64 colores de la NES
NES color index $00-$3F -> RGB tupleModelo de hardware importante
La NES no almacena colores RGB directamente. La PPU tiene una RAM de paletas que contiene índices de color de la NES, y el circuito de vídeo analógico de la PPU convierte esos índices en una señal de vídeo. En un emulador, aproximamos esa salida con tuplas RGB.
Flujo casi real
pixel palette entry
-> PPU palette RAM value $00-$3F
-> PPU hardware color generator / analog video signal
-> TV colorFlujo del emulador
pixel palette entry
-> PPU palette RAM value $00-$3F
-> NES_PALETTE_RGB[index]
-> Framebuffer RGB pixel¿Qué es 2C02G_U_wiki.pal? 2C02G es una revisión NTSC de la PPU de la NES. El archivo .pal referenciado es una aproximación de paleta RGB para ese comportamiento de PPU/salida. No es una tabla RGB literal almacenada dentro del hardware de la NES.
¿Por qué el archivo fuente tiene más de 64 colores? Algunos archivos .pal incluyen variantes de énfasis de PPUMASK. El rango base de índices de color de la NES es de 64 colores, pero el énfasis puede crear 8 variantes de visualización:
64 base colors * 8 emphasis states = 512 RGB entriesPara este paso del tutorial, usa solo las primeras 64 entradas RGB: la paleta normal sin énfasis. El soporte de énfasis se puede añadir más adelante cuando el renderizado use los bits 5-7 de PPUMASK.
Cómo reproducir esta tabla a partir del archivo .pal descargado:
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}),")Ejemplo de implementación sugerida
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]¿Por qué aplicar máscara con $3F?
Los índices de color de la NES son valores de 6 bits
$00-$3FEl enmascaramiento mantiene la búsqueda dentro del rango
index & 0x3FEjemplos
get_nes_rgb_color(0x40) == get_nes_rgb_color(0x00)
get_nes_rgb_color(0x41) == get_nes_rgb_color(0x01)Fuera de alcance
- colores de énfasis de PPUMASK
- paletas PAL/Dendy
- simulación de señal CRT/NTSC
- búsqueda en la RAM de paletas de la PPU en $3F00-$3F1F
- pantalla de pygame
Ejecutar esta lección
uv run pytest tests/chapter_05_rendering_pipeline/test_274_nes_palette_rgb.py -v