Implementação de Raycaster em Bash
(github.com/izabera)- Um raycaster implementado em Bash, uma demonstração pseudo-3D baseada em terminal em que você gira e se move com as setas e sai com
q - A implementação é, em grande parte, um port do tutorial de raycasting de Lode Vandevenne, e toda a matemática é feita com operações inteiras em escala de 64K, sem ponto flutuante
- A maior limitação é o desempenho do Bash: se executar um comando por pixel ou mantiver o estado da tela em arrays/strings, fica difícil imprimir dentro do tempo de um frame
- Para a exibição no terminal, usa Unicode half block e cores de primeiro plano/fundo de 24 bits, efetivamente dobrando a resolução vertical, mas com a restrição de precisar conhecer também a cor do pixel adjacente
- No roadmap atual, fluid movement, decent framerate, parallel rendering, kitty keyboard protocol e um protótipo inicial de sound já foram concluídos; textures, sprites, enemies, particles, multiplayer e outros ainda não foram concluídos
Raycaster de terminal feito em Bash
- Este projeto é um raycaster que roda em Bash e renderiza uma tela pseudo-3D dentro do terminal
- Os controles usam as setas para girar e se mover, e
qpara sair - Mais capturas de tela e vídeos estão no álbum do Imgur
- A implementação é, em grande parte, um port do tutorial de raycasting de Lode Vandevenne
Restrições que dificultaram a implementação
- O maior problema é que Bash é lento
- O autor afirma que, mesmo se for preciso executar apenas um comando por pixel, é difícil obter uma taxa de quadros aceitável
- Mesmo mantendo o estado da tela em um array de cores, o acesso a elementos arbitrários do array é em tempo linear, o que vira um problema
- Mesmo mantendo o estado da tela como uma única string longa, acessar o n-ésimo caractere é em tempo linear mesmo com
LANG=C; só ler para despejar na tela pode levar mais tempo que um frame
- Bash não tem suporte a ponto flutuante nem acesso a uma biblioteca de funções matemáticas
- Toda a matemática é feita com inteiros
- Os valores inteiros são ampliados por uma escala de 64K para os cálculos
- Usar um caractere como se fosse um pixel no terminal não fica bom, então o projeto usa Unicode half block
- Ao definir cores diferentes de primeiro plano e de fundo, a resolução vertical é efetivamente dobrada
- Não há como atualizar apenas uma das duas cores em uma célula
- Também não há como consultar a cor da célula atual e, em Bash, até essa consulta seria lenta demais
- Por isso, sempre que escreve um pixel, é preciso saber a cor do pixel adjacente
Problemas de terminal e entrada/saída
- Atualizar o terminal inteiro de uma vez usando Bash, uma linguagem lenta, não é algo simples
- A maioria dos terminais não foi projetada para videogames, então não permite testar o estado das teclas atualmente pressionadas
- Normalmente, só dá para obter a entrada de uma única tecla sendo mantida pressionada
- A repetição de entrada passa por debounce lento, e o limite de entrada contínua também é baixo, levando a situações de cerca de 5 a 6 caracteres por segundo
- Também é difícil obter a pressão simultânea de várias teclas que não sejam modificadoras
- O autor afirma que o kitty keyboard protocol resolve esse problema
- Preencher o terminal com cores exige muitos dados
- No tamanho de fonte usual do autor, isso gera cerca de 10 MB/s de I/O
- Bash não usa uma única syscall ao imprimir strings com várias quebras de linha
- Este projeto não imprime
\ne move o cursor de outra forma
- Este projeto não imprime
FAQ e requisitos de execução
- Se a janela quebrar ao ser redimensionada, se houver muita cintilação ou se a aparência ficar ruim em um terminal específico, o autor pede para abrir uma issue
- Se a CPU esquentar muito ou se um computador antigo ficar lento, a orientação é reduzir a resolução ou definir a variável de ambiente
FPSpara menos de 30- Como o Microsoft Defender é conhecido por degradar bastante o desempenho, é sugerido desativá-lo
- O autor responde que o projeto não funciona em Bash anterior à versão 5.2
- O código não é feito apenas de Bash puro
- Na inicialização, chama
sttyuma vez para desligar o echo - Ao sair, chama
sttyuma vez para religar o echo - Após o encerramento, algumas estatísticas são coletadas com outras ferramentas
- Na inicialização, chama
Estado do roadmap
- Itens concluídos
-
pseudo-3D semi-preciso
- fluid movement
- decent framerate
- parallel rendering
- 24 bit colours
- kitty keyboard protocol
- framerate-independent speed
- sound, mas ainda como um protótipo muito inicial
- dynamic wall colours
- dynamic map, atualmente não é alterado por eventos, mas é tecnicamente dinâmico
- basic animations effects for walls
- basic on-screen minimap
- Itens não concluídos
-
mouse support
- textures
- sprites
- objects/enemies
- particles
- better perf
- multiplayer
-
1 comentários
Comentários do Hacker News
echopara cada pixel, e o método é bem inteligente.Como o jogo não é 3D “de verdade”, ele só precisa executar o rastreamento de raios uma vez por coluna e desenhar apenas algumas linhas correspondentes ao céu, à grama e aos objetos reais.
A ideia é imprimir no terminal uma string do tipo “desenhe este pixel e mova uma posição para baixo”, usando repetição de strings pelo número de vezes necessário.
Não é para Bash, mas eu estava pensando em criar um motor de renderização de voxels em outro ambiente com recursos computacionais limitados, e acho que certamente vou encontrar algo útil aqui.
VoxelCanvas.jstambém pode ser interessante. É um arquivo JavaScript e usa a mesma ideia de projeção de raios: https://github.com/EngineersNeedArt/Mooncraft2000sttyprecise de fork. Talvez o próximo projeto seja chamar oioctlnecessário com Bash e rowhammer, sem fork.Não tenho matemática suficiente para entender a implementação, mas só de ver já é divertido.
Entendo que alguns apps precisem de todos os comportamentos peculiares do
vt100, mas provavelmente 90% dos apps só escrevem na saída padrão e no erro padrão.Deveria ser possível despejar texto na tela um pouco mais rápido e colocar os outros 10% em modo de compatibilidade, não?
Dá para renderizar animação até em um terminal de 350 colunas, e, considerando as restrições, ela sai bem fluida.
Além disso, a própria premissa deste texto é que Bash é uma linguagem inadequada para projeção de raios. É parecido com implementar bubble sort em CSS.
Nada impede “colocar os outros 10% em modo de compatibilidade”. Bastaria verificar se a string contém apenas caracteres normais e usar um caminho rápido.
O problema é que, na renderização de texto por software, na prática não existe um caminho rápido. Ainda é preciso lidar com coisas como ligaduras.
É por isso que esse é um dos motivos pelos quais não uso Bash para scripting. Nem uso interativamente.
Algumas distribuições Linux populares também evitam Bash como shell de scripting.
pssem fork do autor para criar quase uma implementação de psDoom sem fork.Brincadeiras à parte, é realmente muito legal.
awkde 9 anos atrás também merece uma menção honrosa: https://github.com/TheMozg/awk-raycaster/tree/master