- Projet ayant implémenté un shading 3D en temps réel sur Game Boy Color, où le joueur peut manipuler la trajectoire de la lumière et faire pivoter l’objet
- Basé sur le calcul de vecteurs normalisés et du shading de Lambert (dot product), avec une simplification des opérations grâce aux coordonnées sphériques
- Pour contourner les limites du CPU SM83 dépourvu d’instruction de multiplication, utilisation d’une transformation logarithmique et de tables de correspondance afin d’effectuer les calculs en précision 8 bits
- L’usage de code auto-modifiant (self-modifying code) permet un gain de performances d’environ 10 %, avec le rendu de 15 tuiles par image
- La génération de code avec l’IA a échoué dans la plupart des cas, et l’algorithme central comme le shader ont été finalisés à la main
Vue d’ensemble du projet
- Création d’un jeu qui rend des images en temps réel sur Game Boy Color
- Le joueur contrôle une lumière en orbite et fait pivoter l’objet
- L’intégralité du code est publiée dans le dépôt GitHub (nukep/gbshader)
Processus de création 3D
- Développement du look initial avec Blender, puis poursuite du projet après un résultat visuellement satisfaisant
- Génération de normal maps à l’aide de Cryptomatte et de shaders personnalisés
- Pour le modèle de théière, rotation de la caméra afin d’exporter les normal maps en séquence PNG
- Pour l’écran du modèle Game Boy Color, rendu dans une scène séparée puis composition
Fondements mathématiques
- La normal map sert de champ de vecteurs encodant le vecteur normal de chaque pixel
- Le shading de Lambert se calcule sous la forme du produit scalaire
v = N·L
- En passant aux coordonnées sphériques, on simplifie en
v = sinNθ sinLθ cos(Nφ−Lφ) + cosNθ cosLθ
- Le rayon de tous les vecteurs est supposé valoir r=1 afin de réduire la quantité de calculs
Implémentation sur Game Boy
- Lθ (angle vertical de la lumière) est fixé comme constante, et seul Lφ (angle de rotation de la lumière) est contrôlé par le joueur
- La ROM stocke chaque pixel sous la forme
(Nφ, log(m), b)
- Pour résoudre l’absence d’instruction de multiplication, le projet utilise une transformation logarithmique et des tables de correspondance (
log, pow)
- Le bit de signe est stocké dans le bit de poids fort afin de gérer les valeurs négatives
- Toutes les valeurs scalaires sont représentées comme des fractions 8 bits dans l’intervalle -1.0 à +1.0
- Les additions sont faites dans l’espace linéaire, les multiplications dans l’espace logarithmique
- Le dénominateur 127 est utilisé afin de pouvoir représenter à la fois +1 et -1
cos_log et opérations clés
cos_log est une table combinée sous la forme log(cos x), qui remplace la multiplication par une addition logarithmique
- Coût des opérations par pixel
- 1 soustraction, 1 accès à
cos_log, 1 addition, 1 accès à pow, 1 addition
- Soit un total de 3 additions/soustractions et 2 consultations de table
Performances
- Traitement de 15 tuiles par image, certaines lignes vides étant calculées plus rapidement
- Environ 130 cycles par pixel, contre 3 cycles pour une ligne vide
- Environ 89 % du CPU est utilisé pour les calculs du shader, le reste pour les entrées et les E/S
Code auto-modifiant (Self-Modifying Code)
- Pour optimiser la boucle principale qui traite environ 960 pixels par image, le projet modifie directement les instructions
- Les constantes sont injectées directement dans le code pour être plus rapides qu’un chargement de variable
- Exemple :
sub a, 8 est 12 cycles plus rapide que sub a, variable
- Gain global d’environ 11 520 cycles (10 %)
Tentatives d’usage de l’IA
- 95 % du projet ont été écrits manuellement
- L’IA a rencontré des difficultés pour écrire de l’assembleur Game Boy (SM83)
- Usages de l’IA
- Python : lecture des couches OpenEXR
- Blender : scripts d’automatisation de scène
- SM83 : quelques snippets fonctionnels (ex. : VRAM DMA)
- Tentatives infructueuses
- Génération du code assembleur du shader avec l’IA → code inefficace et nombreuses erreurs
- Tentative de génération d’assembleur à partir de pseudocode avec le modèle Claude Sonnet 4
- Quelques parties fonctionnaient, mais c’était lent et sujet à des erreurs comme la confusion entre Z80 et SM83
- Le code final a été entièrement réécrit manuellement
Conclusion et enseignements
- L’IA est utile pour les scripts simples, mais la précision et la vérification restent indispensables
- Dans le code de traitement OpenEXR, l’IA a provoqué une erreur d’ordre des canaux (BGR vs RGB) ayant entraîné un bug pendant plusieurs semaines
- L’expérience souligne la leçon suivante : « avec l’IA, la vérification est ce qu’il y a de plus important »
- Le projet est considéré comme un exemple expérimental d’implémentation de shader repoussant les limites d’un matériel legacy
Aucun commentaire pour le moment.