5 pontos por GN⁺ 2024-07-29 | 1 comentários | Compartilhar no WhatsApp
  • Com o crescimento de projetos de cenas e níveis 3D executados no navegador, foram se acumulando ferramentas reutilizáveis que aplicam técnicas procedurais e generativas a elementos específicos dos níveis, em vez de gerar mundos inteiros
  • O trabalho com texturas se concentra em Triplanar Mapping e Hex Tiling, usados para aplicar texturas sem UVs ou para ocultar padrões repetitivos
  • Ambas as técnicas aplicam pow() aos pesos de interpolação para aumentar a influência do eixo dominante ou do resultado de amostragem, e reduzem o custo de desempenho ao omitir algumas consultas de textura
  • Shaders de fragmento caros podem ser mitigados com Depth Pre-Pass; em cenas com muito overdraw, o desempenho pode melhorar mais de 30%
  • No lado de malhas e geometria, há uma tendência de expandir a expressão de decoração, planos de fundo e dano por meio de LoD terrain, pipelines de processamento de malha em tempo de execução e, futuramente, Constructive Solid Geometry

Ferramentas procedurais acumuladas em cenas 3D no navegador

  • Ao longo de alguns anos criando cenas e níveis 3D executados no navegador, demos independentes centradas em shaders customizados cresceram para algo mais parecido com um jogo, com partes interconectadas
  • A abordagem comum é aplicar técnicas procedurais e generativas a partes específicas dos níveis, em vez de criar mundos totalmente procedurais
  • Ferramentas e efeitos procedurais e semiprocedurais reutilizados em vários níveis foram se acumulando naturalmente

Shaders e texturas

  • A maioria das texturas é formada por seamless textures, que se repetem sem emendas nos dois eixos; quando espalhadas por áreas grandes, os padrões repetidos podem ficar perceptíveis
  • Vários recursos foram adicionados a shaders customizados que estendem o MeshPhysicalMaterial do Three.JS, melhorando o suporte a seamless texturing
  • Triplanar Mapping

    • Triplanar Mapping é uma ferramenta central de texturização usada em quase todos os níveis
    • Ela permite texturizar uma malha com seamless textures sem um UV map predefinido, o que é útil em casos como terrain gerado proceduralmente, em que um modelador não teve a oportunidade de definir o UV mapping
    • Funciona bem tanto em malhas geradas quanto em malhas modeladas manualmente
    • A implementação é leve e simples, e uma implementação de referência está em triplanarMapping.ts
  • Melhorias no Triplanar Mapping

    • O Triplanar Mapping comum mistura linearmente as consultas de textura dos três eixos com base no normal do fragmento
    • Em áreas onde o normal não está próximo de um único eixo, a textura pode parecer estratificada em camadas
    • Aplicar pow() com um expoente alto aos pesos e depois normalizá-los novamente aumenta a influência do eixo dominante e reduz as regiões de transição
    • Com essa transformação, na maior parte da malha o peso de um eixo fica próximo de 1, e os pesos dos outros dois eixos ficam próximos de 0
    • Ao omitir consultas de textura cujos pesos sejam menores que um limiar, é possível reduzir o custo de desempenho do Triplanar Mapping para apenas um pouco acima da texturização comum baseada em UVs
    • O processamento de normal maps exige considerações separadas no código do shader, e usa-se o método do GPU Gems
    • Os detalhes de implementação podem ser vistos em Normal Mapping for a Triplanar Shader
  • Hex Tiling

    • Hex Tiling é um algoritmo para ocultar o tiling e a repetição perceptíveis de seamless textures
    • Apenas adicionar uma opção de configuração ao material pode fazer uma cena deixar de parecer um mockup de baixa fidelidade e se aproximar de algo semi-realista
    • A implementação inicial foi baseada no Shadertoy de Fabrice Neyret, convertida para o material system do Three.JS e integrada ao shader de material principal do projeto
    • Depois, com permissão, ela foi portada para a biblioteca independente three-hex-tiling, que permite adicionar Hex Tiling aos materiais built-in do Three.JS
    • Diferentemente do Triplanar Mapping, ela exige UV mapping predefinido
    • Usar as duas técnicas juntas pode aumentar o número máximo de texture fetches por fragmento para 27 em cada map, tornando isso impraticável
    • Como o Hex Tiling também interpola linearmente três resultados de consulta por fragmento, a técnica de pesos com pow() usada no Triplanar Mapping pode melhorar tanto o desempenho quanto a qualidade do resultado
  • Depth Pre-Pass

    • Técnicas avançadas de texturização podem criar fragment shaders caros em cenas grandes
    • Depth Pre-Pass consiste em renderizar primeiro a cena inteira com um material muito simples e barato, registrando a profundidade de cada pixel
    • Embora exista o overhead de renderizar a cena duas vezes, em cenas com muito overdraw o ganho geralmente supera o custo
    • Quando há muito overdraw, adicionar Depth Pre-Pass pode melhorar o desempenho em mais de 30%
    • Ao alterar a configuração do pre-pass, é possível renderizar apenas fragmentos ocultos e visualizar os fragmentos que seriam ignorados ao usar o pre-pass
    • Detalhes de implementação e configuração no Three.JS podem ser vistos no artigo dedicado
  • Síntese de texturas PBR baseada em IA

    • Texturas geradas por IA são usadas em quase todas as cenas
    • Quando usadas com moderação, os resultados podem parecer bem bons, e todas as texturas da cena de exemplo são geradas por IA
    • O processo de geração de texturas, criação de PBR maps e combinação em seamless textures 4K sem upscaling é abordado em um post separado
    • O site mencionado nesse post para gerar PBR maps não está mais disponível
    • Atualmente, DeepBump é usado para gerar normal maps e, quando necessário, ferramentas não baseadas em IA como Materialize são usadas para outros maps
  • Volumetric Fog/Clouds

    • Volumetric rendering é uma área que tem atraído interesse por poder dar efeitos únicos às cenas
    • Foi criado um shader relativamente genérico capaz de adicionar clouds ou fog a qualquer cena em Three.JS
    • Inspirado pelo Shadertoy de Inigo Quilez, foi criado um shader básico de volumetric clouds que usa um LoD noise lookup semelhante e depois foi expandido para uma forma mais geral e configurável
    • Esse shader é útil para preencher espaços vazios em níveis mais esparsos e adicionar uma sensação dinâmica a níveis estáticos com clouds ou fog em movimento
    • Também foram usados parcialmente código e abordagem desenvolvidos por n8programs no projeto three-good-godrays
    • three-good-godrays também é usado com frequência e acrescenta uma atmosfera muito característica aos níveis

Malhas e geometria

  • Geração de malhas em tempo de execução é uma área cada vez mais explorada
  • A ideia de um mundo crescendo a partir de uma seed de software é atraente, mas busca-se evitar o fenômeno “infinito, porém vazio” de alguns jogos que se promovem com geração procedural
  • Por isso, em vez de gerar proceduralmente toda a experiência central, o foco principal é adicionar decoração, planos de fundo e floreios procedurais aos níveis
  • LoD Terrain

    • Terrain generation é uma área representativa do desenvolvimento procedural de jogos, e a implementação em si não é especial
    • Como na maioria das abordagens, uma função de noise cria o heightmap do terrain, que é então tessellado em triângulos e renderizado
    • A texturização usa Triplanar Mapping ou Hex Tiling
    • O ponto central é o sistema de LoD, que cria o terrain em tiles e gera cada tile em várias resoluções
    • Resoluções diferentes são trocadas dinamicamente de acordo com a distância entre o tile e a câmera
    • Esse sistema de terrain generation é reutilizado com frequência e, graças à sua flexibilidade e eficiência, pode ser aplicado a vários níveis com pouco esforço
  • Pipeline procedural de processamento e manipulação de malhas

    • A parte que recebeu mais trabalho recentemente foi o pipeline de processamento procedural de malhas
    • O objetivo inicial era subdividir e deformar proceduralmente low-poly meshes, incluindo malhas geradas dinamicamente
    • A intenção é fazer com que malhas simples, como platforms, boulders e estruturas grandes, pareçam mais realistas ou interessantes quando colocadas nos níveis
    • Esse trabalho levou a um pipeline de software que recebe raw geometry data no runtime do navegador, modifica-os livremente e os exporta de volta para um formato renderizável
    • Esse processo exige considerações detalhadas, especialmente no processamento de normals
    • Os detalhes de implementação estão no post subdividing meshes for displacement

Próximo experimento candidato: Constructive Solid Geometry

  • A maioria das ferramentas listadas aqui começou originalmente como uma implementação pontual para um caso de uso específico, mas foi reutilizada repetidas vezes em outros níveis e contextos
  • A próxima grande ideia que se quer experimentar é Constructive Solid Geometry
  • Constructive Solid Geometry é um sistema que aplica operadores booleanos no espaço 3D
    • É possível unir duas malhas arbitrárias
    • É possível recortar chunks de uma malha
    • E executar outras manipulações semelhantes
  • csg.js implementa um toolkit de CSG com mesh primitives, operadores booleanos e uma API limpa em um único arquivo JavaScript comentado de cerca de 500 LoC
  • Há um plano de portar essa biblioteca para Rust algum dia para entender melhor como ela funciona
  • Usar CSG junto com o pipeline de processamento de malhas existente tem grande potencial para gerar resultados interessantes
  • Em especial, há interesse em experimentar a capacidade de danificar malhas proceduralmente
    • Recortar chunks de buildings ou bridges para simular decay ou weathering
    • Gerar cracks em walls ou roads

1 comentários

 
GN⁺ 2024-07-29
Comentários no Hacker News
  • Há algum tempo mexi um pouco com geração procedural, especialmente tentando criar árvores bonitas, mas a peça que faltava para mim era uma forma fácil de conectar geometrias.
    Criar dois cilindros é fácil, mas uni-los de forma natural foi muito difícil.
    Em teoria, CSG poderia preencher essa lacuna, mas não era fácil pensar no problema dessa forma. Não dá para enxergar simplesmente como um loop que adiciona vértices, porque é preciso modelar tudo como formas 3D.
    Também tentei criar uma rotina que recebia dois loops de vértices e adicionava faces heuristicamente para conectá-los, mas escolher quais vértices deveriam se ligar foi muito mais difícil do que eu esperava, e era fácil obter conexões feias.
    Um dia, quero criar um jogo em que diferentes sistemas modulares de geração procedural cooperem mantendo a improvisação. Por exemplo, sistemas que “ocupam” parte do mundo e delegam essa parte a outro sistema, ou a conectam naturalmente aos elementos ao redor.

    • Vale continuar tentando, mas sem complicar demais.
      Houve recentemente um vídeo [0] sobre criação de árvores generativas, e a solução era simplesmente deixar os cilindros se cruzarem. Esse tipo de abordagem meio hacky também funciona bem e pode gerar resultados decentes.
      Como você disse, CSG também é possível, mas pode ser complexo demais. Outra forma é criar o esqueleto da árvore e depois fazer lofting, combinando com CSG se necessário para criar o tronco e a casca.
      Uma boa biblioteca abre possibilidades. Se você encontrar uma biblioteca de geometria 3D que faça operações booleanas, como unir cilindros ou subtrair geometrias, poderá testar muitas ideias novas. Experimentei algumas, e a única de que gostei mais ou menos foi JSCAD [1].
      [0] https://youtu.be/8zMbJmuwEUc?si=KQclrVPeSrIRmsbA
      [1] https://github.com/jscad/OpenJSCAD.org
    • Vale pesquisar campos de distância com sinal. É algo próximo da solução CSG mais elegante para geometria procedural.
      É parecido com descrever e transformar formas 3D de maneira funcional.
      Para ver o que o pessoal da demoscene fez com isso, recomendo muito procurar Mercury Delight no YouTube. Também há muitos exemplos no Shadertoy, e muita coisa realmente impressionante.
    • Se for possível desenrolar o loop, também dá para encarar como uma abordagem de loop que adiciona vértices.
      https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Condition...
      O exemplo abaixo também vale a pena conferir.
      https://github.com/MaxBondABE/batteries/blob/master/src/geom...
  • Uma boa geração de terreno, seja em escala de mundo ou na escala do olhar do jogador, não é nada trivial.
    A abordagem simples, como aparece no texto, cria um heightmap irregular, pouco parecido com terrenos reais e não muito interessante de explorar.
    Por exemplo, Dwarf Fortress começa com deslocamento do ponto médio básico, mas depois passa por bastante pós-processamento personalizado.

    • Acho que esse é um problema de toda a indústria de jogos.
      Nada do que aparece neste texto é nem vagamente trivial. O autor, pelo menos em renderização com shaders de GPU, claramente está mais perto de ser um desenvolvedor 100x do que quase qualquer pessoa no planeta.
      “Abordagem simples”? É simples mesmo?
      Entre 8 bilhões de pessoas, que porcentagem conseguiria implementar sequer a etapa “Hello World” necessária para começar este texto? E quantas sequer sabem o que é um shader? Também não é como se houvesse tantos empregos com OpenGL. Situações do tipo “o que é OpenGL? aqui só usamos Unity” são comuns.
      E quanto aos gamers online? Em 28 de julho de 2024, às 13h22 EST, entre os 1.021.282 [1] conectados ao Counter Strike 2, que porcentagem conseguiria implementar sequer a primeira etapa dos shaders necessários para o jogo que estão jogando?
      Que porcentagem conseguiria compilar um programa C++ simples de linha de comando, ou escrever no navegador um script JavaScript ainda mais simples? Na verdade, essa é meio que uma pergunta capciosa, porque a maioria das pessoas mal consegue lidar com e-mail.
      [1] https://steamdb.info/app/730/charts/
    • Já vi uma abordagem em que se cria primeiro um heightmap irregular e depois se gera o terreno com uma simulação de erosão.
    • Encarei este texto mais como um bom ponto de partida do que como algo “pronto para lançamento”.
      Assim como na IA, a melhor geração procedural de conteúdo também terá pós-processamento personalizado no resultado final.
  • É um texto muito útil por reunir bem várias técnicas procedurais usadas na web.
    Se houver interesse em um editor procedural de níveis de RPG em que estou trabalhando, vale conferir https://github.com/gamedevgrunt/3D-Action-RPG-JavaScript.

  • Minha experiência com pré-passe de profundidade foi mista.
    Nas vezes em que tentei, não vi ganhos de desempenho perceptíveis em GPUs desktop intermediárias a avançadas.
    Não sei exatamente por quê, mas acho que pode ter sido porque a eliminação Z inicial já reduzia as chamadas ao pixel shader. Normalmente renderizo malhas opacas em ordem da frente para trás.
    Dito isso, meus experimentos foram no contexto de aplicações CAD/CAM, não de jogos. As cenas também eram bem diferentes de um ambiente típico de jogo: pouca textura e geometria com contagem de polígonos muito alta.

    • Todas essas técnicas são dependentes do contexto e variam não só conforme o jogo e o engine como um todo, mas também conforme artes específicas e a composição da cena.
      O pré-passe de profundidade muitas vezes também é a primeira etapa do culling por oclusão, mas isso igualmente depende do contexto. É provável que seja muito mais útil em uma paisagem urbana complexa do que em um modelo CAD.
    • Se o gargalo estiver no processamento de vértices, o pré-passe de profundidade não vai ajudar.
      Ele é mais útil quando a complexidade de profundidade é alta e o fragment shader é caro. Para ser justo, a maioria dos jogos se encaixa nisso.
      Em renderizadores diferidos, normalmente não é essencial, mas em forward+ costuma trazer um ganho considerável.