Se você joga jogos AAA modernos, pode ter notado uma tendência a usar paisagens cobertas de neve. Por exemplo, eles estão em
Horizon Zero Dawn ,
Rise of the Tomb Raider e
God of War . Em todos esses jogos, a neve tem uma característica importante: você pode deixar vestígios nela!
Graças a essa interação com o ambiente, a imersão do jogador no jogo é aprimorada. Torna o ambiente mais realista e vamos ser honestos - é apenas interessante. Por que passar longas horas criando um mecânico curioso se você pode simplesmente deixar o jogador cair no chão e fazer anjos da neve?
Neste tutorial, você aprenderá o seguinte:
- Crie pegadas usando a captura de cena para mascarar objetos próximos ao chão
- Use uma máscara com material do terreno para criar neve deformável
- Para otimizar, exiba pegadas na neve apenas ao lado do jogador
Nota: entende-se que você já está familiarizado com o básico do trabalho com o Unreal Engine. Se você é novo, confira nossa série de tutoriais do Unreal Engine para iniciantes .
Começando a trabalhar
Faça o download de
materiais para este tutorial. Descompacte-os, vá para
SnowDeformationStarter e abra
SnowDeformation.uproject . Neste tutorial, criaremos traços com a ajuda de um personagem e várias caixas.
Antes de começarmos, você precisa saber que o método deste tutorial salvará apenas rastreios em uma determinada área, e não em todo o mundo, porque a velocidade depende da resolução da renderização do destino.
Por exemplo, se queremos armazenar rastreamentos para uma grande área, teremos que aumentar a resolução. Mas também aumenta o impacto da captura de cena na velocidade do jogo e no tamanho da memória para a renderização de destino. Para otimização, você precisa limitar o escopo e a resolução.
Tendo resolvido isso, vamos descobrir o que é preciso para perceber as pegadas na neve.
Implementação de pegadas na neve
A primeira coisa que você precisa para criar rastreamentos é a
renderização de destino . O destino de renderização será uma máscara em escala de cinza, na qual branco indica a presença de um traço e preto indica sua ausência. Em seguida, podemos projetar a renderização alvo no chão e usá-la para misturar as texturas e mudar os vértices.
A segunda coisa que precisamos é uma maneira de mascarar apenas objetos que afetam a neve. Isso pode ser implementado renderizando primeiro os objetos em
Profundidade personalizada . Você pode usar a
captura de cena com o
material pós-processo para mascarar todos os objetos renderizados em Profundidade personalizada. Então você pode exibir a máscara na renderização de destino.
Nota: a captura de cena é essencialmente uma câmera com a capacidade de gerar uma renderização de destino.
A parte mais importante da captura de uma cena é a sua localização. A seguir, é apresentado um exemplo de renderização de destino capturada de uma
vista superior . Aqui, um personagem de terceira pessoa e caixas são mascaradas.
À primeira vista, uma captura com uma vista superior nos convém. Os formulários parecem apropriados para malhas, então não deve haver problemas, certo?
Na verdade não. O problema da captura de uma vista superior é que ela não captura nada no ponto mais amplo. Aqui está um exemplo:
Imagine que as setas amarelas vão até o chão. No caso de um cubo e um cone, a ponta da seta sempre permanecerá dentro do objeto. No entanto, no caso de uma esfera, o ponto emerge dela quando se aproxima a Terra. Mas, de acordo com a câmera, a ponta está sempre dentro da esfera. Veja como será a esfera da câmera:
Portanto, a máscara de esfera será maior do que deveria, mesmo que a área de contato com a terra seja pequena.
Além disso, esse problema é complementado pelo fato de que é difícil determinar se o objeto diz respeito à terra.
Para lidar com esses dois problemas, você pode usar a captura
da parte inferior .
Aperto inferior
A captura abaixo é a seguinte:
Como você pode ver, a câmera agora captura o lado inferior, ou seja, aquele que toca o chão. Isso elimina o problema da "área mais larga" que aparece ao capturar de cima.
Para determinar se um objeto está tocando o chão, você pode usar o material de pós-processamento para executar uma verificação de profundidade. Ele verifica se a profundidade do objeto é maior que a profundidade da Terra
e se está abaixo de um deslocamento predeterminado. Se as duas condições forem atendidas, podemos mascarar esse pixel.
Abaixo está um exemplo dentro de um motor com uma zona de captura de 20 unidades acima do solo. Observe que a máscara aparece apenas quando o objeto passa por um determinado ponto. Observe também que a máscara se torna mais branca à medida que o objeto se aproxima do solo.
Primeiro, crie um material de pós-processamento para executar uma verificação de profundidade.
Criando material de teste de profundidade
Para realizar uma verificação de profundidade, é necessário usar dois amortecedores de profundidade - um para o solo e outro para objetos que afetam a neve. Como a captura da cena vê apenas a Terra, a
Profundidade da Cena inferirá profundidade para a Terra. Para obter profundidade para os objetos, apenas renderizamos a
profundidade personalizada .
Nota: para economizar tempo, já renderizei o caractere e as caixas em Profundidade personalizada. Se você deseja adicionar outros objetos que afetam a neve, ative o Render CustomDepth Pass para eles.
Primeiro, você precisa calcular a distância de cada pixel ao chão. Abra
Materials \ PP_DepthCheck e crie o seguinte:
Em seguida, você precisa criar uma zona de captura. Para fazer isso, adicione os nós destacados:
Agora, se o pixel estiver dentro de
25 unidades da Terra, ele aparecerá na máscara. O brilho da máscara depende de quão próximo o pixel está do chão. Clique em
Aplicar e retorne ao editor principal.
Em seguida, você precisa criar uma captura de cena.
Criar captura de cena
Primeiro, precisamos de uma renderização de destino para capturar uma captura de cena. Vá para a pasta
RenderTargets e crie um novo
destino de renderização chamado
RT_Capture .
Agora vamos criar uma captura de cena. Neste tutorial, adicionaremos a captura de cena ao blueprint, pois mais tarde precisaremos de um script para ele. Abra
Blueprints \ BP_Capture e adicione o
Scene Capture Component 2D . Nomeie-o
SceneCapture .
Primeiro, precisamos definir a curva de captura para que ela olhe para o chão. Vá para o painel Detalhes e defina a
rotação para (0, 90, 90) .
Em seguida, vem o tipo de projeção. Como a máscara é uma representação 2D da cena, precisamos nos livrar da distorção da perspectiva. Para fazer isso, defina
Projeção \ Tipo de projeção como
Ortográfico .
Em seguida, precisamos informar a captura de cena em que destino renderizar para gravar. Para fazer isso, selecione o valor de
RT_Capture para
Scene Capture \ Texture Target .
Finalmente, precisamos usar o material de verificação de profundidade. Adicione PP_DepthCheck ao
Rendering Features \ Post Process Materials . Para que o pós-processamento funcione, também precisamos alterar a
cena Capture \ Capture Source para
Final Color (LDR) em RGB .
Agora que a captura de cena está configurada, precisamos especificar o tamanho da área de captura.
Definindo o tamanho da área de captura
Como é melhor usar baixas resoluções para a renderização de destino, precisamos usar o espaço com eficiência. Ou seja, devemos escolher qual área um pixel cobrirá. Por exemplo, se as resoluções da área de captura e a renderização do destino forem as mesmas, obteremos uma proporção de 1: 1. Cada pixel cobrirá uma área 1 × 1 (em unidades do mundo).
Para trilhas na neve, uma proporção de 1: 1 não é necessária, porque provavelmente não precisaremos desses detalhes. Eu recomendo o uso de proporções maiores, pois isso permitirá aumentar o tamanho da área de captura em baixa resolução. Mas não aumente a proporção, caso contrário os detalhes começarão a se perder. Neste tutorial, usaremos uma proporção de 8: 1, ou seja, o tamanho de cada pixel será 8 × 8 unidades do mundo.
Você pode redimensionar a área de captura alterando a propriedade
Scene Capture \ Ortho Width . Por exemplo, se você deseja capturar uma área de 1024 × 1024, defina o valor como 1024. Como estamos usando uma proporção 8: 1, defina o valor como
2048 (a resolução padrão da renderização de destino é 256 × 256).
Isso significa que a captura de cena capturará a área de
2048 × 2048 . É aproximadamente 20 × 20 metros.
O material do solo também precisa de acesso para capturar o tamanho para projetar adequadamente a renderização de destino. A maneira mais fácil de fazer isso é salvando o tamanho da captura na
coleção de parâmetros de material . Esta é essencialmente uma coleção de variáveis que
qualquer material pode acessar.
Salvando o tamanho da captura
Volte ao editor principal e vá para a pasta
Materiais . Crie uma
coleção de parâmetros de materiais que estará em
Materiais e texturas . Renomeie-o para
MPC_Capture e abra.
Em seguida, crie um novo
parâmetro escalar e
chame -o de
CaptureSize . Não se preocupe em definir seu valor - faremos isso sem rodeios.
Volte para
BP_Capture e adicione os nós destacados ao
Event BeginPlay . Defina
Coleção como
MPC_Capture e
Nome do parâmetro como
CaptureSize .
Agora qualquer material pode obter o valor da
Largura Orto , lendo-o
no parâmetro
CaptureSize . Até agora, com a captura da cena, terminamos. Clique em
Compilar e retorne ao editor principal. O próximo passo é projetar a renderização do alvo no chão e usá-lo para deformar a paisagem.
Deformação da paisagem
Abra
M_Landscape e vá para o painel Detalhes. Em seguida, defina as seguintes propriedades:
- Para Frente e verso, selecione ativado . Como a captura da cena “olhará” de baixo, ela verá apenas as faces inversas da terra. Por padrão, o mecanismo não renderiza as faces traseiras das malhas. Isso significa que ele não armazenará a profundidade da terra no buffer de profundidade. Para corrigir isso, precisamos dizer ao mecanismo para renderizar os dois lados da malha.
- Para o mosaico D3D11, selecione Mosaico plano (também podem ser usados triângulos PN). O mosaico dividirá os triângulos da malha em outros menores. Em essência, isso aumenta a resolução da malha e nos permite obter detalhes mais refinados ao mudar os vértices. Sem isso, a densidade dos picos será muito baixa para criar traços críveis.
Depois que os mosaicos são ativados, o
Deslocador de mundo e o
Multiplicador de mosaico são ativados.
O Multiplicador de mosaico controla a quantidade de mosaico. Neste tutorial, não vamos conectar este nó, ou seja, usamos o valor padrão (
1 ).
Deslocamento mundial obtém um valor vetorial que descreve em qual direção e quanto mover o vértice. Para calcular o valor desse contato, precisamos primeiro projetar a renderização do destino no chão.
Renderização do destino do projeto
Para projetar a renderização de destino, você precisa calcular suas coordenadas UV. Para fazer isso, crie o seguinte esquema:
O que está acontecendo aqui:
- Primeiro, precisamos obter a posição XY do vértice atual. Como estamos capturando de baixo, a coordenada X é invertida; portanto, você precisa inverter (se quisermos capturar de cima, não precisaríamos disso).
- Esta parte executa duas tarefas. Em primeiro lugar, centraliza a renderização do alvo de forma que seu meio esteja nas coordenadas (0, 0) do espaço do mundo. Ela então converte as coordenadas do espaço mundial para o espaço UV.
Em seguida, crie os nós selecionados e combine os cálculos anteriores, como mostrado abaixo. Para
Amostra de textura, selecione
RT_Capture .
Isso projetará a renderização de destino no chão. No entanto, todos os vértices fora da área de captura irão amostrar as bordas da renderização de destino. Na verdade, isso é um problema porque a renderização de destino deve ser usada apenas para vértices na área de captura. Aqui está o que parece em um jogo:
Para corrigir isso, precisamos mascarar todos os UVs que estão fora do intervalo de 0 a 1 (ou seja, a área de captura). Para isso, criei a função
MF_MaskUV0-1 . Retorna
0 se o UV transmitido estiver fora da faixa de 0 a 1 e retornará
1 se estiver dentro dele. Multiplicando o resultado pela renderização alvo, realizamos mascaramento.
Agora que projetamos a renderização alvo, podemos usá-la para misturar cores e mover vértices.
Usando renderização de destino
Vamos começar misturando cores. Para fazer isso, basta conectar
1-x ao
Lerp :
Nota: se você não entender por que uso 1-x , explicarei - isso é necessário para inverter a renderização alvo, para que os cálculos se tornem um pouco mais fáceis.
Agora que temos um rastro, a cor da terra está ficando marrom. Se não houver cor, ela permanecerá branca.
O próximo passo é o deslocamento dos vértices. Para fazer isso, adicione os nós selecionados e conecte tudo da seguinte maneira:
Isso fará com que todas as áreas de neve subam
25 unidades. Áreas sem neve têm um deslocamento zero, o que criará uma trilha.
Nota: Você pode alterar DisplacementHeight para aumentar ou diminuir os níveis de neve. Observe também que DisplacementHeight é o mesmo valor que o deslocamento de captura. Quando eles têm o mesmo significado, isso nos dá a deformação exata. Mas há casos em que você precisa alterá-los individualmente, então os deixei como parâmetros separados.
Clique em
Aplicar e retorne ao editor principal. Crie uma instância
BP_Capture no nível e forneça as coordenadas
(0, 0, -2000) para colocá-la no subsolo. Clique em
Play e passeie com as teclas
W ,
A ,
S e
D para entortar a neve.
A deformação funciona, mas não há vestígios! Isso aconteceu porque a captura substitui a renderização de destino toda vez que a captura é executada. Precisamos de alguma maneira de tornar as faixas
permanentes .
Criando rastreamentos permanentes
Para criar persistência, precisamos de outra renderização de destino (
buffer constante ), na qual todo o conteúdo da captura será salvo antes da substituição. Em seguida, adicionaremos um buffer constante à captura (após a substituição). Temos um loop no qual cada renderização de destino grava em outro. É assim que criaremos a permanência do rastreamento.
Primeiro, precisamos criar um buffer constante.
Criando um buffer persistente
Vá para a pasta
RenderTargets e crie um novo
destino de renderização chamado
RT_Persistent . Neste tutorial, não precisamos alterar os parâmetros de textura, mas em seu próprio projeto, você precisará garantir que ambas as renderizações de destino usem a mesma resolução.
Em seguida, precisamos de material que copie a captura em um buffer permanente. Abra
Materials \ M_DrawToPersistent e adicione um nó
Sample Texture . Selecione a textura
RT_Capture para
ela e conecte-a da seguinte maneira:
Agora precisamos usar o material de desenho. Clique em
Aplicar e, em seguida, abra
BP_Capture . Primeiro, crie uma instância dinâmica do material (depois precisaremos passar valores para ele). Adicione os nós destacados ao
Event BeginPlay :
Limpar destino de renderização Os nós
2D limpam cada renderização de destino antes do uso.
Em seguida, abra a função
DrawToPersistent e adicione os nós destacados:
Em seguida, precisamos garantir que o desenho para o buffer constante seja realizado em cada quadro, porque a captura ocorre em cada quadro. Para fazer isso, adicione
DrawToPersistent ao
tick de
evento .
Finalmente, precisamos adicionar um buffer persistente de volta à renderização de captura de destino.
Grave de volta para capturar
Clique em
Compilar e abra o
PP_DepthCheck . Em seguida, adicione os nós destacados. Para
Exemplo de textura, defina o valor como
RT_Persistent :
Agora que o destino processa a gravação, obtemos traços que permanecem. Clique em
Aplicar e feche o material. Clique em
Play e comece a deixar faixas!
O resultado parece ótimo, mas o circuito resultante funciona apenas para uma área do mapa. Se você for além da área de captura, os traços deixarão de aparecer.
Você pode resolver esse problema movendo a área de captura com o player. Isso significa que os rastros sempre aparecerão em torno da área em que o jogador está localizado.
Nota: à medida que a captura se move, todas as informações fora da área de captura são removidas. Isso significa que se você retornar à área onde já havia vestígios, eles já desaparecerão. No próximo tutorial, mostrarei como criar faixas parcialmente retidas.
Capturar movimento
Você pode decidir que é simples o suficiente para vincular a posição de captura XY à posição XY do jogador. Mas se você fizer isso, a renderização alvo começará a ficar embaçada. Isso ocorre porque estamos movendo a renderização de destino com uma etapa menor que um pixel. Quando isso acontece, a nova posição do pixel fica
entre os pixels. Como resultado, um pixel é interpolado por vários pixels. Aqui está o que parece:
Para corrigir esse problema, precisamos mover a captura em etapas discretas. Calculamos
o tamanho do pixel no mundo e, em seguida, movemos a captura para etapas iguais a esse tamanho. Então, cada pixel nunca estará entre os outros, portanto o desfoque não aparecerá.
Para começar, vamos criar um parâmetro no qual o local da captura será armazenado. O material terrestre precisará dele para realizar cálculos de projeção. Abra
MPC_Capture e adicione um
Parâmetro de Vetor chamado
CaptureLocation .
Em seguida, você precisa atualizar o material terra para usar o novo parâmetro. Feche
MPC_Capture e abra
M_Landscape . Modifique a primeira parte dos cálculos da projeção da seguinte maneira:
Agora a renderização alvo sempre será projetada no local da captura. Clique em
Aplicar e feche o material.
Em seguida, faremos a captura se mover com uma etapa discreta.
Movimento discreto de captura de etapas
Para calcular o tamanho do pixel no mundo, você pode usar a seguinte equação:
(1 / RenderTargetResolution) * CaptureSize
Para calcular a nova posição, usamos a equação mostrada abaixo para cada componente da posição (no nosso caso, para as coordenadas X e Y).
(floor(Position / PixelWorldSize) + 0.5) * PixelWorldSize
Agora use-os na captura do blueprint. Para economizar tempo, criei a macro
SnapToPixelWorldSize para a segunda equação. Abra
BP_Capture e, em seguida, abra a função
MoveCapture . Em seguida, crie o seguinte diagrama:
Ele calculará o novo local e salvará a diferença entre o local novo e o atual no
MoveOffset . Se você estiver usando uma resolução diferente de 256 × 256, altere o valor destacado.
Em seguida, adicione os nós selecionados:
Este circuito moverá a captura com o deslocamento calculado. Ela salvará o novo local de captura no
MPC_Capture para que possa ser usado pelo material do solo.
Finalmente, precisamos executar uma atualização de posição em cada quadro. Feche a função e adicione-a ao
Tick de evento antes de
DrawToPersistent MoveCapture .
Mover uma captura é apenas metade da solução. Também precisamos mover o buffer constante. Caso contrário, a captura e o buffer persistente ficarão fora de sincronia e produzirão resultados estranhos.
Movimento permanente do buffer
Para mudar um buffer constante, precisamos passar pelo deslocamento de deslocamento calculado. Abra
M_DrawToPersistent e adicione os nós destacados:
Devido a isso, o buffer constante será alterado pelo valor do deslocamento transmitido. Como no material da Terra, precisamos inverter a coordenada X e realizar o mascaramento. Clique em
Aplicar e feche o material.
Então você precisa transferir o deslocamento. Abra
BP_Capture e, em seguida, abra a função
DrawToPersistent . Em seguida, adicione os nós destacados:
É assim que convertemos o
MoveOffset em espaço UV e o passamos para o material de desenho.
Clique em
Compilar e feche o blueprint. Clique em
Play e corra para o conteúdo do seu coração! Não importa o quão longe você corra, os traços sempre permanecerão ao seu redor.
Para onde ir a seguir?
O projeto finalizado pode ser baixado aqui.
Não é necessário usar as faixas criadas neste tutorial apenas para neve. Você pode até usá-los para coisas como grama triturada (no próximo tutorial, mostrarei como criar uma versão avançada do sistema).
Se você deseja trabalhar com paisagens e renderizações de alvo, recomendo assistir ao vídeo de Chris Murphy:
Construindo efeitos de jogabilidade de última geração com o Blueprint . Este tutorial mostrará como criar um enorme laser que queima terra e grama!