Dicas de gráficos e renderização de Survival Kids

STEVEN CANNAVAN AND DANIEL REIDLER / UNITYSurvival Kids
Aug 20, 2025|8 Min
Jogabilidade em Survival Kids
Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Neste verão, a Unity lançou o primeiro jogo desenvolvido do início ao fim internamente, uma atualização sobre o jogo familiar cooperativo Survival Kids, em parceria com a KONAMI. O jogo foi construído por uma pequena equipe interna de cerca de 20 pessoas no máximo, então a equipe teve que encontrar maneiras inovadoras de permanecer dentro do escopo do projeto e do cronograma de lançamento com recursos limitados, assim como qualquer estúdio indie. Neste post, exploramos como criamos a estrutura visual e a renderização do jogo.

Definindo a identidade visual

Queríamos alcançar algo visualmente interessante. Nossos objetivos eram muito artísticos, mas também queríamos torná-lo muito barato em termos de desempenho, já que não sabíamos com que tipo de capacidades de dispositivo estaríamos trabalhando no início.

A primeira parte do projeto foi apenas explorar visualmente – tínhamos um diorama de arte que estávamos usando para mostrar como imaginávamos a arte. Parte disso é uma configuração de iluminação muito estilizada, incluindo sombras personalizadas.

Optamos pelo Universal Render Pipeline (URP) já que ele tem um ótimo histórico de desempenho em uma ampla gama de dispositivos, e é relativamente fácil criar quaisquer novos recursos que precisemos para atingir as metas visuais do jogo. O quadro renderizado está muito próximo do URP padrão no modo Forward, já que o jogo tem principalmente apenas uma fonte de luz, o sol. Temos algumas modificações aqui e ali, como as sombras personalizadas, oclusão ambiental e alguns outros recursos de renderização personalizados, mas no geral é o URP padrão na tela.

Jogabilidade em Survival Kids
Jogabilidade em Survival Kids

A maior adição foi aos shaders para suportar o visual muito específico da direção de arte, já que precisávamos fazer modificações em como a iluminação era calculada. Criar shaders personalizados não é particularmente novo, no entanto, escrevemos nossos próprios alvos personalizados do Shader Graph para garantir que qualquer um pudesse contribuir. Usar AssemblyDefinitionReferences nos permitiu adicionar alvos de Shader Graph específicos do projeto sem precisar ter uma versão URP completamente personalizada. Isso nos permitiu manter o URP padrão com apenas nossos alvos locais do Shader Graph, o que funcionou muito bem para nosso projeto.

Iluminação e iluminação global

Um de nossos objetivos era ter iluminação dinâmica – queríamos a opção de poder mudar a cor da iluminação, intensidade, etc. Isso significava que não poderíamos facilmente assar informações de iluminação usando lightmaps, então estaríamos perdendo alguns dos detalhes de iluminação que você obteria ao assar a iluminação de reflexão / iluminação global. Precisávamos pensar em diferentes maneiras de equilibrar alta qualidade visual e bom desempenho com uma abordagem de iluminação dinâmica, já que normalmente é mais cara. Isso nos levou a usar LightProbes inicialmente e também a confiar mais fortemente na Oclusão Ambiental (AO) para ajudar a ancorar objetos.

Informações de iluminação e iluminação global em um ambiente de Survival Kids
Informações de iluminação e iluminação global

Porque sabíamos que a iluminação global seria muito importante para este projeto, inicialmente implementamos uma solução personalizada que atualizaria os LightProbes em tempo de execução. Mas então, quando mudamos para o Unity 6, a equipe realmente queria mudar para Volumes de Probes Adaptativos (APVs) porque a qualidade visual era consideravelmente melhor do que o sistema que havíamos montado, mantendo um impacto de desempenho comparável. Quando você tem a opção de atualizar de algo bom para algo realmente bom que é de alta qualidade e eficiente, você simplesmente muda.

Oceano

O oceano foi fortemente baseado em um projeto de demonstração do Unity URP Boat Attack, mas com uma aparência mais estilizada. Uma das coisas que realmente queríamos fazer era ter ondas saindo da ilha e outros elementos na água. Isso geralmente é implementado usando o buffer de profundidade para determinar a costa pela distância – mas realmente não temos uma costa, temos uma ilha Whurtle.

As ondas ocorrem na água do oceano a uma distância definida da costa de um pequeno ambiente insular.
O Campo de Distância Assinada (SDF) cria ondas a uma distância definida da costa.

Com a ilha Whurtle, você tem uma queda abrupta, e não há queda de profundidade suficiente para o efeito, especialmente levando em conta o terreno submerso sob a água. A melhor ideia que tivemos foi usar um campo de distância assinada, ou SDF – é basicamente uma textura que codifica a distância assinada de um objeto, ou, no nosso caso, a costa. Dessa forma, podemos iniciar a onda a uma certa distância da costa, e então usar ondas senoidais e algumas texturas de distorção para dar uma aparência interessante.

No final, tivemos uma ferramenta de Editor que assa a distância assinada para a costa com base em quatro alturas de água definidas. Então fizemos algumas misturas e interpolação entre elas para uma aproximação grosseira de onde a costa realmente estava, já que o nível da água na maioria dos níveis muda dependendo do progresso do jogador. Confiamos nessas informações SDF pré-assadas para vários efeitos diferentes, desde ajustar a altura das ondas do oceano até adicionar espuma, ondas e causticas.


Análise de um quadro
Análise de alto nível de um quadro renderizado
Quebra de alto nível de um quadro renderizado, onde as passagens marcadas em vermelho são personalizadas
Interação visual

Para interações visuais, uma cápsula é renderizada de uma visão de cima para baixo em torno de qualquer coisa que precisássemos rastrear a posição, como jogadores, objetos transportáveis, ferramentas, etc., em um RenderTexture. A textura é baseada no espaço do mundo com uma janela deslizante à medida que a câmera do jogador se move.

Geramos um deslocamento (vermelho, azul) a partir do centro da cápsula, bem como informações de altura no espaço do mundo (verde). No canal alfa, armazenamos um valor de queda para a força. Isso é então usado por diferentes shaders para criar efeitos como a curvatura da vegetação, ondulações animadas em superfícies de água ou escurecimento do terreno um pouco para criar um efeito de sombra muito suave.


Shaders são aplicados a uma "cápsula" RenderTexture em torno de jogadores e objetos para criar efeitos que os fazem parecer mais ancorados no mundo, como sombras ou rastros na água.
Shaders são aplicados a uma "cápsula" RenderTexture em torno de cada jogador e objeto transportável para criar efeitos que os fazem parecer mais ancorados no mundo, como sombras ou rastros na água.
Pré-passagem de profundidade e dither

Para uma otimização de desempenho, usamos uma pré-passagem de profundidade, que preenche o buffer de profundidade antes de renderizarmos objetos normalmente, reduzindo o custo de renderização desses objetos devido à rejeição precoce do teste de profundidade.

Um stencil usado para desfoque na Passagem SMAA e pré-preenchimento de profundidade com padrão de dither para ver atrás da geometria
Um stencil usado para desfoque na Passagem SMAA e pré-preenchimento de profundidade com padrão de dither para ver atrás da geometria

Tratamos objetos dithered separadamente em uma passagem personalizada porque precisamos renderizá-los de maneira diferente dependendo de seu estado e de qual jogador os está visualizando. Eles estão em uma camada de GameObject diferente que é excluída da Máscara de Camada Opaca no renderizador, então não são renderizados automaticamente, e isso significa que precisamos renderizá-los em uma passagem personalizada. Usamos MaterialPropertyBlocks para definir valores individuais para objetos e aplicamos stencils para marcar os objetos que estão dithered para que possamos desfocar essas seções mais tarde. No entanto, como isso quebra o agrupamento SRP, precisávamos limitar seu uso. Decidimos aplicar MaterialPropertyBlocks apenas conforme necessário e removê-los quando terminássemos, restaurando os objetos a um estado agrupável.

No final, temos uma passagem inteira que lida apenas com como renderizamos aquela camada particular no buffer de profundidade. Em seguida, aplicamos um stencil no buffer de profundidade para marcar quais pixels fazem parte dos objetos que estamos desvanecendo, e isso é usado mais tarde quando estamos fazendo anti-aliasing.

Sombras em gradiente

Parte do nosso estilo artístico era ter sombras coloridas com um gradiente ao longo da direção da sombra. Para conseguir isso, geramos uma textura personalizada em espaço de tela a partir de um RenderFeature que amostrava o mapa de sombras no espaço do mundo, mas também olhava à frente no plano XZ para determinar um valor de mistura de sombra. Isso é semelhante a um filtro PCF usado em sombras suaves, mas em uma direção. Isso foi renderizado em uma textura reduzida, cerca de um quarto do tamanho da tela, e então misturamos a cor da sombra entre três cores.

Uma passagem de espaço de tela de baixa resolução nas sombras gera as cores do gradiente de borda e cria um gradiente na direção da luz. Em seguida, depende da amostragem bilinear mais tarde.
Uma passagem de espaço de tela de baixa resolução nas sombras gera as cores do gradiente de borda e cria um gradiente na direção da luz. Em seguida, depende da amostragem bilinear mais tarde.
MSVAO (oclusão ambiental volumétrica em múltiplas escalas)

Infelizmente para nós, o SSAO fornecido com o URP não era bem adequado às nossas necessidades. Embora seja uma implementação amigável para dispositivos móveis, para o visual que estávamos buscando, precisávamos definir o valor do raio bastante alto, o que consumiu uma parte significativa do nosso orçamento de quadro (~4ms). Em vez disso, reutilizamos a implementação do MSVAO do antigo pacote PostProcessing Stack v2, com algumas pequenas mudanças para torná-lo mais eficiente e integrar nossa cor de sombra.

Perspectiva de uma cena de ilha que mostra uma versão personalizada do MSVAO do pacote PostProcessing Stack v2, com ajustes menores para torná-lo mais eficiente em uma plataforma alvo.
Uma versão personalizada do MSVAO do pacote PostProcessing Stack v2, com ajustes menores para torná-lo mais eficiente em uma plataforma alvo.
Desenhando a cena

Survival Kids tem as passagens de renderização padrão que você espera no URP (Opaco, Skybox, Transparência), mas também temos uma passagem adicional para lidar com nossos objetos dithered, logo após a passagem opaca. É aqui que realmente renderizaremos nossa geometria dithered devido ao fato de que a geometria nesta camada não é renderizada na passagem opaca. Também fazemos um teste de igualdade de profundidade nesta passagem para garantir que renderizamos apenas onde pré-preenchemos o buffer de profundidade.

Desenho da geometria (teste de igualdade de profundidade) para nossos objetos dithered. A oclusão ambiental está desativada neste estado.
Desenho da geometria (teste de igualdade de profundidade) para nossos objetos dithered. A oclusão ambiental está desativada neste estado.

Para objetos que são dithered, precisamos desativar a oclusão ambiental neles devido aos artefatos que ocorrerão devido ao MSVAO tratar os "buracos" no buffer de profundidade como oclusão.

Imagem do jogo mostrando os efeitos visuais de usar SMAA + desfoque em dither.
SMAA + desfoque em dithering

Após a cena ser renderizada, aplicamos nosso anti-aliasing. Infelizmente, as áreas que são dithering causarão problemas para o algoritmo (SMAA), causando artefatos visuais. Para evitar isso, precisamos lidar com essas áreas separadamente. Áreas que são dithering (determinadas pelo stencil) são desfocadas, produzindo um efeito de mesclagem alfa nessas áreas, e então o SMAA é processado nas áreas que não são dithering. Isso é pulado em certas circunstâncias, mas acabamos com uma imagem final limpa pronta para o pós-processamento.

Pós-processamento e UI

Mantivemos nossos efeitos de pós-processamento o mais baratos possível, usando apenas um pouco de Tonemapping, Bloom e Correção de Cor.

Em um ponto, usamos o Desfoque do URP no pós-processamento para suavizar o jogo atrás da UI, mas substituímos isso por um RenderFeature de desfoque Kawase mais barato depois. Nosso sistema de UI é construído em UGUI com um pouco de renderização personalizada para o desvanecimento.

UI em Survival Kids
UI em Survival Kids

A maneira como inicialmente configuramos nossa UI, estávamos desvanecendo menus para dentro e para fora, mas essa abordagem causou alguns problemas devido à forma como o alfa é feito para a UI. A princípio, começamos a renderizar a UI em uma textura separada via uma câmera, depois blitamos isso corretamente para que pudéssemos desvanecer a UI na imagem principal, mudamos isso para que pudesse ser alcançado usando um RenderFeature em vez de usar uma câmera extra inteira.

Este post oferece apenas uma visão de como configuramos gráficos e renderização estilizados e performáticos para atingir nossa taxa alvo para Survival Kids. Fique atento ao blog da Unity para mais posts sobre os bastidores do jogo, incluindo uma análise em duas partes sobre rede multiplayer e uma visão sobre o terreno e fluxos de trabalho da equipe, ou confira mais histórias técnicas de desenvolvedores em nossa Página de Recursos.