1 pontos por GN⁺ 4 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • Catlantean 3D é um projeto paralelo que busca criar um FPS completo com as limitações típicas dos jogos de PC do início dos anos 1990, tendo como meta renderização em 320x240 com paleta de 256 cores
  • Como o renderizador lida apenas com índices de paleta, ele pré-calcula um colormap de 32 níveis para escurecimento por distância e, em tempo de execução, escolhe a cor escura com uma consulta O(1)
  • A produção de assets se divide entre sprites pré-renderizados com base no Blender, sprites e texturas desenhados à mão e texturas procedurais geradas por scripts em Python
  • Um HUD desenhado à mão e regras de escala por pixel são restrições centrais para manter nitidez e legibilidade em baixa resolução, com 1 unidade do mundo definida como 64 pixels
  • Em vez de usar o Tiled, o autor criou seu próprio editor de mapas e planeja disponibilizar o mesmo editor aos jogadores após o lançamento, além de publicar o código-fonte do jogo como open source no GitHub

Objetivos e restrições do projeto

  • Catlantean 3D é um projeto paralelo desenvolvido lentamente há mais de um ano no tempo livre, com meta de lançamento na Steam no ano seguinte
  • O objetivo é criar um FPS completo e publicável usando técnicas comuns no início dos anos 1990
  • Compiladores modernos e camadas de abstração de plataforma são permitidos, mas a abstração fica limitada basicamente a framebuffer para escrever pixels, entrada de teclado e mouse, buffer de áudio para escrever samples e I/O de sistema de arquivos
  • O jogo precisa ser feito inteiramente do zero, incluindo todos os assets, e tanto a renderização quanto a mixagem de som precisam ser implementadas manualmente
  • A resolução-alvo é 320x240, e cada pixel da tela pode usar apenas uma das 256 cores da paleta
  • A lógica do jogo usa ponto fixo para garantir comportamento determinístico, enquanto a renderização usa ponto flutuante, já que determinismo ali é menos importante
  • O resultado precisa ser um jogo completo e divertido de jogar, não apenas uma demo técnica, e não usa conteúdo gerado por IA
  • Tudo o que foi mostrado ainda está em desenvolvimento e pode mudar bastante

Renderização com paleta

  • Gráficos VGA

    • O Mode 13h do hardware VGA era um modo gráfico de 320x200 com 256 cores, famoso por definir toda uma geração de jogos de PC
    • Do ponto de vista do programador, o Mode 13h oferecia um framebuffer linear em que cada pixel era representado por 1 byte, correspondente a um índice na paleta de 256 cores
    • Para desenhar um pixel, bastava escrever 1 byte em um endereço específico, sem precisar lidar com conceitos como shaders ou VRAM
    • Assets gráficos modernos podem usar milhões de cores, mas com o limite de 256 cores toda escolha de cor precisa ser cuidadosa e intencional
    • Jogos como Doom e Duke Nukem são citados como exemplos em que a nitidez e a clareza visual surgiram justamente das limitações técnicas
    • Catlantean 3D tenta recriar essa sensação, mas escolhe 320x240, mais próximo do VGA Mode-X, em vez de 320x200
    • Exibir 320x200 em uma tela 4:3 produz pixels não quadrados; embora isso seja mais autêntico, foi evitado por preferência pessoal
  • Paleta

    • A paleta começa com 768 bytes e foi escolhida após muitas tentativas, erros e iterações
    • Há uma cor rosa forte reservada para transparência, uma para branco puro e uma para preto puro
    • Era necessário ter muitos tons de vermelho para representar sangue, além de verdes e azuis para chaves vermelhas, verdes e azuis e portas codificadas por cor
    • O cenário é Catlantis, uma terra-paródia parecida com o Egito Antigo por causa do culto a gatos, então eram necessários muitos tons arenosos de amarelo e marrom
    • Como Catlantis foi ocupada por homens-cão cibernéticos, também eram necessários muitos tons de cinza para instalações tecnológicas
    • Tons bege foram incluídos para quebrar a monotonia dos cinzas e servir como alternativa mais quente ao escurecer
    • As cores restantes foram preenchidas conforme surgiam necessidades na produção de texturas e definidas de forma subjetiva, quando “pareciam certas”
    • A paleta não foi finalizada de uma vez; ela continuou sendo ajustada durante a criação de assets, testes e novas iterações

colormap e tratamento de iluminação

  • Estrutura do raycaster

    • Catlantean 3D é um raycaster tradicional, com mapas compostos por tiles de tamanho uniforme
    • Alguns tiles são paredes, enquanto outros são espaços vazios com apenas piso e teto
    • Para cada coluna da tela, o renderizador percorre o tilemap com o algoritmo DDA até encontrar o ponto de colisão com a geometria do mapa
    • Com base nesse ponto, desenha na tela a coluna de parede amostrada da coordenada correta da textura
    • Piso e teto são renderizados depois com scanlines horizontais para preencher o restante da tela
    • Renderizar o mundo apenas com a paleta produziria uma imagem chapada e sem impacto
    • Quando a luz diminui com a distância do jogador e um lado do tile do mapa fica um pouco mais escuro que o outro, surge uma sensação de profundidade
  • Escurecimento baseado em paleta

    • Em renderizadores modernos com aceleração por hardware, é fácil escurecer cores em shaders multiplicando vetores de cor por coeficientes em ponto flutuante com base na distância aos vértices
    • Um renderizador com paleta não lida com cores em si, apenas com índices de paleta, então para encontrar uma versão mais escura de uma cor específica seria preciso procurar na paleta inteira
    • Fazer isso para cada pixel renderizado seria lento demais, então é feito um pré-cálculo antes da execução para permitir consultas rápidas de cor
    • Se a paleta for organizada em uma linha e forem escolhidos 32 níveis de iluminação, cada cor precisa de 31 variações mais escuras além da original
    • O sistema calcula uma cor-alvo escurecida a partir do RGB de cada cor e do índice de iluminação, mas essa cor pode não existir de fato na paleta
    • Para construir o colormap, a paleta é percorrida em busca da cor existente mais próxima da cor-alvo
    • No início foi usada distância euclidiana, mas muitas cores tendiam a puxar para o cinza, deixando os tons escuros frios e sem vida
    • Depois disso, as cores passaram a ser convertidas para o espaço Oklab e comparadas com uma fórmula de distância perceptual mais próxima da percepção humana
    • Também foi aplicado hue shifting, uma ideia comum em pixel art, deslocando levemente o tom para cores mais quentes à medida que a cor escurece
    • O colormap é uma matriz 2D de índices de paleta representando os níveis de iluminação de cada cor e, como ainda só se pode usar cores da paleta, os gradientes não ficam perfeitos
  • Redução do custo em tempo de execução

    • Uma vez definido o índice da linha do colormap com base na distância, basta pegar o item N daquela linha para obter o índice de paleta da versão escurecida da cor N
    • Assim, a escolha da cor escura em tempo de execução vira uma consulta O(1)
    • Na renderização de paredes, como a coluna inteira da parede é vertical e todos os pixels daquela coluna estão à mesma distância da câmera, o índice da linha do colormap é calculado só uma vez por coluna da tela
    • Na renderização de piso, como todos os pixels de uma mesma linha horizontal estão à mesma distância, o cálculo é feito só uma vez por linha da tela
    • Sprites são billboards planos cujos pixels ficam todos à mesma distância da câmera, então o cálculo é feito uma vez por sprite visível
    • Basta calcular 320 vezes para as paredes, no máximo 240 vezes para o piso, e uma vez para cada sprite visível; além disso, o raycasting já oferece remoção de objetos ocultos “de graça”
    • Doom e vários outros jogos também usavam abordagens parecidas

Como os assets são produzidos

  • Três categorias de assets

    • As texturas e sprites de Catlantean 3D se dividem em três categorias
    • A primeira é de sprites pré-renderizados a partir de modelos 3D feitos no Blender
    • A segunda é de sprites e texturas desenhados à mão
    • A terceira é de texturas procedurais geradas por scripts especiais em Python que combinam arte desenhada à mão
  • Sprites pré-renderizados

    • Sprites complexos e animados exigem editar vários frames, o que torna a iteração trabalhosa e demorada
    • Uma forma mais eficiente é criar modelos 3D no Blender, fazer rig e animação, e usar scripts com a API Python do Blender para renderizar várias texturas
    • As mudanças são feitas no modelo e o script de renderização cuida do resto, reduzindo bastante o tempo de iteração
    • A principal dificuldade foi que os sprites renderizados saíam muito borrados e com aparência desbotada
    • Renderizar em alta resolução e reduzir com filtragem trouxe resultados mistos, porque muitos detalhes eram suavizados e a nitidez das bordas se perdia
    • A abordagem mais eficaz e reutilizável foi usar o compositor do Blender para obter contraste e nitidez adequados
    • Quando a imagem fica pronta, um script especial em Python faz a quantização para a paleta e gera imagens de 1 byte por pixel usadas pelo engine
    • Para cada pixel da imagem original, o script encontra a cor da paleta perceptualmente mais próxima com base em Oklab e usa o índice dessa cor como valor do pixel
    • O array de índices e as informações de tamanho são empacotados em um formato TEX simples usado pelo jogo
    • Sprites de inimigos podem ter várias animações, e cada animação precisa de frames para as 8 direções para as quais o sprite pode estar voltado
    • O script em Python gira o sprite para cada animação, renderiza todos os frames e repete o processo
    • Os nomes dos arquivos seguem uma convenção que indica nome do sprite, nome da ação, direção e índice do frame
    • Os sprites renderizados não ficam no repositório; entram no .gitignore, e em outro computador o script de build renderiza todos os modelos para gerar os sprites
    • Em uma RTX 3070, processar cerca de 15 modelos leva aproximadamente 10 segundos
  • Sprites e texturas desenhados à mão

    • No começo do desenvolvimento, foi feita no Blender uma cabeça em forma de gato com textura do gato Vilko para usar como rosto da barra de status
    • O resultado parecia preguiçoso, de baixo esforço e pouco expressivo, e foi uma das primeiras coisas apontadas pelas pessoas ao comentar o clima do jogo
    • Alguns elementos simplesmente precisam ser desenhados à mão, e concluiu-se que uma versão desenhada e animada ficaria muito melhor
    • Devido ao tamanho dos sprites, cada pixel precisa ser intencional, sem espaço para deixar isso por conta do renderizador do Blender
    • A mesma lógica passou a valer para a maior parte dos itens coletáveis, porque os resultados pré-renderizados anteriores não ficavam consistentemente bons em escala pequena
    • Depois de passar por trabalho manual, a nitidez e a legibilidade dos itens coletáveis melhoraram bastante
    • Aumentar simplesmente a resolução dos sprites permitiria ao rasterizador do jogo escalá-los, mas o resultado não ficava bom por causa da inconsistência na escala dos pixels
    • Existe uma expectativa inconsciente de que, ao se mover para frente ou para trás na mesma linha ou coluna da tela, o tamanho dos pixels permaneça igual; se cada sprite tiver uma escala de pixel diferente, a imagem fica estranha
    • Em Catlantean 3D, 1 unidade do mundo corresponde a 64 pixels, e todos os sprites são produzidos seguindo essa escala
    • Um sprite com altura de um quarto de unidade do mundo precisa ter 64/4=16 pixels de altura

HUD e pipeline de geração procedural

  • HUD

    • O HUD e quase todos os seus componentes são posicionados e desenhados manualmente
    • A barra de status na parte inferior, vários painéis e telas de transição e as fontes entram na categoria de HUD desenhado à mão
    • Em vez de pintar tudo diretamente, há uso intenso de efeitos de camada e composição no Affinity Photo
    • Entre os efeitos usados estão emboss para dar sensação 3D a superfícies planas, geração e overlay de ruído para criar aspereza, overlay de cor, modos de blend e efeitos de glow
    • Como os elementos do HUD são revisados com frequência, a praticidade de reposicionamento via camadas também é importante
    • Em geral, o trabalho começa em truecolor no Affinity Photo, e muitos elementos são apenas retângulos sólidos com efeitos especiais e blend aplicados
    • As imagens exportadas do Affinity Photo continham artefatos estranhos, aparentemente relacionados a antialiasing, que não puderam ser desativados de forma confiável
    • Como ele não era adequado para trabalho com precisão de pixel, etapas adicionais foram feitas no Aseprite, como texto pixel-perfect, recorte de partes da arte e reforço de contornos mais nítidos
  • Texturas de geração procedural

    • Algumas texturas são simples ou específicas o bastante para serem desenhadas manualmente, mas muitas compartilham variações de desgaste, sujeira e detalhes de superfície sobre materiais-base
    • Desenhar manualmente cada variação seria tedioso e inconsistente, então elas são geradas por scripts em Python
    • O pipeline de geração recebe como entrada um heightmap que define o relevo da superfície, um noise map para variação, um grime map para sujeira e desgaste, duas cores-base e um brightmap
    • O heightmap é usado na prática para gerar um normal map, que por sua vez é usado para “assar” iluminação e sombras simples
    • O brightmap define as partes que devem manter sua cor independentemente dos outros parâmetros
    • O script cria a textura final e também faz a quantização para a paleta, deixando-a pronta para uso imediato no engine
    • Modificar uma textura passa a ser uma questão de ajustar parâmetros em vez de redesenhar pixels, o que economiza muito tempo em um projeto solo

gibs e efeitos pré-renderizados

  • Gibs

    • Quando um inimigo recebe dano excessivo, como um tiro de shotgun à queima-roupa ou uma explosão, normalmente ocorre gibbing
    • Para transmitir o impacto do dano, o inimigo explode em pedaços ensanguentados por meio de uma animação
    • Esse pipeline é conduzido por um script em Python que recebe um sprite, a paleta e um conjunto de parâmetros para gerar os frames de animação que entram nos dados do jogo
    • Na primeira etapa, Voronoi decomposition, K pixels-semente são escolhidos aleatoriamente no corpo opaco do sprite, e todos os pixels são atribuídos à semente mais próxima
    • Cada célula resultante vira um pedaço voador
    • Na segunda etapa, wound bleeding, os pixels de borda adjacentes a outros pedaços são marcados como feridas de profundidade 0, e uma BFS se espalha para dentro atribuindo valores de profundidade
    • Na renderização, os pixels perto da borda são misturados em direção a cores de sangue de uma ramp derivada da paleta do jogo, enquanto no interior dos pedaços a cor original do sprite é mais preservada
    • A escolha da ramp da paleta é parametrizada, então certos inimigos podem usar “sangue” verde ou azul
    • Na terceira etapa, physics, cada pedaço recebe um centro, uma velocidade de dispersão aleatória apontando para fora a partir do centro do sprite, velocidade angular, gravidade e drag
    • Não há detecção de colisão, mas os pedaços param ao atingir o chão, o que é simples, porém suficiente
    • Quantidade de pedaços, força da explosão, gravidade, drag, dispersão e profundidade da ferida podem ser ajustados por parâmetros
    • É preciso um pouco de tentativa e erro para encontrar sementes visualmente boas, mas ainda assim é mais rápido do que desenhar a animação à mão
    • A mesma técnica também é usada em objetos destrutíveis do cenário, como vasos, barris e caixas
    • Assim como as animações pré-renderizadas, os resultados de gibs não ficam no repositório; são gerados novamente após o checkout, e o tempo de execução disso é insignificante
  • Sistema de partículas pré-renderizado

    • A maior parte dos efeitos de partículas é desenhada à mão no Aseprite, mas alguns são gerados e “assados” do mesmo modo que os gibs
    • Um script em Python roda a simulação para criar uma sequência de frames em PNG, que depois é quantizada para TEX
    • Não existe sistema de partículas em tempo de execução; todos os efeitos são pré-assados para que o rasterizador em software possa renderizar o mais rápido possível
    • Aqui, a palavra “particle” é um pouco enganosa, porque na prática não há simulação de partículas individuais
    • Cada frame é composto calculando um campo radial de energia por pixel e somando várias camadas independentes
    • O core é um disco suave que se expande para fora ao longo da animação
    • Os rays são feixes pontiagudos ao redor do core; é possível configurar sharpness e length, e cada ray recebe uma variação pseudoaleatória de comprimento para parecer irregular
    • O ring é uma shockwave expansiva opcional, e o noise multiplica a energia total por value noise para transformar formas limpas em algo mais áspero e irregular
    • A energia acumulada por pixel é quantizada de acordo com uma ramp da paleta especificada pelos parâmetros do script
    • Como a paleta foi desenhada para que cada linha possa ser tratada como um gradiente do claro ao escuro, o sistema escurece pixels apenas com aritmética de índices de paleta, sem cálculos de blend ou alpha
    • Acima de certos limiares, os pixels são empurrados em direção ao branco para dar a impressão de um núcleo white-hot
    • Opcionalmente, pequenos sparkles podem ser espalhados por cima; essas formas em cruz se movem para fora e desaparecem ao longo de sua vida útil
    • A animação suporta tanto modo one-shot, em que cresce e desaparece, como em explosões ou flashes de teleporte, quanto modo loop, em que o primeiro e o último frame se encaixam sem emenda
    • O modo loop é útil para efeitos repetidos e contínuos, como plasma bolts e energy projectiles

Editor de mapas e ecossistema de ferramentas

  • A edição de mapas começou no Tiled, que em geral era uma ferramenta razoável, mas não tinha alguns recursos específicos de que o jogo precisava
  • O Tiled não tinha pintura de light level por célula, cell flags nem um conceito de propriedades específicas do jogo, então no começo isso foi contornado com uso excessivo de object properties
  • Também era necessário um script em Python para converter a saída JSON do Tiled para o formato binário usado pelo engine, o que adicionava mais uma peça para compensar o desalinhamento entre a ferramenta e as necessidades do jogo
  • Se um jogador precisasse instalar o Tiled, aprender a interface e configurar o script de conversão para criar mapas, a barreira seria tão grande que praticamente eliminaria a chance de o editor ser usado de verdade
  • O editor próprio oferece suporte nativo a pintura de light level, cell flags e todos os tipos de entity e property que o jogo conhece
  • Quando o jogo for lançado, os jogadores também receberão o mesmo editor usado no desenvolvimento
  • O editor é plug and play e permite executar o nível diretamente a partir dele
  • O autor sabe que os ícones da barra de ferramentas são horríveis e justamente por isso pretende mantê-los assim
  • O editor foi feito em wxPython, que se encaixou melhor do que tkinter em widgets, tratamento de eventos e layout
  • O resultado com wxPython também parecia mais nativo, e a iteração foi rápida
  • Uma estrutura centrada no padrão MVP separa de forma limpa a lógica da UI e os dados do mapa, algo importante porque o formato do mapa ainda não está estável e os dois lados mudam com frequência
  • Nem tudo no editor foi escrito em Python; boa parte do model depende da biblioteca pybast
  • pybast é um binding interno em Python do engine via pybind e fornece leitura do game data archive, leitura das texturas do jogo, uma classe de ponto fixo para coordenadas de entities e serialização
  • Essa foi a forma escolhida para não reimplementar em Python funcionalidades já existentes em C++, e engine e ferramentas acabam formando um ecossistema pequeno e bem integrado

Plano de lançamento e forma de publicação

  • A expectativa é revelar Catlantean 3D no primeiro trimestre de 2027
  • O foco atual está em level design, adição de inimigos e armas e trabalhos contínuos de polish
  • A meta de preço fica na faixa de 5 a 8 dólares
  • O código-fonte do jogo deve ser publicado como open source no GitHub
  • O data archive real, com gráficos, fases, sons, música etc., só será recebido por quem comprar o jogo
  • Transparência no processo é vista como um dos poucos elementos capazes de construir confiança contínua
  • Diferentemente de jogos AAA, jogos indie dependem de um público menor, mas esse público tende mais a acompanhar o projeto, torcer por ele e divulgá-lo para outras pessoas
  • Mostrar o processo de trabalho é apresentado como a forma mais honesta de demonstrar que o autor realmente se importa com o que está criando

1 comentários

 
GN⁺ 4 시간 전
Comentários do Hacker News
  • Se você quiser brincar com renderização por software, há um exemplo quase no menor código possível para colocar com eficiência na tela, em todas as plataformas, um array 2D ARGB8888 na memória principal usando SDL2 e C: https://gist.github.com/CoryBloyd/6725bb78323bb1157ff8d4175d...
    Você mesmo precisa fazer a conversão de um framebuffer com paleta 320x200x8 bits para ARGB ;)
    Se quiser se inspirar no que dá para fazer com um framebuffer com paleta, clique em Show Options em http://www.effectgames.com/demos/canvascycle/ ou veja a palestra do artista na GDC: https://youtu.be/aMcJ1Jvtef0
    Depois, se quiser uma vibe clássica de Deluxe Paint IIe, abra https://github.com/mriale/PyDPainter; se quiser uma ferramenta mais moderna, abra https://www.aseprite.org/

    • Pelo menos no SDL3, você não precisa mais de renderer nem de texture. Basta obter a surface com SDL_GetWindowSurface e exibi-la com SDL_UpdateWindowSurface
      Pelo que entendi da biblioteca, essa é a forma mais próxima possível de gráficos por software, e o SDL ainda cuida do double buffering para você

    • É com certeza a forma mais básica. Se quiser uma pequena otimização no loop interno, basta pré-calcular o offset da scanline antes de entrar no loop de pixels:
      int s = y*screenRect.w;

      for (int x = 0; x < screenRect.w; x++) {
      pixels[s + x] = argb(255, frame>>3, y+frame, x+frame);
      }

    • Obrigado por compartilhar. Já existem vários forks populares de Quake, mas a Planimeter distribui o fork Quake-VS2026, que não inclui mudanças
      A equipe está trabalhando em builds x64 e, para isso, precisa substituir a antiga SciTech Multi-platform Graphics Library (somente x86) por SDL3. A outra opção seria portar a scitech-mgl para x64, o que parece improvável, e, pelo que eu sabia por último, o renderizador por software talvez acabasse ficando de fora
      Mas talvez ainda dê para preservá-lo com o renderizador por software e SDL_Texture

  • Este artigo se inspirou bastante em Doom, mas o motor de ray casting real é mais próximo dos predecessores de Doom, especialmente Wolfenstein 3D
    Ele usa paredes verticais e alturas fixas de chão e teto. O Wolf3D não tinha chão e teto texturizados por razões de desempenho, mas outros jogos parecidos tinham
    Doom e, se bem me lembro, Duke Nukem também usavam um motor BSP muito mais flexível, em que as paredes podiam se cruzar em ângulos arbitrários e a altura do chão e do teto podia variar. Ainda assim, os níveis continuavam “planos”, então não era possível criar vários andares em um mesmo nível; por exemplo, você não podia projetar uma ponte com passagem tanto por cima quanto por baixo

    • O engine Build não usava BSP. Ele tratava as conexões entre setores como portais, fazia clipping nesses portais e rasterizava as paredes como trapézios girados 90 graus
      Isso permitia geometria de paredes dinâmica, como trens em movimento ou luzes giratórias, e também tornava possível uma configuração de “sala sobre sala”, desde que você não pudesse ver dois cômodos ao mesmo tempo
      Em Blood e Shadow Warrior, eles criavam setores com a mesma forma e usavam o chão de um setor como um portal para o teto de outro, criando por gambiarra espaços mais próximos de “3D” de verdade. Não era algo que o engine suportasse originalmente, mas ele era flexível o bastante para que estúdios sem sequer acesso ao código-fonte conseguissem fazer isso
      A primeira fase de Duke Nukem 3D também usa alguns truques do Build. Por exemplo, sprites podem ficar alinhados ao eixo em vez de girar acompanhando a câmera e também podem ter colisão, então, se você tratar cada sprite como um retângulo alinhado ao eixo, consegue criar geometria 3D básica. Na primeira fase, isso foi usado para criar a ponte entre dois prédios logo antes do botão de saída

    • Blake Stone e Rise of the Triad usavam versões posteriores do engine do Wolf3D e tinham chão e teto texturizados
      O engine Build de Duke Nukem não usava BSP

      https://www.jonof.id.au/forum/topic-137.html#msg1548

    • Acho que depois, em Shadow Warrior, isso também era possível. Lembro que era implementado com portais e era bem doloroso de configurar no editor

    • Sobre o chão, até onde eu sei, nem o DOOM fazia isso corretamente. Em paredes verticais, para um determinado trecho da parede, basta fazer a divisão de perspectiva uma única vez por coluna de pixels
      Já no chão, infelizmente, esse luxo não existe e, se bem me lembro, o DOOM dividia o chão em patches, calculava a perspectiva correta apenas nos cantos e interpolava o resto no meio

    • No começo, achei que fosse só um Wolfenstein 3D com a skin trocada. Isso teria sido um julgamento tremendamente injusto; claramente houve muito trabalho aqui

  • Excelente texto. Gostei especialmente da abordagem para criar a animação de gib.
    Era um demo técnico, mas em meados dos anos 90 eu também fiz algo parecido. Um ponto que não apareceu neste texto é que usar lightmaps de 8x8 ou 16x16 na textura facilitava criar efeitos como tochas tremeluzentes ou foguetes passando por corredores e iluminando tudo. Se quisesse, também dava para usar lightmaps para “assar” a iluminação
    Como o lightmap tinha “apenas” 8x8, dava para aguentar a matemática de calcular, para cada luxel, ou seja, cada unidade do lightmap, a distância até a fonte de luz e a linha de visão para obter o valor de brilho. Na hora de renderizar a textura, os luxels eram usados junto com uma tabela de consulta para determinar a cor real do pixel desenhado
    Pelo que lembro, por desempenho os lightmaps eram atualizados 15 vezes por segundo. Graças ao DJGPP, usei assembly inline na renderização e, como operações de ponto flutuante eram lentas na época, usei aritmética de ponto fixo, que era bem otimizada. Para os computadores daquela época, o desempenho de renderização era surpreendente

    • A ideia de ponto fixo parece ser usada e valorizada muito menos do que deveria. Há realmente muitas áreas em que ela é a melhor escolha, e às vezes até com desempenho superior
  • A programação gráfica do começo e meio dos anos 90 era bem divertida. Você escrevia os dados dos pixels na VRAM mapeada em memória e eles apareciam imediatamente na tela
    Bastava um ponteiro para 0xA0000, e não era preciso nenhuma API. O motivo do citado modo VGA 320×200 com pixels não quadrados era que o buffer de vídeo tinha 64000 bytes, então cabia em um segmento de 16 bits e isso facilitava o endereçamento em código e CPU de 16 bits

    • Sempre achei curioso que, embora o PC tivesse uma CPU monstruosa em comparação com os consoles da época, ele ainda sofresse para implementar scroll suave como o Mario do NES de 1985, por causa da arquitetura gráfica
      Mas essa fraqueza também permitia fazer muito mais trabalho por pixel na tela, e foi isso que tornou possíveis sistemas como raycasting e árvores BSP
      Não havia processadores dedicados para sprites e camadas de fundo, mas em compensação o PC não ficava preso a uma estrutura rígida de funções fixas
      Quando os processadores 3D dedicados apareceram em meados e no fim dos anos 90, isso deixou de ser um problema, mas por um breve período no começo dos anos 90 existiu um playground de renderização visual bem único
    • Um ponteiro para 0xA0000 realmente bastava, mas o extender em uso podia tornar essa parte um pouco mais chata :-P
      DJGPP e Free Pascal usam o mesmo extender go32 do DJ Delorie, e como ele não fazia um mapeamento linear completo, era preciso mexer um pouco mais para colocar algo na tela
    • Antes do VGA aparecer, a história era muito mais complicada
  • O mais interessante são as ferramentas internas. Coisas como o script em Python para criar a animação de gib, ou outro script em Python para gerar spritesheets 2D a partir do Blender
    O autor original claramente parece ser um engenheiro 10x que também sabe produzir boa arte, e acho isso realmente raro. O fato de haver uma direção de arte consistente também foi bem surpreendente

    • Para quem era fã desse gênero nos anos 90, parecia que esses engenheiros renascentistas estavam sempre por trás dos grandes sucessos. Ainda me lembro de alguns nomes, e eles eram artistas de verdade
      Nos últimos 15 anos da indústria de games, tirando CEOs e diretores principais, quase não sei o nome de ninguém
  • Acabei de perceber que este jogo talvez seja um dos raros shooters com protagonista feminina. O gato tem pelagem calico, e gatos assim quase sempre são fêmeas (https://en.wikipedia.org/wiki/Calico_cat)

    • Shooter com protagonista feminina é assim tão raro? Só de cabeça, já dá para citar vários títulos bem mainstream como Perfect Dark, Mirror's Edge, Dishonored 1 ou 2, Metroid etc., todos sendo algum tipo de shooter com protagonista feminina
      Claro, sendo 100% exato, Mirror's Edge está mais para “primeira pessoa” do que propriamente para “shooter”
      Além disso, entre os “RPG + FPS” também há muitos jogos em que você pode jogar como homem ou mulher
      O autor também parece saber da pelagem e das possibilidades de sexo do gato:

      After all, I do need to give the protagonist his fair share. [image] (Yes, I know it's a female, but call it convention rooted in dialect.)

    • Este não é o jogo Perfect Dark

    • Hoje em dia há bastante protagonista feminina em boomer shooters. Por exemplo Selaco[0], Supplice[1], The Citadel[2] e a continuação[3], Zortch[4] e a continuação ainda por vir[5], Nightmare Reaper[6], COVEN[7], Viscerafest[8], Hedon[9] etc.
      Na verdade, hoje em dia parece até que há mais boomer shooters com protagonista feminina do que sem :-P Se você combinar as tags “boomer shooter” e “female protagonist” na busca da Steam, aparecem 143 resultados, embora isso inclua jogos em que você pode escolher o gênero do personagem ou em que na maior parte do tempo se joga com um homem, mas em alguns trechos se joga com uma mulher

      [0] https://store.steampowered.com/app/1592280/Selaco/

      [1] https://store.steampowered.com/app/1693280/Supplice/

      [2] https://store.steampowered.com/app/1378290/The_Citadel/

      [3] https://store.steampowered.com/app/3371240/Beyond_Citadel/

      [4] https://store.steampowered.com/app/2443360/Zortch/

      [5] https://store.steampowered.com/app/3807500/Zortch_2/

[6] https://store.steampowered.com/app/1051690/Nightmare_Reaper/

[7] [https://store.steampowered.com/app/1785940/COVEN/](<https://store.steampowered.com/app/1785940/COVEN/>;)

[8] [https://store.steampowered.com/app/1406780/Viscerafest/](<https://store.steampowered.com/app/1406780/Viscerafest/>;)

[9] [https://store.steampowered.com/app/1072150/Hedon_Bloodrite/](<https://store.steampowered.com/app/1072150/Hedon_Bloodrite/>;)
  • Pode até não ter sido intencional, mas no geral isso não me impressiona muito nem vejo muito valor nisso. É a mesma coisa que em Hollywood mostrarem uma mulher nocauteando um homem com o dobro do tamanho dela
    Acho irrealista, ridículo e prejudicial

  • Muito legal. Outro truque divertido usado nos anos 90 era a animação de paleta. Só de mudar a paleta já dava para criar efeitos incríveis com custo de execução baixo

    • Sim. Para ver um ótimo exemplo dessa técnica, recomendo muito este site

      http://www.effectgames.com/demos/canvascycle/

    • Também é divertido trocar a paleta no meio do frame. No PC não havia nada como o copper do Amiga, então era preciso prestar muito mais atenção ao timing, mas ainda assim era possível

    • Se bem me lembro, muitos inimigos de Diablo 1 e 2 eram basicamente o mesmo sprite com paletas diferentes aplicadas. É o mesmo truque?

    • Azul é água, roxo é plasma, vermelho/laranja é sangue ou lava

  • Fiquei realmente surpreso com o quão bons os sprites quantizados ficaram depois da renderização. Graças àquela conversão rápida, eles pareciam bem nítidos

  • Como colega desenvolvedor que faz engines 3D com restrições absurdamente severas, foi muito legal ver os detalhes e o processo descritos aqui

  • Estou brincando com uma demo técnica de renderização de espaço de voxels para homebrew de PlayStation. Mesmo depois de só um ou dois dias de trabalho de fim de semana, já estou conseguindo resultados razoáveis na faixa de 10~15 FPS, e ainda não usei DMA, GTE nem mesmo primitivas básicas de polilinha
    É revigorante voltar a mexer com trigonometria e truques antigos de otimização de baixo nível. Quando seu scratch buffer tem 1 KiB e você só pode usar parte disso para a stack, você percebe o quanto os microcontroladores que uso no trabalho são luxuosos. Lá, cada thread ganha uma stack de 8 KiB, e ainda aparecem backtraces com mais de 50 funções de template em C++ empilhadas