Post | Sanctuary
top of page
  • Join our Discord!
  • Join our Kickstarter!

anéis de alcance que guiam geral

Olá, eu sou o OzoneX e sou artista técnico e programador de renderização para o projeto Sanctuary. Tenho mais de 10 anos de experiência na indústria de jogos, fazendo a ligação entre artistas e programadores para conectar essas duas áreas. Artistas geralmente não entendem a parte técnica da arte do jogo, então é meu trabalho ajudá-los e prover o que for necessário para alcançarem visuais incríveis. Também ajudo coders a conectar modelos, animações e efeitos com as mecânicas e sistemas do jogo, garantindo que se pareçam e se comportem da maneira que os times de design e arte almejam.


Antes do Sanctuary, eu estava trabalhando num editor de mapas para o Supreme Commander. Isso me deu um bom entendimento de como fazer renderização adequada para um jogo de estratégia em tempo e real e como todos os visuais devem se comportar para terem uma boa aparência e uma ótima performance.


Basicamente, meu trabalho no projeto é garantir que tudo tenha a melhor aparência possível usando a menor quantidade possível de poder de processamento.


O problema


Em jogos de estratégia em tempo real é importante mostrar o que cada unidade é capaz de fazer, o que pode ver e o que pode detectar. O modo mais simples de mostrar essa informação é usar linhas de alcance. A maioria desses valores são, na verdade, apenas uma distância que possamos exibir em formato de anel, tendo sempre o mesmo raio, e por isso os chamamos de anéis de alcance.


O problema com uma abordagem ingênua se iniciam quando selecionamos um grande grupo de unidades no jogo com diferentes dados para cada uma, e todos os seus alcances precisam ser mostrados juntos. Se, por exemplo, pegarmos um navio que tem um canhão, um lançador de torpedos e um radar, então já temos 3 informações diferentes. Agora imagine que temos que mostrar isso tudo para 200 unidades iguais. Para mostrar todos esses dados precisaríamos desenhar 600 círculos na tela, cada um com uma posição diferente e com uma cor diferente para cada tipo de arma ou equipamento. Tudo vai começar a se afundar num mar de dados, amalgamando-se em uma bolha de cores. nessa imagem temos apenas 100 círculos vermelhos e já fica difícil de ver alguma coisa.

100 círculos vermelhos


Projetar essa grande quantidades de círculos no mapa também fica caro. Em jogos mais antigos, eram desenhados gerando uma malha na CPU. Com tantas unidades com diferentes alcances, pode tomar muito do poder de processamento, ao ter que criar e atualizar toda essa geometria, quando o hardware já tinha dificuldade ao tentar calcular todas as simulações de um jogo. Não é aceitável para um jogador que algo tão simples e pequeno como uma sobreposição 2D desacelere e atrase o jogo.



a solução

Uma boa maneira de melhorar a performance e a legibilidade desses dados é combinar alcances de um mesmo tipo em uma mesma forma. Ao invés de desenhar centenas de círculos, podemos desenhar uma área, então se tivermos os mesmos 200 navios, precisamos desenhar apenas 3 círculos, sendo eles para o canhão, torpedo e radar. Na maioria dos casos, são dados suficientes para o jogador, geralmente ele só precisa saber se uma unidade daquele grupo pode atirar ou se certa área está no alcance de uma torre de radar. Isso reduz a quantidade de geometria exibida pela placa de vídeo e deixa tudo limpo e organizado.

Formas combinadas de todos os círculos


Resolvemos a parte visual, mas como convertemos isso para código? Foi a parte mais difícil e um problema que muitos jogos RTS enfrentaram. Para achar a melhor e mais moderna solução, primeiro preciso dividir o problema em pequenas partes.

Campos de distância


Primeiramente, qual é o melhor modo de desenhar um círculo? Criar uma textura e projetá-lo em um terreno traz um monte de problemas. A resolução precisa aumentar com o tamanho e, como temos o zoom estratégico, tudo precisa parecer nítido, não importa a distância ou a proximidade da câmera. Uma das melhores soluções para isso é usando um campo de distância. Um campo de distância é uma textura onde cada pixel armazena informações do quão próximo está de um objeto 2D. Se esse objeto for um círculo, então essa será a distância até o ponto mais próximo em seu perímetro.


Podemos descrever um círculo no shader dessa forma:


Primeiro teste de um campo de distância com UV e distância do círculo mais próximo


Isso nos dará um valor negativo quando o pixel está dentro de um círculo e positivo quando está fora. Agora, quando misturar os pixels dessa textura de campo de distância, terá uma transição suave entre suas coordenadas em um mundo, para que não fique limitado aos dados para a resolução dessa textura, pois pode interpolar todos os outros dados para qualquer outra resolução. Essa técnica é geralmente usada em casos muito mais complexos como fazer uma fonte que é nítida a qualquer distância da câmera ou fazer um campo de distância 3D para conseguir uma sombra simples de um objeto.


Também incluímos ângulos e raios em tais dados, que nos permite posteriormente reconstruir o UV (coordenadas 2D) de um anel para que possa se projetar textura nele.

Resultado de tais campos de distância com reconstrução

renderização de Pipeline


Agora que temos um círculo que ficará nítido independente da distância, precisamos, de alguma forma, desenhar centenas deles. O modo mais fácil é fazer um loop que desenha o campo de distância de cada círculo e mostra apenas o mais próximo. Mas isso significa que teríamos que calcular cada círculo para cada pixel de uma textura, mesmo que esteja muito longe ou oculto por outros círculos. com 600 círculos em uma pequena textura de 512x512, isso significa que precisaríamos fazer esse cálculo mais de 66 milhões de vezes! Para cada frame do jogo! Não podemos fazer previamente esses dados como com fontes, porque tudo se move e muda o tempo todo.


Se houvesse uma maneira de selecionar objetos e desenhar apenas o valor que está em cima... Espere, aqui está! É exatamente desse modo que o pipeline de renderização 3D funciona. Primeiro, verifica-se o que está dentro da área de visualização, e usando um buffer de profundidade, verifica quais objetos estão por cima. Só ai que desenha-se apenas aquela superfície do objeto por cima. Esse é um processo bem otimizado que está desenhando na tela quase tudo do ambiente 3D do jogo. É necessário ser rápido para ser capaz de desenhar mundos enormes com muitos projetos.


Usar o DrawInstancing é ótimo com essa grande quantidade de objetos iguais. Instancing é uma técnica usada pela GPU para desenhar o mesmo objeto que contenham pequenas mudanças como posição ou cor. A Unity tem um ótimo suporte para esse recurso, então usando ComputeBuffers e DrawMeshInstanced somos capazes de facilmente desenhar milhares de objetos. Também usamos essa técnica para desenhar unidades, ícones estratégicos e qualquer coisa que é usada múltiplas vezes no jogo.


Agora temos um modo de descrever um círculo e desenhar milhares deles. O último problema a ser resolvido é como combinar esses círculos. Como dizer à GPU qual círculo é o mais próximo. Idealmente, não queremos desenvolver nossa própria plataforma de combinação. Vai ser caro, o mesmo que misturar uma transparência, porque cada pixel de um círculo precisa comparar a si com outros anéis na mesma posição. Não possuímos isso, porque apenas um círculo pode ser mostrado em cada pixel quando todos os outros são selecionados por visualização ou buffer de profundidade. O modo de fazê-lo é usarmos o que temos. A informação que é guardada para cada objeto que detalha o quão longe a superfície está da câmera é um tipo de buffer de profundidade. Precisamos garantir que o círculo mais próximo na superfície também é o mais próximo da câmera. Tal efeito pode ser alcançado representando anéis como cones, onde a base do cone é o raio do anel e a altura também é o mesmo valor do raio. Quanto maior o anel, mais perto do centro estará a câmera.

Campo de distância final

cones 3D dos anéis de alcance

Um desafio difícil foi descobrir uma maneira de desenhar múltiplos objetos de uma câmera em texturas de campo de distância separados para ter dados para cada tipo de círculo. Eu consegui fazer funcionar usando o recurso CustomPass da Unity, do HDRP renderer. Ele permite coletar objetos de listas de renderização selecionadas e desenhá-los em uma textura de render usando a mesma câmera com o seu buffer de profundidade no mesmo frame. Cada anel apenas precisa ser renderizado com uma lista diferente, que eu consigo fazendo com que um material diferente seja usado em cada círculo. Não é uma grande perda, não podemos agrupar tipos diferentes de qualquer maneira, já que são renderizados separadamente.


Essa técnica nos dá uma ótima e muito eficiente maneira de desenhar milhares de anéis com quase nenhum custo. Chegamos até mesmo a testar 22 mil círculos espalhados no maior tamanho de mapa suportado, que é muito mais do que o jogo precisa aguentar e mesmo assim não tivemos uma queda notável na performance. Também usamos isso para desenhar a neblina de guerra e, posteriormente, implementamos a projeção de buracos no terreno.

22000 anéis de alcance + a neblina de guerra


12 views0 comments
bottom of page