Unidade Terrane e mistura de malha

Olá pessoal, em junho, a OTUS está lançando o curso Unity Games Developer novamente . Antecipando o início do curso, preparamos uma tradução de material interessante sobre o tema.



Hoje, falaremos sobre como mesclar uma malha terrestre (ou outra malha) no Unity. Este guia é bastante avançado, mas tentei dividi-lo em etapas separadas. Supõe-se que você tenha habilidades gerais do Unity e conhecimentos básicos de C #. O guia foi desenvolvido para amplificação, mas acho que também pode ser aplicado ao Shader Graph. Este é o meu primeiro guia, por isso espero que seja claro o suficiente. Se você quiser adicionar algo, me avise. Incluí aqui um kit inicial, que possui alguns shaders adicionais para este projeto, além de um kit básico para começar. Todos os arquivos de projeto do manual de hoje estão disponíveis para meus clientes por US $ 5, mas em um futuro próximo, eles estarão disponíveis para todos.

Kit inicial
O projeto inteiro

: , , . . , , , . ( ) unity . , , .

Então, vamos começar. Para começar, vou destacar o problema e me aprofundar na teoria em que a solução será baseada. Para fazer a mistura entre o terrane e outras malhas, é necessário dizer de alguma forma ao sombreador onde ocorre a interseção com o terrane. Dizer é mais fácil do que fazer, mas existe um caminho. E em que consiste? Render Texture nos ajudará !

O que é uma textura de renderização?


Render Texture é, de fato, a posição da câmera, que é salva no arquivo de ativos. A Textura de renderização nos jogos é usada com mais frequência em coisas como a criação de telas de vigilância por vídeo. Podemos usar a Textura de renderização para salvar a visualização da câmera no editor, para que essa textura possa ser usada como um sombreador. Isso é útil porque podemos incluir informações sobre nosso terreno, como altura, normais e cores, em uma textura que pode ser usada em tempo de execução para se misturar entre malhas e terran.


Renderização de textura usada para exibir imagens de ganso em TVs no jogo Untitled Goose

Costumização


Para começar, vamos criar uma nova cena e terrane. Defina um tamanho conveniente para o trabalho, por exemplo, 200 x 200. Agora você pode organizar o terrane como quiser. Em seguida, crie uma nova camada e nomeie-a como “Terreno” e atribua um terreno a essa camada. Isso é necessário para que a máscara da câmera possa gravar terrane na Textura de renderização .


Minha obra-prima terrane

Os arquivos de origem do projeto têm uma pré-fabricada chamada “BlendBakingCamera” - arraste-a para o palco. Você receberá uma câmera ortográfica simples. Na câmera, você precisa colocar uma máscara de seleção em uma nova camada de terrane. Posicione a câmera no centro do terreno, ligeiramente acima do ponto mais alto do terreno. Em seguida, ajuste o plano do clipe distante para que a câmera veja o piso terrestre. No final, a cena deve se parecer com isso:


Shader de substituição


Agora que a câmera está configurada, você precisa encontrar uma maneira de gravar dados terrestres. Para isso, precisamos de Shaders de substituição. O Shader de substituição parece uma perspectiva duvidosa; eu mesmo não entendo como funciona há muito tempo. Mas, de fato, tudo é muito simples e eficaz. Usar o Shader de substituição significa essencialmente renderizar cada objeto no campo de visão da câmera com um único shader, independentemente de qual shader esteja sobreposto aos objetos. Como resultado, todos os objetos serão renderizados usando o Shader de substituição selecionado , que na verdade é apenas um shader comum.

O shader necessário para a mistura é o shader de profundidade. Ele cria a profundidade da cena e é um componente-chave na criação de nosso efeito de mesclagem, pois grava os valores de profundidade de nossa câmera na textura para que possamos lê-los mais tarde. Para saber mais sobre esse shader e o shader de substituição em geral, recomendo que você leia este manual em Como tornar as coisas com bom aspecto no Unity .


Exemplo de Shader de profundidade

Vamos começar a assar


Vamos criar uma nova classe e denominá- la "TerrainBlendingBaker" . Vamos começar implementando uma máscara de profundidade para o terrane de base. Mais tarde, retornaremos a esse script para adicionar cores e normais.

Defina várias variáveis.

//Shader that renders object based on distance to camera
public Shader depthShader;
//The render texture which will store the depth of our terrain
public RenderTexture depthTexture;
//The camera this script is attached to
private Camera cam;

Agora vamos criar um novo método e chamá-lo de "UpdateBakingCamera" . Neste método, determinaremos os dados da câmera que o shader pode precisar para tornar a mistura em variáveis ​​globais.

private void UpdateBakingCamera()
    {
        //if the camera hasn't been assigned then assign it
        if (cam == null)
        {
            cam = GetComponent<Camera>();
        }
 
        //the total width of the bounding box of our cameras view
        Shader.SetGlobalFloat("TB_SCALE", GetComponent<Camera>().orthographicSize * 2);
        //find the bottom corner of the texture in world scale by subtracting the size of the camera from its x and z position
        Shader.SetGlobalFloat("TB_OFFSET_X", cam.transform.position.x - cam.orthographicSize);
        Shader.SetGlobalFloat("TB_OFFSET_Z", cam.transform.position.z - cam.orthographicSize);
        //we'll also need the relative y position of the camera, lets get this by subtracting the far clip plane from the camera y position
        Shader.SetGlobalFloat("TB_OFFSET_Y", cam.transform.position.y - cam.farClipPlane);
        //we'll also need the far clip plane itself to know the range of y values in the depth texture
        Shader.SetGlobalFloat("TB_FARCLIP", cam.farClipPlane);
 
        //NOTE: some of the arithmatic here could be moved to the shader but keeping it here makes the shader cleaner so ¯\_(ツ)_/¯
    }

Agora vamos assar a profundidade do terrane na textura.

// The context menu tag allows us to run methods from the inspector (https://docs.unity3d.com/ScriptReference/ContextMenu.html)
[ContextMenu("Bake Depth Texture")]
public void BakeTerrainDepth()
{
    //call our update camera method 
    UpdateBakingCamera();
 
    //Make sure the shader and texture are assigned in the inspector
    if (depthShader != null && depthTexture != null)
    {
        //Set the camera replacment shader to the depth shader that we will assign in the inspector 
        cam.SetReplacementShader(depthShader, "RenderType");
        //set the target render texture of the camera to the depth texture 
        cam.targetTexture = depthTexture;
        //set the render texture we just created as a global shader texture variable
        Shader.SetGlobalTexture("TB_DEPTH", depthTexture);
    }
    else
    {
        Debug.Log("You need to assign the depth shader and depth texture in the inspector");
    }
}

Os valores ficarão mais claros quando começarmos a trabalhar com o shader. Aqui está uma pequena figura que pode lançar alguma luz:


Vamos transferir o terrane para a textura de profundidade, para depois ler e entender onde fazer a mistura

Agora temos tudo o que precisamos para criar o efeito básico de mistura no shader. Neste ponto, o script se parece com: pastebin.com/xNusLJfh

Ok, agora que temos o script, podemos adicioná-lo à câmera de cozimento que adicionamos anteriormente. Os ativos iniciais têm um sombreador chamado 'DepthShader' (Inresin / Shaders / DepthShader) e uma Textura de renderização chamada 'DepthTerrainRT' (Inresin / RenderTextures / DepthTextureRT) , você precisa colocá-los nos campos apropriados no inspetor.

Depois disso, basta executar o método no menu de contexto para definir a profundidade do terreno na Textura de renderização .


Shader


Vamos finalmente criar um shader para a mistura. Crie um novo shader de amplificação padrão e abra-o, chame -o de 'TerrainBlending' ou algo assim.

Agora precisamos criar um UV para a textura de renderização . Essa será a diferença entre o ponto que está sendo renderizado e a posição da câmera de cozimento dimensionada em relação à área total. As três variáveis ​​globais aqui são as que acabamos de declarar no código. Também definimos worldY como uma variável local, precisaremos mais tarde.



Vamos pegar a textura de profundidade, que atribuímos como uma variável global (para este nó de amostra de textura adicional , torne-o global e nomeie-o 'TB_DEPTH'), se colocarmos a saída no debug amplify shader 'a field , podemos ver o que acontece. Crie um plano com o material ao qual nosso novo shader será aplicado.


Portanto, no shader, temos informações sobre a profundidade, agora precisamos adicionar um deslocamento em y para obter a mistura.



Esse bloco dimensiona a posição y da máscara do plano do clipe remoto , subtrai esse valor da posição mundial ao longo do eixo y do ponto que está sendo renderizado e, finalmente, o desloca para o lado inferior da caixa delimitadora da câmera (posição y da câmera menos o plano do clipe distante ).

Já alguma coisa! Vemos como as arestas do avião se fundem com o terrano.



Ok, vamos tornar a mistura mais controlada.



Agora podemos controlar a espessura e a área do terrane de mistura.



Acho que podemos adicionar um pouco de barulho. Vamos usar a posição mundial para gerar ruído a partir da textura.



As texturas de ruído estão na pasta de textura no projeto de inicialização, elas podem ser atribuídas no inspetor ou como uma constante no próprio sombreador.

Finalmente, é hora de adicionar algumas texturas! Para começar, vamos usar algumas texturas simples de uma cor; adicionei duas texturas à pasta com ativos de textura. Torne a textura 'SingleColorGrass' uma textura terrane. Em seguida, no shader, é necessário criar uma textura terrane e um nó de textura do objeto . Vamos alternar entre eles no canal vermelho da máscara que acabamos de criar.




E aqui é o shader completa.



Adicionando personalizado toon iluminação ou iluminação apagadas modelos irá fornecer os melhores resultados para este shader. Liguei apagadoterrane shader e versão apagada do shader no pacote completo disponível para os patrocinadores.


Terrane e malhas

apagadas Eu também recomendo adicionar um shader triplanar ao terrane e possivelmente a outras malhas. Eu posso considerar esse problema no próximo guia.

Bem, estamos quase terminando o tema principal do guia de hoje. Adicionei algumas seções que podem ajudar na extensão do sombreador.

Extensão de Shader - Normal


Eu adicionei um sombreador regular aos arquivos, você pode usá-lo para escrever normais na superfície terrane e também usar blending. Para o meu jogo, não preciso de misturas normais, então experimentei um pouco e parece que o experimento foi um sucesso, e minha ideia funciona bem para terrenos sem um alto grau de alteração de altura. Incluí o shader de mapeamento normal no diretório com os shaders do conjunto inicial. Aqui você pode ver minha implementação básica do mapa normal:



O código é quase o mesmo do mapa de profundidade, mas desta vez usaremos o mapa normal como um shader de substituição . Também adicionei um conjunto de textura de renderização para gravação normal (aqui você pode precisar de uma configuração adicional).

Para que os normais funcionem bem, pode ser necessário um pouco mais de esforço relacionado ao ajuste, e também pode haver uma restrição associada aos terrenos de baixo nível (mas eu não realizei testes suficientes para confirmar isso).

Extensão de Shader - Todas as Cores


Não vou me aprofundar nos detalhes deste tópico, pois ele vai além do necessário para o meu jogo, mas pensei nisso enquanto escrevia este guia. Para adicionar a mistura de várias cores, podemos selecionar apagadas cores terrane e guardá-las como texturas. Nesse caso, somos limitados por uma resolução bastante baixa, mas esse método funciona bem ao usar texturas de terreno de uma cor e texturas de baixa resolução ou ao usar sangramento fraco . Com pequenos ajustes, eles também podem ser aplicados em malhas terrane.

Aqui está o código para a opção multicolorida:

[Header("The following settings are only if using the multi-color terrain shader")]
//Shader that renders the unlit terraom of an object
public Shader unlitTerrainShader;
//The render texture which will store the normals of our terrain
public RenderTexture surfaceTexture;
//An unlit terrain material used to capture the texture of our terrain without any lighting
public Material unlitTerrainMaterial;
//The terrain you want to capture the textures of
public Terrain yourTerrain;
 
[ContextMenu("Bake Surface Texture")]
public void BakeTerrainSurface()
{
    UpdateBakingCamera();
 
    //return if there is no terrain assigned
    if (yourTerrain == null)
    {
        Debug.Log("You need to assign a terrain to capture surface texture");
        return;
    }
 
    StartCoroutine(BakeColors());
}
 
IEnumerator BakeColors()
{
    Material tempTerrainMaterial = yourTerrain.materialTemplate;
 
    yourTerrain.materialTemplate = unlitTerrainMaterial;
 
    yield return 0;
 
    cam.SetReplacementShader(unlitTerrainShader, "RenderType");
    cam.targetTexture = surfaceTexture;
    Shader.SetGlobalTexture("TB_SURFACE", surfaceTexture);
 
    yield return 0;
 
    cam.targetTexture = null;
    yourTerrain.materialTemplate = tempTerrainMaterial;
 
    yield return null;
 
}

A única mudança no shader é que, em vez de usar uma textura predefinida, usamos uma textura de superfície de terreno definida globalmente que usará a posição relativa como UV. Uma extensão adicional permite obter uma mistura mais agradável de texturas.


Misturando várias texturas

Aqui está um gráfico multicolorido completo com um sombreador de mesclagem normal:




Conclusão


Parabéns se você leu até este ponto! Como eu disse, este é o meu primeiro guia, então eu realmente aprecio o feedback. Se você quiser me apoiar na criação de jogos e tutoriais, dê uma olhada aqui ou assine o meu Twitter .



Saiba mais sobre o curso

All Articles