¿Cómo se organizaron los gráficos NES?

imagen

Lanzada en 1983, la consola doméstica de Nintendo Entertainment System (NES) era una máquina barata pero poderosa que logró un éxito fenomenal. Usando la unidad de procesamiento de imágenes (PPU), el sistema podría crear gráficos bastante impresionantes para esos tiempos, que incluso hoy se ven bastante bien en el contexto correcto. El aspecto más importante era la eficiencia de la memoria: al crear gráficos, teníamos que administrar con la menor cantidad de bytes posible. Sin embargo, junto con esto, NES proporcionó a los desarrolladores características potentes y fáciles de usar que le permitieron destacarse de las consolas domésticas más antiguas. Habiendo entendido los principios de la creación de gráficos NES, puede sentir la perfección técnica del sistema y darse cuenta de lo fácil que es para los desarrolladores de juegos modernos trabajar.

Los gráficos de fondo de NES se ensamblaron a partir de cuatro componentes separados, cuya combinación formó la imagen que vemos en la pantalla. Cada componente era responsable de un aspecto separado; color, diseño, gráficos de píxeles en bruto, etc. Tal sistema puede parecer innecesariamente complicado y engorroso, pero al final utilizó la memoria de manera mucho más eficiente y permitió crear efectos simples en una pequeña cantidad de código. Si desea comprender los gráficos NES, estos cuatro componentes serán información clave.

Este artículo asume que está familiarizado con las matemáticas de la computadora, y en particular con el hecho de que 8 bits = 1 byte y 8 bits pueden representar 256 valores. También es necesario comprender cómo funciona la notación hexadecimal. Pero incluso sin este conocimiento técnico, el artículo puede parecer interesante.

Breve reseña



Arriba hay una imagen de la primera escena de Castlevania (1986): la puerta que conduce al castillo, donde tendrá lugar el juego. Esta imagen tiene un tamaño de 256 × 240 píxeles y utiliza 10 colores diferentes. Para describir esta imagen en la memoria, debemos aprovechar la paleta de colores limitada y ahorrar espacio almacenando solo una cantidad mínima de información. Uno de los enfoques ingenuos es utilizar una paleta indexada en la que cada píxel tiene un volumen de 4 bits, es decir, 2 píxeles se colocan en un byte. Esto requerirá 256 * 240/2 = 30720 bytes, pero como veremos pronto, NES puede hacer frente a esta tarea de manera mucho más eficiente.

Los conceptos principales en el tema de gráficos NES son mosaicos y bloques [1]. Un mosaico es un área de 8 × 8 píxeles, y un bloque es un área de 16 × 16 píxeles, y cada uno de ellos está vinculado a una cuadrícula con el mismo tamaño de celda. Después de agregar estas cuadrículas, podemos ver la estructura de los gráficos. Aquí está la entrada al castillo con una cuadrícula con doble aumento.


En esta cuadrícula, los bloques se muestran en verde claro y los mosaicos en verde oscuro. Las reglas a lo largo de los ejes tienen valores hexadecimales que se pueden agregar para encontrar una posición; por ejemplo, el corazón en la barra de estado está en $ 15 + $ 60 = $ 75, que en decimal es 117. Cada pantalla contiene 16 × 15 bloques (240) y 32 × 30 fichas (960). Ahora veamos cómo se describe esta imagen y comencemos con los gráficos de píxeles sin procesar.

CHR


La estructura CHR describe gráficos de píxeles "en bruto" sin su color y posición, y se configura en mosaicos. Toda la página de memoria contiene 256 mosaicos CHR, y cada mosaico tiene una profundidad de 2 bits. Aquí están los gráficos del corazón:


Y así es como se describe en CHR [2]:

pixel-heart-chr

Dicha descripción toma 2 bits por píxel, es decir, con un tamaño de 8 × 8 resulta 8 * 8 * 2 = 128 bits = 16 bytes. Entonces toda la página toma 16 * 256 = 4096 bytes. Aquí están todos los CHR utilizados en la imagen de Castlevania.


Recuerde que completar una imagen requiere 960 mosaicos, pero CHR le permite usar solo 256. Esto significa que la mayoría de los mosaicos se repiten, en promedio, 3.75 veces, pero con mayor frecuencia solo se usa un pequeño número de ellos (por ejemplo, fondo vacío, mosaicos monocromos o patrones repetitivos). La imagen de Castlevania usa muchos mosaicos vacíos, así como también azul sólido. Para ver cómo se asignan los mosaicos, utilizamos tablas de nombres.

NOMBRABLE


La tabla de nombres asigna un archivo CHR a cada posición en la pantalla, y hay un total de 960. Cada posición se especifica en un byte, es decir, la tabla de nombres completa ocupa hasta 960 bytes. Los mosaicos se asignan en orden de izquierda a derecha, de arriba a abajo, y corresponden a la posición calculada encontrada al agregar los valores de las reglas que se muestran arriba. Es decir, la posición en la esquina superior izquierda es $ 0, a la derecha de la misma es $ 1, y debajo está $ 20.

Los valores en la tabla de nombres dependen del orden en que se rellena el CHR. Aquí está una de las opciones [3]:


En este caso, el corazón (en la posición $ 75) tiene un valor de $ 13.

Luego, para agregar color, debemos seleccionar una paleta.

Paleta


NES tiene una paleta de sistema de 64 colores [4], y de ella seleccionamos las paletas que se utilizarán en el renderizado. Cada paleta contiene 3 colores únicos más el color de fondo general. La imagen tiene un máximo de 4 paletas, que en total ocupan 16 bytes. Aquí están las paletas para la imagen de Castlevania:

Castlevania-pal

Las paletas no pueden usarse arbitrariamente. Solo se aplica una paleta por bloque. Es por esta necesidad de separar cada área de 16 × 16 de acuerdo con la paleta de colores del juego para que NES tenga ese aspecto de "bloque". Los gráficos ejecutados con maestría, por ejemplo, desde la pantalla de inicio de Castlevania, se pueden evitar mezclando colores en los bordes de los bloques, lo que oculta la presencia de una cuadrícula.

La selección de una paleta para cada bloque se realiza utilizando el último componente: atributos.

Atributos


Los atributos ocupan 2 bits por bloque. Determinan cuál de las 4 paletas usar. Esta imagen muestra qué paletas definidas por los atributos usan diferentes bloques [5]:


Como puede ver, las paletas se dividen en secciones, pero esto es complicado debido al uso de los mismos colores en diferentes áreas. Rojo en el centro de la puerta se funde con las paredes que lo rodean, y un fondo negro desdibuja la línea entre el castillo
y puertas

Con 2 bits por bloque o 4 bloques por byte, los atributos de imagen ocupan solo 240/4 = 60 bytes, pero debido a la forma en que están codificados, se desperdician otros 4 bytes, es decir, se obtienen un total de 64 bytes. Esto significa que toda la imagen, incluidos CHR, tabla de nombres, paletas y atributos, ocupa 4096 + 960 + 16 + 64 = 5136 bytes, mucho mejor que el 30720 mencionado anteriormente.

MAKECHR


Crear estos cuatro componentes para gráficos NES es más difícil que usar las API de mapa de bits normales, pero las herramientas vienen al rescate. Los desarrolladores de NES probablemente tenían algún tipo de cadena de herramientas, pero fuera lo que fuera, la historia no lo salvó. Hoy en día, los desarrolladores suelen escribir programas para convertir gráficos al formato NES deseado.

Todas las imágenes en esta publicación fueron creadas usando makechr , una herramienta reescrita utilizada por Star Versus . Esta es una herramienta de línea de comandos diseñada para compilaciones automatizadas y dirigida a la velocidad, mensajes de error de calidad, portabilidad y comprensión. También crea visualizaciones interesantes como las utilizadas en la publicación.

Referencias


Principalmente conocimiento sobre programación para NES, y especialmente sobre creación de gráficos, obtuve de las siguientes fuentes:


Notas


[1] Terminología: en algunos documentos, los bloques se denominan "meta-mosaicos", que personalmente me parecen menos útiles.

[2] Codificación CHR: 2 bits por píxel no se almacenan uno al lado del otro. La imagen completa se guarda primero solo con los bits bajos, y luego nuevamente se guarda solo con los bits altos.

Es decir, el corazón se almacenará así:

pixel-heart-lowpixel-heart-high

Cada línea es un byte. Es decir, 01100110 es $ 66, 01111111 es $ 7f. En total, los bytes del corazón se ven así:

$ 66 $ 7f $ ff $ ff $ ff $ 7e $ 3c $ 18 $ 66 $ 5f $ bf $ bf $ ff $ 7e $ 3c $ 18

[3] Tabla de nombres: en esta tabla del juego, la tabla de nombres se usa de manera diferente. Por lo general, las letras del alfabeto se guardan en la memoria del vecindario, incluido Castlevania.

[4] Paleta del sistema: NES no utiliza una paleta RGB, y los colores reales que representa dependen del televisor en particular. Los emuladores generalmente usan paletas RGB completamente diferentes. Los colores en este artículo corresponden a la paleta enunciada en makechr.

[5] Codificación de atributos: los atributos se almacenan en un orden extraño. No van de izquierda a derecha, de arriba a abajo: el área del bloque 2 × 2 está codificada con un byte, en forma de letra Z. Es por eso que se desperdician 4 bytes; el resultado final es un total de 8 bytes.

grupo-bloque-pal

Por ejemplo, un bloque de $ 308 se almacena con $ 30a, $ 348 y $ 34a. Sus valores de paleta son 1, 2, 3 y 3, y se almacenan en orden desde la posición más baja hasta la posición más alta, o 11 :: 11 :: 10 :: 01 = 11111001. Por lo tanto, el valor de byte de estos atributos es $ f9.

Parte 2


En la primera parte, hablamos sobre los componentes de los gráficos de fondo de NES: CHR, tabla de nombres, paletas y atributos. Pero esto es solo la mitad de la historia.

Para empezar, en realidad hay dos tablas de nombres [6]. Cada uno de ellos tiene sus propios atributos para establecer el color, pero tienen el mismo CHR. El equipo de cartucho determina su posición: ya sea uno al lado del otro o uno encima del otro. Los siguientes son ejemplos de dos tipos diferentes de ubicaciones: Lode Runner (1984) y Bubble Bobble (1988).


Bubble-bobble-scrolling

Desplazamiento


Para aprovechar la presencia de dos tablas de nombres, PPU admite la capacidad de desplazarse por píxel a la vez a lo largo de los ejes X e Y. Está controlado por un registro con pantalla de memoria a $ 2005: al escribir solo dos bytes en esta dirección, la pantalla completa se mueve al número deseado de píxeles [7] . En el momento del lanzamiento de NES, esta era la principal ventaja sobre otras consolas domésticas, en las que para el desplazamiento a menudo había que reescribir toda la memoria de video. Tal esquema fácil de usar condujo a la aparición de una gran cantidad de plataformas y tiradores, y se convirtió en la razón principal de un gran éxito del sistema.

Para un juego simple, cuyo campo tiene solo dos pantallas de ancho, por ejemplo, Load Runner, fue suficiente para completar ambas tablas de nombres y cambiar el desplazamiento en consecuencia. Pero en la mayoría de los juegos de desplazamiento, los niveles tenían un ancho arbitrario. Para implementarlos, el juego debe actualizar la parte fuera de la pantalla de las tablas de nombres antes de que aparezcan en la pantalla. El valor de desplazamiento se repite, pero dado que la tabla de nombres se actualiza constantemente, esto crea la ilusión de un tamaño infinito.


Sprites


Además de desplazarse por las tablas de nombres, NES también tenía un aspecto completamente diferente de los gráficos: sprites. A diferencia de las tablas de nombres que deben alinearse en cuadrículas, los sprites se pueden colocar de forma arbitraria, por lo que se pueden usar para mostrar personajes de jugadores, obstáculos, proyectiles y cualquier objeto con movimientos complejos. Por ejemplo, en la escena anterior de Mega Man (1987) para mostrar el personaje de un jugador. los puntos y las tiras de energía son sprites utilizados, lo que les permite salir de la cuadrícula de tablas de nombres al desplazarse por la pantalla.

Los sprites tienen su propia página CHR [8] y un conjunto de 4 paletas. Además, ocupan una página de memoria de 256 bytes. que enumera la posición y la apariencia de cada sprite (como resultado, la memoria de video NES es dos veces y media más grande que la mencionada en la primera parte del artículo). El formato de estos registros es bastante inusual: contienen primero una posición en Y, luego un número de mosaico, luego un atributo, luego una posición en X [9]. Como cada registro tiene 4 bytes, hay una restricción estricta: en la pantalla no puede haber más de 256/4 = 64 sprites a la vez.

Los bytes Y y X especifican el píxel superior izquierdo del sprite dibujado. Por lo tanto, en el lado derecho de la pantalla, el sprite se puede recortar, pero en el lado izquierdo deja un espacio vacío. El byte del mosaico es similar al valor en la tabla de nombres, solo para estos mosaicos los sprites usan su propio CHR. Un byte de atributo es un paquete de bits que realiza tres tareas: se asignan dos bits a la paleta, se usan dos bits para reflejar el sprite horizontal o verticalmente, y un bit determina si se representa el sprite bajo las tablas de nombres [10].

Limitaciones


Los sistemas modernos permiten trabajar con sprites de cualquier tamaño arbitrario, pero en NES el sprite debido a limitaciones de CHR tenía que tener un tamaño de 8 × 8 [11]. Los objetos más grandes están formados por varios sprites, y el programa debe asegurarse de que todas las partes individuales se representen una al lado de la otra. Por ejemplo, un tamaño de personaje de Megaman puede alcanzar 10 sprites, lo que también le permite usar más colores, en particular para sus ojos blancos y el tono de su piel.


La principal limitación asociada con el uso de sprites es que no debe haber más de 8 sprites por línea de trama. Si aparecen más de 8 sprites en cualquier línea horizontal de la pantalla, entonces los que aparecieron más tarde simplemente no se procesarán. Esta es la razón para parpadear en juegos con muchos sprites; el programa intercambia las direcciones de los sprites en la memoria para que cada uno de ellos se represente al menos ocasionalmente.

megaman-parpadeo

Finalmente, el desplazamiento no afecta a los sprites: la posición del sprite en la pantalla está determinada por sus valores Y y X, independientemente de la posición del desplazamiento. A veces esto es una ventaja, por ejemplo, cuando el nivel se mueve en relación con el jugador o la interfaz permanece en una posición fija. Sin embargo, en otros casos, esto es un signo negativo: debe mover el objeto en movimiento y luego cambiar su posición según la cantidad de cambio en el desplazamiento.

Notas


[6] En teoría, en realidad hay cuatro tablas de nombres, pero están reflejadas de tal manera que solo 2 de ellas contienen gráficos únicos. Cuando se colocan lado a lado, esto se llama reflejo vertical, y cuando las tablas de nombres se encuentran una encima de la otra, reflejo horizontal.

[7] También hay un registro que selecciona con qué tabla de nombres comenzar a renderizar, es decir, el desplazamiento es en realidad un valor de 10 bits, o 9 bits, considerando la duplicación.

[8] Este no es siempre el caso. PPU se puede configurar para usar la misma página CHR para tablas de nombres que para sprites.

[9] Quizás este orden se utilizó porque corresponde a los datos que la PPU necesita procesar para una representación eficiente.

[10] Este bit se usa para varios efectos, por ejemplo, para mover a Mario debajo de los bloques blancos en Super Mario Bros 3, o para generar niebla sobre sprites en Castlevania 3.

[11] PPU también tiene una opción para habilitar sprites de 8 × 16, que se usa en juegos como Contra, donde hay personajes altos. Sin embargo, se aplican todas las demás restricciones.

Parte 3


En las partes anteriores, hablamos sobre datos de CHR, fondos basados ​​en tablas de nombres, sprites y desplazamiento. Y eso es prácticamente todo lo que un simple cartucho NES puede hacer sin hardware adicional. Pero para ir más allá, necesitamos explicar en detalle cómo funciona el renderizado.

Renderizado



Representación ráster con una pausa para vblank

Al igual que otras computadoras viejas, NES fue diseñado para funcionar con televisores CRT. Dibujan líneas de exploración en la pantalla, una a la vez, de izquierda a derecha, de arriba a abajo, utilizando una pistola de electrones que se mueve físicamente hasta el punto de la pantalla donde se dibujan estas líneas. Después de llegar a la esquina inferior, se establece un período de tiempo llamado "blanco vertical" (o vblank): la pistola de electrones regresa a la esquina superior izquierda para prepararse para dibujar el siguiente cuadro. Dentro de NES, la unidad de procesamiento de imágenes (PPU) realiza la representación ráster automáticamente, en cada cuadro, y el código que funciona en la CPU realiza todas las tareas que debe realizar el juego. Vblank permite que el programa reemplace los datos en la memoria PPU, porque de lo contrario, estos datos se utilizarán para la representación. Muy a menudo, los cambios en la tabla de nombres y paletas PPU se realizan durante esta pequeña ventana.

Sin embargo, se pueden hacer algunos cambios en el estado de la PPU durante el renderizado de la pantalla. Se llaman "efectos de trama". La acción más común realizada durante el renderizado de pantalla es establecer la posición de desplazamiento. Gracias a esto, parte de la imagen permanece estática (por ejemplo, la interfaz del juego), y todo lo demás continúa desplazándose. Para lograr este efecto, es necesario seleccionar con precisión el tiempo para cambiar el valor de desplazamiento para que ocurra en la línea ráster deseada. Existen muchas técnicas para implementar este tipo de sincronización entre el código del juego y la PPU.

Pantalla dividida



El nivel se desplaza y la interfaz en la parte superior de la pantalla permanece estacionaria

En primer lugar, PPU tiene un hardware incorporado que procesa sprites en la posición de memoria cero de una manera especial. Al representar este sprite, si uno de sus píxeles se superpone a la parte visible del fondo, se establece un bit llamado "bandera sprite0". El código del juego puede colocar primero este sprite donde debería ocurrir la división de la pantalla, y luego esperar en un bucle, verificando el valor de la bandera sprite0. Por lo tanto, cuando se sale del bucle, el juego sabrá con certeza qué línea de trama se está representando actualmente. Esta técnica se utiliza para implementar el uso compartido de pantalla simple en muchos juegos de NES, incluido Ninja Gaiden (1989), que se muestra arriba [12]

ninja-hud

Sprite0 se encuentra en Y $ 26, X $ a0. Cuando se representa su fila inferior de píxeles, se establece el indicador sprite0

En algunos juegos, el indicador sprite0 se combina con otra técnica: bucle de tiempo predecible ("un ciclo con tiempo predecible"): el programa espera hasta que se procesen algunas líneas adicionales para dividir la pantalla en más partes. Por ejemplo, esta técnica se usa en muchos protectores de pantalla de Ninja Gaiden para crear efectos dramáticos, por ejemplo, un campo impulsado por el viento o una imagen de un castillo en la distancia.El juego realiza tareas como reproducir música y esperar a que el jugador ingrese, al comienzo de la representación del cuadro, luego usa sprite0 para buscar la primera división, y para todos los demás usa bucles temporizados.

ninjas en el campo

vista del castillo

, , . ( , (memory mapping)), [13], . , . NES, , .

nivel de tren

Aquí hay un ejemplo de Ninja Gaiden 2, que utiliza un mapeador para realizar varias particiones y simular el desplazamiento de paralaje, lo que crea una sensación de gran velocidad, a pesar de la naturaleza estática del nivel. Tenga en cuenta que todas las partes móviles individuales ocupan franjas estrictamente horizontales; es decir, ninguna de las capas de fondo puede superponerse con otra. Esto se debe a que las separaciones se implementan realmente cambiando el desplazamiento de las líneas de trama individuales.

Cambio de banco


, . , , [14]. ( ), CHR, , . , . . Ninja Gaiden , , CHR.

goofall-bg

,

goofall-nt

,


CHR. ,

¿Quiénes son ellos?

En la parte inferior, se utiliza otro banco CHR. Al cambiar de banco, el valor de desplazamiento también se restablece.

La conmutación de banco también se puede utilizar para el desplazamiento de paralaje, en una forma limitada (pero aún impresionante). Si la escena tiene una parte del fondo compuesta por un patrón repetitivo corto, entonces este mismo patrón puede estar contenido en varios bancos con una compensación por una cantidad diferente. Luego, este patrón puede desplazarse a un cierto valor cambiando al banco con el desplazamiento correspondiente. Dicha técnica se puede utilizar para el desplazamiento de paralaje incluso con una superposición de fondo debido a la presencia de mosaicos que no se ven afectados por el cambio de memoria [15]. La desventaja de este método es que, en total, todos los bancos necesitan ocupar mucho espacio CHR.

tormenta de metal bg

Metal Storm (1991) utiliza el cambio de banco para el desplazamiento de capa por lado

tormenta de metal

La repetición de la tabla de nombres le permite crear este efecto

CHR con el cambio de bancos: esta es una herramienta muy poderosa, pero tiene sus limitaciones. Aunque es útil para animar la pantalla completa, esta técnica no es muy adecuada para reemplazar solo una pequeña parte de la pantalla; Esto también requiere cambios en la tabla de nombres. Además, la cantidad de CHR en el cartucho es limitada, y para cambiar a datos, primero deben existir. Finalmente, con la excepción de los efectos de trama basados ​​en desplazamiento, el juego siempre tiene una grilla estricta de tablas de nombres, lo que limita el rango dinámico de los efectos gráficos.

Otros ejemplos


vicio de fuego

El juego Vice: Project Doom (1991) crea este efecto de llama al configurar repetidamente la posición de desplazamiento en cada línea de trama. El personaje en primer plano se crea a partir de sprites que no se ven afectados por el desplazamiento.

maestro de espada

Sword Master (1990) utiliza el cambio de banco para desplazarse montañas en la distancia, y también al dividir la pantalla para la interfaz y la hierba en primer plano.

Agradecimientos


No podría generar todos estos gráficos para un artículo sin las potentes funciones de depuración proporcionadas por el emulador FCEUX. Además, el wiki del sitio NesDev se ha convertido en una fuente útil de información sobre sprite0:


Notas


[12] De hecho, la situación con Ninja Gaiden es un poco más complicada. El juego utiliza sprites de sprites de 8 × 16, un modo especial proporcionado por la PPU que representa los sprites como pares superpuestos verticalmente. Es decir, sprite0 es completamente transparente y sprite1 tiene una fila de píxeles en la parte inferior. También establece la capa z de estos sprites para que se representen detrás de la oscuridad de la interfaz, lo que hace que todo sea invisible.

[13] Esto es bastante difícil de implementar. El código del juego escribe la línea ráster deseada en el espacio de direcciones del mapeador. El mapeador intercepta las solicitudes de acceso a la memoria PPU, contando cuándo se representa una nueva línea de trama. Al llegar a la línea ráster deseada, genera una interrupción de programa (IRQ), durante la cual se ejecuta el código del juego, haciendo lo que se necesita durante esta línea ráster particular.

[14] , , . , , - 4 8 .

[15] CHR : , . , , 1 , .

Source: https://habr.com/ru/post/470614/


All Articles