- Um projeto que implementa sombreamento 3D em tempo real no Game Boy Color, no qual o jogador pode manipular a trajetória da luz e rotacionar o objeto
- Com base no cálculo de vetores normalizados e sombreamento de Lambert (produto escalar / dot product), simplifica os cálculos usando coordenadas esféricas
- Para superar as limitações da CPU SM83 sem instrução de multiplicação, usa transformação logarítmica e tabelas de consulta para realizar operações com precisão de 8 bits
- Usando código auto-modificável (self-modifying code), alcança cerca de 10% de ganho de desempenho e renderiza 15 tiles por quadro
- O uso de IA para geração de código falhou na maior parte, e o algoritmo principal e o shader foram concluídos com código manual escrito diretamente pelo autor
Visão geral do projeto
- Criação de um jogo que renderiza imagens em tempo real no Game Boy Color
- O jogador controla uma luz em órbita e gira o objeto
- Todo o código está disponível no repositório do GitHub (nukep/gbshader)
Processo de criação em 3D
- O desenvolvimento visual inicial (lookdev) foi feito com Blender, e como o resultado ficou visualmente satisfatório, o projeto seguiu adiante
- Com Cryptomatte e um shader personalizado, foi gerado um normal map
- No modelo do bule (teapot), a câmera foi rotacionada para exportar o normal map como uma sequência de PNGs
- A parte da tela do modelo do Game Boy Color foi renderizada em uma cena separada e depois compositada
Base matemática
- O normal map é usado como um campo vetorial que codifica o vetor normal de cada pixel
- O sombreamento de Lambert é calculado como um produto escalar na forma
v = N·L
- Ao converter para coordenadas esféricas, isso é simplificado para
v = sinNθ sinLθ cos(Nφ−Lφ) + cosNθ cosLθ
- Assume-se raio r=1 para todos os vetores, reduzindo a quantidade de operações
Implementação no Game Boy
- Lθ (ângulo vertical da luz) é fixado como constante, e apenas Lφ (ângulo de rotação da luz) é controlado pelo jogador
- A ROM armazena cada pixel no formato
(Nφ, log(m), b)
- Para contornar a ausência de instrução de multiplicação, são usados transformação logarítmica e lookup tables (
log, pow)
- O bit de sinal é armazenado no bit mais alto para permitir operações com valores negativos
- Todos os valores escalares são representados como frações de 8 bits no intervalo de -1.0 a +1.0
- A soma é feita no espaço linear, e a multiplicação no espaço logarítmico
- Usa-se 127 como denominador para permitir representar tanto +1 quanto -1
cos_log e a operação principal
cos_log é uma lookup table combinada na forma log(cos x), substituindo multiplicação por soma no espaço logarítmico
- Operações por pixel
- 1 subtração, 1 consulta a
cos_log, 1 soma, 1 consulta a pow, 1 soma
- Total de 3 somas/subtrações e 2 consultas
Desempenho
- Processa 15 tiles por quadro, e algumas linhas vazias são calculadas mais rapidamente
- Cerca de 130 ciclos por pixel, e linhas vazias levam 3 ciclos
- Aproximadamente 89% da CPU é usada nas operações do shader, e o restante em entrada e I/O
Código auto-modificável (Self-Modifying Code)
- Para otimizar o loop principal que processa cerca de 960 pixels por quadro, o código modifica as próprias instruções
- Insere constantes diretamente no código para executar operações mais rápidas do que carregar variáveis
- Ex.:
sub a, 8 é 12 ciclos mais rápido do que sub a, variable
- No total, há uma economia de cerca de 11.520 ciclos (10%)
Tentativas de usar IA
- 95% de todo o projeto foi escrito manualmente
- A IA teve dificuldade para escrever assembly de Game Boy (SM83)
- Usos de IA
- Python: leitura de camadas OpenEXR
- Blender: scripts de automação da cena
- SM83: alguns snippets de funcionalidade (ex.: VRAM DMA)
- Tentativa malsucedida
- Tentativa de gerar código assembly do shader com IA → ineficiente e com muitos erros
- Tentativa de gerar assembly a partir de pseudocódigo com o modelo Claude Sonnet 4
- Algumas partes funcionaram, mas eram lentas e continham erros, como confundir Z80 com SM83
- O código final acabou sendo totalmente reescrito manualmente
Conclusão e lições
- A IA é útil para scripts simples, mas precisão e validação são indispensáveis
- No código de processamento OpenEXR, a IA causou um erro de alinhamento de canais (BGR vs RGB), gerando bugs por várias semanas
- A experiência reforça a lição de que “ao usar IA, o mais importante é validar”
- O projeto é avaliado como um caso experimental de implementação de shader que supera os limites de hardware legado
1 comentários
Comentários do Hacker News
Fico feliz de ver um texto com verdadeira vibe hacker no HN
O resultado ficou realmente incrível. Pelo que entendi, isso é “parece 3D, mas na verdade é um shader que aplica iluminação sobre um normal map 2D pré-renderizado”
Os frames estão neste link do GitHub
A parte de processamento dos triângulos 3D é mantida simples, e os shaders de iluminação mais caros são executados só uma vez sobre a imagem 2D, o que é eficiente
Do ponto de vista do shader, se a entrada é um vetor 3D, então é um shader 3D. Ter ou não um rasterizador 3D é outra questão
Jogos 3D modernos também usam esse tipo de abordagem de várias formas. A técnica de imposters, que usa modelos pré-renderizados de vários ângulos, também é uma técnica usada em engines 3D de verdade
Só que, desta vez, o impressionante é isso rodar em um Game Boy Color
Olá, sou o autor. Ouvi dizer que postaram isso aqui, então criei uma conta. Obrigado por compartilhar
Também estou fazendo experimentos para simplificar ainda mais usando environment mapping, e dá para ver no link que compartilhei no Bsky
Projeto realmente interessante. Me lembrou da época em que eu programava em assembly de C64.
Naquele tempo também não havia instrução de multiplicação, então era preciso encontrar maneiras criativas de contornar as limitações do hardware
Foi uma tentativa de usar IA, mas no fim acabou sendo um experimento fracassado.
Como o setor está em polvorosa com IA, eu queria experimentar por conta própria, e acho importante divulgar com transparência se foi usada IA generativa.
Se esconder isso, prejudica a confiança; se divulgar, dá para ter uma conversa aberta até com quem pensa diferente
Eu só queria registrar esse processo
Esse shader para GBC mostra a verdade de que “todo cálculo é uma aproximação dentro de restrições”.
A multiplicação é substituída por consulta em tabela e adição, e a precisão é ajustada de acordo com o resultado visual
Realmente impressionante. Principalmente o fato de isso rodar em hardware real de Game Boy Color.
Muitas vezes colocam um processador poderoso no cartucho e usam o GBC como simples terminal, mas isso não é esse tipo de hack
Sinceramente, eu queria que a Nintendo relançasse o GBC ou o GBA.
Se vendessem em formato de cartucho com alguns jogos embutidos, eu compraria na hora
Mas hoje em dia um portátil Android no mesmo formato acaba sendo mais prático.
Eu também tenho uma coleção de Game Boy, mas hoje em dia emulador é muito mais conveniente
Mesmo que a Nintendo fizesse um novo, acho difícil ficar tão bom quanto esse
É para textos assim que o HN existe.
Faz sentir de novo a diversão de folhear antigas revistas de tecnologia
Esse autor é um gênio maluco, no melhor sentido