- Proyecto que implementa sombreado 3D en tiempo real en una Game Boy Color, donde el jugador puede manipular la trayectoria de la luz y rotar objetos
- Basado en cálculos de vectores normalizados y sombreado Lambertiano (dot product), simplificando las operaciones mediante coordenadas esféricas
- Para superar las limitaciones de la CPU SM83, que no tiene instrucción de multiplicación, se usan transformaciones logarítmicas y tablas de búsqueda, realizando operaciones con precisión de 8 bits
- Se logró una mejora de rendimiento de alrededor del 10% usando código auto modificable (self-modifying code), renderizando 15 tiles por cuadro
- El uso de IA para generar código falló en la mayoría de los casos, y el algoritmo central y el shader se completaron manualmente, con código escrito a mano
Descripción general del proyecto
- Se creó un juego que renderiza imágenes en tiempo real en la Game Boy Color
- El jugador manipula una luz en órbita mientras rota el objeto
- Todo el código está publicado en el repositorio de GitHub (nukep/gbshader)
Proceso de creación 3D
- Se usó Blender para desarrollar el look inicial, y como el resultado fue visualmente satisfactorio, se siguió adelante con el proyecto
- Se generó un normal map usando Cryptomatte y shaders personalizados
- En el modelo de la tetera (teapot), se rotó la cámara para exportar el normal map como una secuencia PNG
- La parte de la pantalla del modelo de la Game Boy Color se renderizó en una escena aparte y luego se compuso
Base matemática
- El normal map se usa como un campo vectorial que codifica el vector normal de cada píxel
- El sombreado Lambertiano se calcula como un producto punto con la forma
v = N·L
- Al convertir a coordenadas esféricas, se simplifica a
v = sinNθ sinLθ cos(Nφ−Lφ) + cosNθ cosLθ
- Se asume que todos los vectores tienen radio r=1 para reducir la carga de cómputo
Implementación en Game Boy
- Lθ (ángulo vertical de la luz) se fija como constante, y el jugador solo controla Lφ (ángulo de rotación de la luz)
- En la ROM, cada píxel se almacena con la forma
(Nφ, log(m), b)
- Para resolver la ausencia de instrucción de multiplicación, se usan transformaciones logarítmicas y tablas de búsqueda (
log, pow)
- El bit de signo (sign) se guarda en el bit más alto para soportar operaciones con negativos
- Todos los valores escalares se representan como fracciones de 8 bits en el rango -1.0 a +1.0
- La suma se realiza en espacio lineal y la multiplicación en espacio logarítmico
- Se usa 127 como denominador para poder representar tanto +1 como -1
cos_log y la operación clave
cos_log es una tabla combinada del tipo log(cos x), que reemplaza la multiplicación por suma en espacio logarítmico
- Operaciones por píxel
- 1 resta, 1 consulta a
cos_log, 1 suma, 1 consulta a pow, 1 suma
- En total: 3 sumas/restas y 2 consultas
Rendimiento
- Se procesan 15 tiles por cuadro, y algunas filas vacías se calculan más rápido
- Aproximadamente 130 ciclos por píxel, y las filas vacías requieren 3 ciclos
- Cerca del 89% de la CPU se usa para el cálculo del shader, y el resto para entrada y manejo de I/O
Código auto modificable (Self-Modifying Code)
- Para optimizar el bucle principal que procesa unos 960 píxeles por cuadro, se modifican las propias instrucciones
- Se insertan constantes directamente en el código para ejecutar operaciones más rápidas que cargando variables
- Ejemplo:
sub a, 8 es 12 ciclos más rápido que sub a, variable
- En total, se ahorran aproximadamente 11,520 ciclos (10%)
Intentos de uso de IA
- El 95% del proyecto fue escrito manualmente
- La IA tuvo dificultades para escribir ensamblador de Game Boy (SM83)
- Uso concreto de la IA
- Python: lectura de capas OpenEXR
- Blender: scripts de automatización de escenas
- SM83: algunos fragmentos funcionales (por ejemplo, VRAM DMA)
- Intentos fallidos
- Se intentó generar código ensamblador del shader con IA → resultó ineficiente y con muchos errores
- Se probó usar el modelo Claude Sonnet 4 para generar ensamblador a partir de pseudocódigo
- Parte funcionó, pero era lento y cometía errores como confundir Z80 con SM83
- El código final fue completamente reescrito a mano
Conclusión y lecciones
- La IA puede ser útil para scripts simples, pero la precisión y la verificación son imprescindibles
- En el código para procesar OpenEXR, la IA provocó un error de orden de canales (BGR vs RGB) que generó bugs durante semanas
- A partir de la experiencia, se enfatiza la lección de que “lo más importante al usar IA es verificar”
- El proyecto se considera un caso experimental de implementación de shaders que supera los límites del hardware legado
1 comentarios
Comentarios en Hacker News
Da gusto ver en HN un texto con verdadera vibra hacker.
El resultado está realmente genial. Según entiendo, esto es “parece 3D, pero en realidad es un shader con iluminación aplicado sobre un normal map 2D prerenderizado”.
Los frames están en este enlace de GitHub
La parte de procesar triángulos 3D se mantiene simple, y los shaders de iluminación costosos se ejecutan solo una vez sobre la imagen 2D, así que es eficiente.
Desde la perspectiva del shader, si la entrada es un vector 3D, entonces es un shader 3D. Que exista o no un rasterizador 3D es otro tema.
Los juegos 3D modernos también usan este tipo de enfoque de muchas maneras. La técnica de imposters, que usa modelos prerenderizados desde varios ángulos, también es una técnica usada en motores 3D formales.
Pero esta vez lo sorprendente es que corre en una Game Boy Color.
Hola, soy el autor. Escuché que esto se publicó aquí, así que me hice una cuenta. Gracias por compartirlo.
También estoy haciendo experimentos para simplificarlo aún más usando environment maps, y se puede ver en el enlace que compartí en Bsky
Es un proyecto realmente interesante. Me recordó la época en que programaba en ensamblador para C64.
En ese entonces tampoco había instrucción de multiplicación, así que había que buscar maneras creativas de rodear las limitaciones del hardware.
Fue un intento de usar IA, pero al final terminó siendo un experimento fallido.
Como la industria no deja de hablar de IA, quise probarla por mí mismo, y creo que es importante dejar claro de forma transparente si se usó IA generativa o no.
Si se oculta, se daña la confianza; si se dice abiertamente, se puede tener una conversación abierta incluso con gente que piensa distinto.
Solo quería documentar este proceso.
Este shader de GBC muestra la verdad de que “todos los cálculos son aproximaciones dentro de restricciones”.
La multiplicación se reemplaza con búsquedas en tablas y sumas, y la precisión se ajusta en función del resultado visible.
De verdad impresionante. Sobre todo, sorprende que esto corra en hardware real de Game Boy Color.
A veces meten un procesador potente en el cartucho y usan la GBC solo como terminal, pero este no es ese tipo de hack.
Honestamente, ojalá Nintendo relanzara la GBC o la GBA.
Si las vendieran en forma de cartucho con algunos juegos integrados, la compraría de inmediato.
Aun así, hoy en día un dispositivo portátil Android del mismo formato es más práctico.
Yo también tengo una colección de Game Boy, pero ahora los emuladores son mucho más cómodos.
Incluso si Nintendo sacara una nueva, no creo que pudiera ser tan buena como esa.
Este tipo de textos es exactamente la razón por la que existe HN.
Me hace sentir otra vez la diversión de hojear las antiguas revistas de tecnología.
Este autor es un genio loco en el buen sentido.