Sob o capô do Graveyard Keeper: como os efeitos gráficos são implementados

Olá pessoal! Durante os quatro anos inteiros eu não escrevi em Habr. Minha última série de posts foi sobre várias ferramentas e truques que usamos no nosso último jogo (desenvolvendo-o no Unity). Desde então, lançamos o jogo com sucesso e também lançamos um novo. Então agora você pode respirar um pouco e escrever alguns novos artigos que podem ser úteis para alguém.


Hoje eu quero falar sobre os truques gráficos que usamos para criar a imagem que você vê no GIF acima.

Somos muito sensíveis ao visual de nossos jogos e, portanto, investimos bastante tempo e esforço em vários efeitos e outros pães que tornariam nossa arte em pixel o mais atraente possível. Talvez alguém encontre algo útil para si.

Para começar, listarei brevemente como será a imagem em nosso jogo:

  1. Luz ambiente variável - uma mudança banal na iluminação, dependendo da hora do dia.
  2. Correção de cores LUT - é responsável por alterar o tom da imagem, dependendo da hora do dia (ou do tipo de zona).
  3. Fontes de luz dinâmicas - tochas, fogões, lâmpadas.
  4. Mapas normais - responsáveis ​​por dar volume aos objetos, principalmente ao mover fontes de luz.
  5. Matemática da distribuição de luz 3D - é responsável por garantir que a fonte de luz no centro da tela ilumine corretamente um objeto mais alto, mas não ilumine um objeto que seja mais baixo (ou seja, voltado para a câmera com o lado apagado).
  6. Sombras - feitas por sprites, giram e respondem à posição das fontes de luz.
  7. Simulação da altura dos objetos - para a exibição correta do nevoeiro.
  8. Outros decoradores: chuva, vento, animações (incluindo animação sombreada de folhagem e grama), etc.

Agora com mais detalhes.

Luz ambiente variável


Aqui, em princípio, nada de especial. À noite - mais escuro, durante o dia - mais leve. A cor da luz é definida pelo gradiente de tempo. À noite, a fonte de luz não fica apenas mais escura, mas adquire uma tonalidade azul.

É assim:


Correção de cores LUT


LUT (tabela de consulta) - tabelas de troca de cores. Grosso modo, trata-se de uma matriz RGB tridimensional em que em cada nó existe um valor de cor, que deve ser substituído pelo correspondente. Ou seja, se um ponto vermelho estiver localizado nas coordenadas (1, 1, 1), isso significa que toda a cor branca na imagem será substituída por vermelho. Se as coordenadas (1, 1, 1) forem brancas (R = 1, G = 1, B = 1), não haverá alterações. Por conseguinte, a LUT sem alterações possui uma cor para cada coordenada correspondente a essas mesmas coordenadas. I.e. no ponto (0,4, 0,5, 0,8) é a cor (R = 0,4, G = 0,5, B = 0,8).

Bem, vale ressaltar que, por conveniência, eles apresentam uma textura 3D como bidimensional. Por exemplo, é assim que a LUT "padrão" se parece (sem alterar a reprodução de cores):



É implementado elementarmente, funciona de forma rápida e conveniente.

Também é muito fácil de configurar - você dá ao artista qualquer imagem do jogo e diz "tom de cor para parecer a noite". Depois disso, aplique todas as camadas de correção de cores à LUT padrão e obtenha a LUT à noite.

No nosso caso, o artista ficou um pouco preso e criou até 10 LUTs diferentes para diferentes momentos do dia (noite, crepúsculo, noite, etc.). É assim que a configuração deles é:


Como resultado, dependendo da hora do dia, o mesmo local parece diferente:



Aqui, a transparência dos sprites de luz das janelas também muda dependendo da hora do dia.

Fontes de luz dinâmicas e mapas normais


As fontes de luz são usadas absolutamente comuns, da Unity. Além disso, mapas normais são desenhados para cada sprite, o que permite obter uma sensação de volume.


Tais normais são desenhados de maneira bastante simples. O artista pinta aproximadamente uma luz com um pincel de 4 lados:


E então este script está indo para o mapa normal:


Se você estiver procurando por um sombreador (e software) que faça isso, poderá olhar na direção da Lâmpada Sprite.

Simulação de luz 3D


Isso é um pouco mais complicado. Você não pode simplesmente pegar e destacar sprites. Precisamos considerar se o sprite está "atrás" da fonte de luz ou "na frente".

Preste atenção a esta imagem:


Ambas as árvores estão à mesma distância da fonte de luz; no entanto, a árvore distante fica iluminada e a árvore mais próxima não está (porque sua parte apagada está voltada para a câmera).

Resolvi esse problema de maneira simples. O sombreador calcula a distância ao longo do eixo vertical y entre a fonte de luz e o sprite. E se for positivo (a fonte de luz antes do sprite), iluminaremos o sprite como de costume, mas se for negativo (o sprite bloqueia a fonte de luz), mas a intensidade da luz decai muito à distância com um coeficiente muito grande. É precisamente o coeficiente que foi criado, e não apenas “não está iluminando”, de modo que quando a fonte de luz se move e aparece repentinamente atrás do sprite, o sprite não fica instantaneamente preto, mas gradualmente. Mas ainda bem rápido.


Sombras


Sombras são feitas por sprites girando em torno de um ponto. Tentei adicionar mais compressão (inclinação) a eles, mas acabou sendo desnecessário.

No total, cada objeto pode ter no máximo 4 sombras. Um é do Sol e três são de fontes de luz dinâmicas. A imagem abaixo mostra o princípio:



A tarefa “encontrar as próximas 3 fontes de luz e calcular a distância / ângulo das sombras para elas” é resolvida por um script que gira em Update. Sim, não funciona muito rápido, porque você tem que fazer muita matemática. Se eu escrevesse agora, usaria os novos sistemas de trabalhos paralelos no Unity. Mas isso ainda não era, então eu apenas otimizei os scripts comuns o máximo possível.

A única coisa que importa é que fiz a rotação do sprite não se transformar, mas dentro do shader de vértice. I.e. rotação não se move. Só que o parâmetro está definido no sprite (usei a cor para isso, porque todas as sombras são todas pretas) e o shader já é responsável pela rotação do sprite. Isso é mais rápido porque não precisa puxar a geometria no Unity.

Outro ponto negativo dessa abordagem é que as sombras precisam ser configuradas (e às vezes pintadas) individualmente para cada objeto. É verdade que conseguimos, provavelmente, uma dúzia de diferentes sprites mais ou menos universais (finos, grossos, ovais etc.).

A segunda desvantagem é que às vezes é difícil fazer sombra para um objeto cujo ponto de contato com a Terra é muito alongado. Por exemplo, observe a sombra da cerca:


Não é perfeito. É assim que parece se você tornar o sprite da cerca translúcido:


Aqui, no entanto, vale a pena notar que o sprite ainda está muito deformado verticalmente (o sprite de sombra original se parece quase com um círculo). É por isso que o turno dele parece não apenas um turno, mas uma distorção.

Simulações de nevoeiro e altura


Há neblina no jogo. Parece com isso (acima é a versão normal, abaixo é uma névoa extrema de 100% para demonstrar o efeito).



Como você pode ver, o topo das casas e as árvores se destacam da neblina. De fato, alcançar esse efeito foi bastante simples. O nevoeiro consiste em muitas nuvens horizontais que são distribuídas por toda a profundidade do palco. E, como resultado, acontece que a parte superior de todos os sprites é bloqueada por menos sprites de neblina:



O vento


O vento da pixel art é outra história. Não há muitas opções. Ou anime com as mãos (o que é quase impossível com a nossa quantidade de obras de arte) ou escreve um shader deformador, mas às vezes é preciso suportar distorções feias. É claro que você não pode animar, mas a imagem parece inanimada.

Escolhemos a opção de distorção usando o sombreador. É assim:


Se você aplicar esse shader a uma textura quadriculada, ficará claro o que acontece:


Também é importante notar que não estamos animando toda a coroa, mas apenas as folhas individuais:


Também sacudimos o trigo ao vento, mas tudo é simples - o sombreador de vértices deforma as coordenadas x, levando em consideração o componente y. Quanto maior o ponto, mais forte o cambalhota. Isso é feito para que apenas os principais cambaleiam, mas a raiz não. Além disso - a fase de oscilação muda de coordenadas x / y para que diferentes sprites na tela oscilem aleatoriamente.


O mesmo sombreador também é usado para criar o efeito de balançar trigo e grama quando um jogador passa por eles.


Provavelmente é tudo por enquanto. Eu intencionalmente não lidei com a questão de construir a cena e sua geometria, porque isso é material para um artigo separado. De resto, ele falou sobre as principais soluções usadas no desenvolvimento.

PS: Se alguém estiver interessado em alguns aspectos técnicos, escreva nos comentários. Talvez eu conte em outro artigo. A menos que, é claro, seja necessário.

PPS: Aproveito a oportunidade para dizer que agora queremos encontrar várias pessoas competentes na equipe (programador, PM, KM, artista). Os detalhes estão no site do estúdio. Espero que esta frase não tenha violado as regras.

Source: https://habr.com/ru/post/pt425989/


All Articles