Nota: Este artigo foi publicado originalmente no LinkedIn. Se você gostou do meu artigo, por favor Clique através e considere compartilhar nas mídias sociais e se conectar comigo!

No ano passado, eu estava procurando por um projeto paralelo para trabalhar que idealmente me permitiria começar a aprender sobre geração procedural de uma maneira fácil e interessante, e também poderia ser concluído com cerca de um mês de trabalho. Foi quando comecei a pesquisar sobre L Systems e geração procedural de folhagens.

Os jogos precisam de arte de fundo detalhada. Embora geralmente não seja central para a jogabilidade, a qualidade e a arte de fundo variada são necessárias para contar histórias, imersão, apelo visual e polimento geral. A falta de variedade nas árvores de fundo é perceptível em um jogo. Criar uma variedade de árvores à mão exige muito esforço, e cada variação precisa de um trabalho personalizado de um artista.

A Geração Processual pode ser usada para esse problema reduzindo a necessidade de criação de conteúdo sob medida. Como desenvolvedor, você geralmente não terá controle total sobre seu trabalho de geração procedural. Isso o torna particularmente adequado para esse tipo de conteúdo em segundo plano – conteúdo que é importante, mas não atrapalhará o jogo se não for totalmente perfeito.

Uma abordagem programática para a folhagem também permite que uma aparência e um estilo específicos sejam criados por meio de parâmetros e, em seguida, variem um pouco mais usando esses parâmetros.

LSystems para geração de folhagens

Nenhum texto alternativo fornecido para esta imagem

LSystems têm sido usados ​​extensivamente para geração de folhagens. O algoritmo é naturalmente adequado para produzir árvores e plantas ramificadas. Ele permite um grau de randomização e permite controlar a estética do que você gera através do uso de parâmetros e regras.

LSystems básicos são fáceis. A contagem de ramificação, comprimento, ângulo e iteração cobre isso.

Mas o que você pode fazer para gerar obras de arte mais estéticas?

Adorei a estética do trabalho de Kate Compton e modelei meu próprio trabalho com base nele, com o critério adicional de que qualquer coisa que eu crie deve ser adequada para simulação em tempo real no Unity, para que possa ser usada como uma ferramenta para geração de arte para jogos 2D Unity. Kate compartilhou seu trabalho de PCG de folhagem, bem como guias para quem está começando com PCG – http://www.galaxykate.com/blog/generator.html

Adicionei os seguintes parâmetros:

Iterações

Ângulos de ramificação

Largura do ramo

Comprimento máximo da ramificação

Comprimento máximo da folha

Cor básica

Variação de cor

Saturação

Formas de folhas

Proporção da folha

Contagem de pétalas

Tamanho da pétala

Forma de pétala

Cor da base da pétala

Saturação de pétalas

Espiga de galhos

Conicidade do ramo

Nenhum texto alternativo fornecido para esta imagem

Adicionando Variação Visual

E se você quiser mais variedade para cada árvore gerada, para que não haja apenas duplicatas por toda parte?

Gere uma floresta “mudando-a um pouco”. Pegue o modelo base, armazenado em uma estrutura (DNA), e então altere um pouco os parâmetros relevantes para gerar uma árvore semelhante da mesma espécie. Repita x10 ou x100 e você terá uma floresta uniforme, mas não cheia da mesma árvore clonada.

Além disso, Kate também compartilha alguns princípios gerais para mais variedade visual ao usar técnicas de PCG.

Aqui estão algumas ideias que ela sugere para melhorar a distribuição, então tudo que você gera parece natural e tem alguma variação.

1. Barnacling – Envolva objetos grandes com alguns pequenos ou talvez muitos pequenos para dar uma aparência mais natural.

2. Footing – Certifique-se de que tudo que você gera se encaixa no mundo, basicamente manipulando onde os objetos iriam interagir (neste caso o galho da árvore e o chão se sobreporiam na forma de raízes)

3. Greebling – Originalmente usado por quem trabalha na Luz e magia industriais para efeitos especiais de Guerra das Estrelas. Adicione pequenos detalhes a grandes superfícies para torná-los mais complexos e interessantes.

Nenhum texto alternativo fornecido para esta imagem

Depois de adicionar árvores menores e ligeiramente alteradas ao lado de cada uma:

Nenhum texto alternativo fornecido para esta imagem

Para adicionar a isso, você também pode simular o crescimento – Aumente o comprimento do ramo, a contagem de folhas, a contagem de flores e, finalmente, a contagem de iteração ao longo do tempo.

Animação

Eu fui com a abordagem óbvia primeiro – apenas manipulando a posição e a rotação da malha usando uma onda senoidal. Eu aplico isso com uma variável de força do vento relativa à distância da base da árvore – então, basicamente, a raiz não se move, e as copas das árvores se movem mais. Parece bom o suficiente para um jogo 2D estilizado.

Mais tarde, usei o Shader Graph para mover toda a animação para a GPU – manipular a posição do vértice usando o código do sombreador funciona tão bem e resulta em um desempenho muito melhor.

Estruturas de dados – DNA vegetal

A geração é aleatória, mas determinista. Se você usar os mesmos parâmetros, obterá a mesma árvore novamente. De certa forma, a estrutura dos parâmetros é o DNA de uma árvore.

Gerar uma árvore que você gosta? Salve seu DNA. Carregue-o mais tarde para reutilizar em seu jogo.

Levando isso adiante, você pode cruzar e evoluir espécies usando Algoritmos Genéticos. O DNA da árvore já está definido como uma estrutura de floats.

Simplificando, use um algoritmo genético para combinar duas metades do DNA e depois transformá-las para criar uma nova espécie. Em seguida, configure-o para que um designer possa selecionar duas árvores de que goste e misturá-las.

Você pode estender esse conceito para a jogabilidade – adicione algum conteúdo gerado pelo jogador, permitindo que os jogadores gerem suas próprias plantas, salve-as, evolua-as, replante-as e assim por diante.

Como você otimiza a geração de malha para gerar e simular folhagem em tempo real na unidade

A geração é lenta e leva algum tempo. Contanto que o código não seja desnecessariamente complexo, você pode obter 300 árvores em cerca de um minuto e talvez adicionar uma tela de carregamento se precisar fazer isso durante o jogo.

Mas as árvores geradas também precisam ser otimizadas para desempenho. A arte de fundo é exatamente isso: fundo. Você não pode ter muito trabalho de CPU/GPU e chamadas de desenho sendo ocupadas pela arte de fundo – é necessário para arte de primeiro plano, animações, shaders, iluminação, IA, jogabilidade, etc.

Os ramos são sprites individuais e não há muitos deles.

As folhas são malhas geradas, que possuem 4 vértices cada. Isso é necessário para a aparência estilizada e a variedade. É aqui que a maior parte do tempo de renderização é gasta, então é aí que a otimização deve ser focada.

Veja como eu lidei com a otimização:

Materiais compartilhados

Cada folha tem o mesmo conjunto de parâmetros e as cores são determinadas dentro do sombreador com base na distância do pixel ao solo. Isso significa que uma única instância de um material pode ser compartilhada por cada folha em uma árvore.

Sombreadores otimizados

Para iniciantes, use o URP do Unity e os shaders URP não iluminados associados.

Animação de vento baseada em sombreador

A GPU tende a ser subutilizada em comparação com a CPU, por isso é quase sempre benéfico mover cálculos complexos para a GPU. Nesse caso, é melhor simular o vento aplicando uma função seno aos vértices da malha (eu amo Shader Graph por isso). A alternativa seria alterar as posições e rotações do objeto usando código C#, que quase sempre terá um desempenho pior.

Combine malhas para reduzir ainda mais as chamadas de desenho

Descobri alguns anos atrás, durante um projeto diferente (Jogos Educacionais de RV para a indústria de petróleo e gás) que os lotes Estático e Dinâmico do Unity são ineficientes. Eu assumi que o número de malhas em uma cena não importaria, apenas a contagem de triângulos e vértices. Acontece que geralmente você pode melhorar bastante o desempenho combinando malhas sempre que possível, em vez de confiar apenas em lotes.

Eu amo o recurso Simple Mesh Combine da Unity Asset Store para isso.

Antes de combinar malhas:

Nenhum texto alternativo fornecido para esta imagem
Nenhum texto alternativo fornecido para esta imagem

Depois de combinar as malhas:

Chamadas de sorteio reduzidas para cerca de 33%, Lotes reduzidos para 0,5% da contagem inicial.

Nenhum texto alternativo fornecido para esta imagem

O tempo de CPU e GPU quase metade, o que pode ser traduzido livremente para significar que o desempenho e a taxa de quadros serão 2x melhores.

Nenhum texto alternativo fornecido para esta imagem

Combinado, sem animação

Nenhum texto alternativo fornecido para esta imagem
Nenhum texto alternativo fornecido para esta imagem

Isso ainda ocupa uma quantidade razoável de tempo de renderização devido ao número de malhas usadas. As taxas de quadros ainda estão boas, mas podem ser um problema se você estiver visando hardware de baixo custo ou precisar de outra computação mais cara para gráficos, IA ou jogabilidade.

Outdoors para performance – talvez não neste caso?

Se você puder fugir sem animações, não precisará mais de malhas individuais. Eu queria testar usando Render Texture para produzir outdoors em vez de renderizar árvores com centenas de malhas de folhas.

Aqui está o que meu script faz:

  1. Isole as árvores em sua própria camada e certifique-se de que a câmera de renderização para textura veja apenas essa camada (máscara de remoção).
  2. Centralize a câmera na árvore.
  3. Renderize o que a câmera vê em uma textura.
  4. Aplique esta textura a um sprite.
  5. Desative a árvore e substitua-a por um único sprite.

Isso é lento embora. Cada árvore deve ser renderizada individualmente, uma por frame. Leva cerca de 10 minutos para 100 árvores. O resultado reduz drasticamente as chamadas de desenho e reduz o tempo de quadro.

Nenhum texto alternativo fornecido para esta imagem
Nenhum texto alternativo fornecido para esta imagem

Isso não funcionou como esperado para mim – as chamadas de desenho, triângulos e vértices são reduzidos, mas o tempo real de renderização do quadro não diminuiu. Então, no final, combinar malhas parecia me dar um melhor desempenho.

Bom design de ferramentas

Para este projeto, meu foco estava em ser capaz de iterar e prototipar rapidamente, e não tanto na qualidade ou usabilidade do produto final. Ainda assim, pensei um pouco no design da ferramenta:

  • Uma estrutura única para o DNA da planta – estou reiterando, mas isso contribui para um bom design de ferramenta, bem como todos os parâmetros que afetam a geração são armazenados e acessíveis juntos.
  • Cada parâmetro tem restrições fixas tanto no código quanto na interface do usuário do editor – Devido às características únicas pelas quais cada parâmetro é responsável, definir essas restrições facilita a iteração adicional e o uso da ferramenta. Isso garante que tudo o que você está gerando ainda se assemelhe às formas que você está procurando.
  • Controles deslizantes da interface do usuário do Unity para cada parâmetro, restritos da mesma maneira.
  • UI para outras funções – posso selecionar plantas clicando nelas e, em seguida, substituí-las, evoluí-las com o botão de algoritmo genético ou salvá-las e carregá-las.
Nenhum texto alternativo fornecido para esta imagem

Um próximo passo muito óbvio para um bom design de ferramentas seria usar o Unity Editor Scripting, talvez com o UI Toolkit, para tornar essa interface acessível por meio do editor Unity, em vez de pela interface do jogo.

Pensamentos finais

Para mim, este projeto foi um ponto de partida decente e rápido para entrar em mais conteúdo PCG. Se você quiser saber mais sobre o PCG, eu definitivamente recomendo dar uma olhada no blog da Kate aqui – http://www.galaxykate.com/blog/generator.html

A otimização de desempenho foi fundamental para garantir que o que estou gerando possa realmente ser usado em um jogo real. Eu posso estar olhando para a geração de folhagem 3D a seguir, então a otimização e apenas aprender sobre as maneiras mais eficientes de renderizar arte 3D no Unity serão essenciais para isso.

Depois disso, trabalhei em uma ferramenta para geração de narrativa processual. Fique de olho em um artigo sobre isso em breve!

Com informações de GameDev.net.