Água Estilizada na Unity com Shader Graph (URP)

Unity URP water nó a nó no Shader Graph: cor por profundidade, espuma na borda, ondas no vertex e refração com Scene Color, e por que a água fica opaca no URP.
Água Estilizada na Unity com Shader Graph (URP)
Uma Unity URP water estilizada precisa de quatro ingredientes no Shader Graph: cor que muda com a profundidade (raso claro, fundo escuro), espuma onde a água encosta em algo, movimento (ondas no vertex e normais rolando) e uma refração leve que distorce o que está embaixo. Os dois primeiros dependem de uma configuração do URP Asset que, desligada, deixa a água inteira opaca e chapada. É o erro mais comum, e ele aparece logo no começo do post.
No fim você tem um material de água toon pronto para lago ou mar de jogo estilizado, no Unity 6.3 LTS, e um script C# para fazer objetos boiarem sincronizados com as ondas do shader.
O que a água estilizada precisa: profundidade, espuma, movimento
Água de jogo estilizado é ilusão, e cada ilusão tem um dado por trás:
- Profundidade: quanto de água existe entre a superfície e o chão naquele pixel. Vem da Depth Texture.
- Espuma: onde essa profundidade é quase zero (margem, pedra, pilar). Mesma Depth Texture, outro limite.
- Movimento: deslocamento de vértices para as ondas e normal maps que deslizam para o brilho.
- Refração: a imagem do fundo, levemente deslocada. Vem da Opaque Texture.
Configurar o URP Asset: Depth Texture e Opaque Texture
Antes de abrir o Shader Graph:
- Edit > Project Settings > Graphics (ou Quality, se cada nível tem o próprio asset) e veja qual URP Asset está em uso.
- Selecione esse asset. Na seção Rendering, ligue Depth Texture e Opaque Texture.
- Na câmera, em Rendering, os campos Depth Texture e Opaque Texture devem estar em Use Pipeline Settings (ou ligados).
A Depth Texture faz o URP gerar a _CameraDepthTexture, que o nó Scene Depth lê. A Opaque Texture gera a _CameraOpaqueTexture, uma cópia da tela feita antes dos objetos transparentes, que o nó Scene Color lê. Sem a Depth Texture, o Scene Depth devolve um valor fixo (cinza médio) e a água perde toda a variação. O mesmo URP Asset tem Opaque Downsampling, que reduz a resolução dessa cópia e economiza em mobile.
Shader Graph de água na Unity URP: cor rasa e funda
- Create > Shader Graph > URP > Lit Shader Graph. Nomeie
StylizedWater. - Graph Settings: Surface Type: Transparent, Blending Mode Alpha. Scene Depth e Scene Color precisam de superfície transparente para funcionar como esperado.
- Propriedades:
ShallowColor(Color, turquesa, alpha 0,6)DeepColor(Color, azul escuro, alpha 0,95)DepthDistance(Float, 2)
- Nó Scene Depth com Sampling Mode Eye: distância da câmera até o chão atrás da água, em metros.
- Nó Screen Position em modo Raw, e um Split. O componente W é a distância da câmera até a superfície da água.
- Subtract: Scene Depth menos W. O resultado é quantos metros de água existem naquele pixel.
- Divide pelo
DepthDistancee Saturate. Agora você tem 0 na margem e 1 a partir de 2 metros de fundo. - Lerp de
ShallowColorparaDeepColorusando esse valor. Ligue o RGB em Base Color e o canal A em Alpha.
Crie um material, aplique num Plane sobre um terreno com desnível e veja a cor escurecer conforme o fundo afunda.
Espuma de borda com Scene Depth e ruído
A espuma usa a mesma diferença de profundidade, com um limite bem menor.
- Propriedades:
FoamDistance(Float, 0,4),FoamColor(Color, branco),FoamNoiseScale(Float, 20),FoamSpeed(Float, 0,05). - Pegue a diferença de profundidade (passo 6 anterior), Divide por
FoamDistance, Saturate e One Minus. Isso é 1 grudado na margem e 0 a partir da distância da espuma. - Gradient Noise com UV deslocado no tempo: Time multiplicado por
FoamSpeed, ligado no Offset de um Tiling And Offset, que alimenta o UV do ruído. - Step: Edge = ruído, In = máscara da margem. A espuma vira manchas recortadas que crescem perto da borda.
- Lerp da cor da água para
FoamColorusando o Step, e use esse resultado em Base Color. No alpha, Maximum entre o alpha da água e o Step, para a espuma ficar opaca.
O Step dá aquela espuma recortada típica de jogo toon. Se quiser mais suave, troque por Smoothstep.
Ondas no vertex e normais rolando
Ondas. No bloco Vertex:
- Propriedades:
WaveAmplitude(0,1),WaveFrequency(1),WaveSpeed(1). - Position em espaço World, Split, some X e Z.
- Multiplique por
WaveFrequency, some Time multiplicado porWaveSpeed, passe por Sine e multiplique porWaveAmplitude. - Monte um Vector 3 (0, onda, 0), some à Position em espaço Object e ligue em Position do Vertex.
O Plane padrão da Unity tem 10 x 10 quadrados. Para onda visível, use uma malha mais subdividida, feita no Blender ou gerada por ProBuilder.
Normais. Duas amostras de um normal map de água (Sample Texture 2D com Type Normal), cada uma com Tiling And Offset deslizando em direção diferente, combinadas com o nó Normal Blend, ligadas em Normal (Tangent Space). É isso que faz a luz brilhar e se mexer na superfície. Ajuste Smoothness alto (0,9) para o brilho especular aparecer.
Refração simples com Scene Color
- Nó Screen Position em modo Default.
- Pegue o RG da normal combinada, multiplique por
RefractionStrength(Float, 0,02) e some à Screen Position. - Ligue no UV do nó Scene Color. O resultado é a imagem do fundo levemente distorcida.
- Lerp do Scene Color para a cor da água usando o alpha da cor por profundidade. Ligue em Base Color e deixe Alpha em 1, porque agora o shader já mistura o fundo sozinho.
Refração com Scene Color não mostra objetos transparentes que estejam embaixo da água, porque a Opaque Texture é tirada antes deles. Para jogo estilizado, isso raramente aparece.
Se a água tem ondas, o jogador espera que barco, folha e caixa acompanhem. A GPU não devolve a altura da onda para a física, então replique a fórmula em C#.
Arquivo WaterFloater.cs:
using UnityEngine;
public class WaterFloater : MonoBehaviour
{
private static readonly int AmplitudeId = Shader.PropertyToID("_WaveAmplitude");
private static readonly int FrequencyId = Shader.PropertyToID("_WaveFrequency");
private static readonly int SpeedId = Shader.PropertyToID("_WaveSpeed");
[SerializeField] private Renderer waterRenderer;
[SerializeField] private float waterBaseHeight = 0f;
[SerializeField] private float floatOffset = 0.05f;
private Material waterMaterial;
private void Awake()
{
waterMaterial = waterRenderer.sharedMaterial;
}
private void LateUpdate()
{
Vector3 position = transform.position;
position.y = waterBaseHeight + SampleWaveHeight(position) + floatOffset;
transform.position = position;
}
private float SampleWaveHeight(Vector3 worldPosition)
{
float amplitude = waterMaterial.GetFloat(AmplitudeId);
float frequency = waterMaterial.GetFloat(FrequencyId);
float speed = waterMaterial.GetFloat(SpeedId);
float phase = (worldPosition.x + worldPosition.z) * frequency + Time.timeSinceLevelLoad * speed;
return Mathf.Sin(phase) * amplitude;
}
}
O nó Time do Shader Graph usa o tempo desde o carregamento da cena, por isso o script usa Time.timeSinceLevelLoad. As References das propriedades no graph precisam ser exatamente _WaveAmplitude, _WaveFrequency e _WaveSpeed. Se a água estiver rotacionada ou escalada, a conta precisa considerar isso; mantenha o objeto de água sem rotação para começar.
Montar o shader é uma etapa. Fazer a água conviver com câmera, personagem que nada, barco que boia, performance em mais de uma plataforma e o resto da fase é onde o projeto solo emperra. No curso de Unity a gente encara essas integrações dentro de jogos completos, com as decisões de custo explicadas.
Erros comuns: água preta, opaca ou piscando
Água toda da mesma cor, sem gradiente. Depth Texture desligada no URP Asset ou na câmera. É o erro do título: o Scene Depth devolve um valor fixo e a diferença de profundidade não varia.
Refração preta ou cinza. Opaque Texture desligada, ou o graph está em Surface Type Opaque.
Água opaca escondendo o fundo. Surface Type em Opaque, ou alpha 1 sem a refração montada. Volte para Transparent e confira o Lerp final.
Espuma em todo lugar. O FoamDistance está maior que a profundidade da água, ou a câmera está muito perto e o Scene Depth está em modo Linear 01 em vez de Eye. Use Eye para trabalhar em metros.
Água piscando. A superfície da água coincide com o chão em alguma área (z-fighting), ou o material está escrevendo profundidade. Afaste a água do terreno e mantenha o padrão de transparente, que não escreve profundidade.
Ondas que não aparecem. Malha com poucos vértices. Subdivida o plano.
Material rosa. Graph sem target Universal ou projeto fora do URP.
Próximo passo: reflexo e interação com objetos
Com a base pronta, os próximos passos naturais são reflexo (Reflection Probe na cena e Smoothness alto já dão um resultado razoável) e interação, como rastro de espuma atrás do barco. Comece pelo reflexo, que não pede código.
Antes de partir para isso, faça uma rodada de ajuste fino com a cena real do jogo, não com o plano de teste. Abra o material e mexa em uma propriedade por vez: DepthDistance define o quão rápido a água escurece, então lago raso pede valor pequeno e mar pede valor grande. FoamDistance acima de 0,6 costuma virar uma faixa branca grossa que chama mais atenção que o personagem. WaveAmplitude alto em câmera de cima parece borracha; em câmera baixa, parece mar agitado. Salve duas ou três variações como materiais separados (lago, rio, pântano) e você tem uma biblioteca de água reaproveitável para os próximos projetos.
Para comparar abordagens, o post sobre água 2D com shader na Godot resolve a versão 2D do mesmo problema, e o de shaders na Godot para iniciantes explica os conceitos de UV, tempo e máscara que você usou aqui em forma de código. Os fundamentos do editor de nós estão no guia de Shader Graph na Unity.
Perguntas frequentes
Por que minha água no Shader Graph fica toda da mesma cor?
O nó Scene Depth depende da Depth Texture ligada no URP Asset. Sem ela, o nó devolve um cinza fixo e a diferença de profundidade vira constante, então a água fica chapada. Ligue Depth Texture no URP Asset e confira se a câmera não está com a opção desligada no próprio Inspector.
Por que a refração da água aparece preta?
O nó Scene Color lê a Opaque Texture, que também precisa estar ligada no URP Asset. Além disso, o graph precisa estar com Surface Type Transparent: em Opaque, o Scene Color amostra a tela antes dos objetos opacos serem desenhados.
A água do Shader Graph funciona em mobile?
Funciona, mas Depth Texture e Opaque Texture custam memória e banda. Em celular modesto, considere desligar a refração (Scene Color), baixar o Opaque Downsampling e manter só cor por profundidade e espuma, que já entregam a maior parte do visual.
Como fazer um objeto boiar nas ondas do shader?
A onda calculada na GPU não existe para a física. Replique a mesma fórmula em C#, lendo os parâmetros do material, e posicione o objeto na altura calculada. Assim o barco sobe e desce junto com a malha.


