Parte 1: Dissolução Shader
O shader de dissolução retorna um efeito bonito; além disso, é fácil criar e entender; Hoje vamos fazê-lo no
Unity Shader Graph e também escrever no
HLSL .
Aqui está um exemplo do que criaremos:
Como isso funciona
Para criar um shader de
dissolução , teremos que
trabalhar com o valor
AlphaClipThreshold no shader “Shader Graph” ou usar a função HLSL chamada
clip .
Essencialmente, dizemos ao shader
para não renderizar o pixel com base na
textura e no
valor passado. Precisamos saber o seguinte: as
partes brancas se dissolvem mais rapidamente .
Usaremos a seguinte textura:
Você pode criar o seu próprio - linhas retas, triângulos, mas qualquer coisa! Lembre-se de que as
partes brancas se dissolvem mais rapidamente .
Eu criei essa textura no Photoshop usando o filtro Clouds.
Mesmo se você estiver interessado apenas no Shader Graph e não souber nada sobre o HLSL, ainda recomendo a leitura desta parte, porque é útil entender como o Unity Shader Graph funciona por dentro.
Hlsl
No HLSL, usamos a função
clip (x) . A função
clipe (x) descarta todos os pixels com um valor menor que
zero . Portanto, se chamarmos
clip (-1) , teremos certeza de que o shader nunca renderizará esse pixel. Você pode ler mais sobre o
clipe no
Microsoft Docs .
As propriedades
O sombreador precisa de duas propriedades,
Dissolver textura e
Quantidade (que indicará o processo geral de execução). Assim como outras propriedades e variáveis, você pode chamá-las como quiser.
Properties { //Your other properties //[...] //Dissolve shader properties _DissolveTexture("Dissolve Texture", 2D) = "white" {} _Amount("Amount", Range(0,1)) = 0 }
Certifique-se de adicionar o seguinte após CGPROGRAM SubShader (em outras palavras, declarar variáveis):
sampler2D _DissolveTexture; half _Amount;
Além disso, não esqueça. que seus nomes devem corresponder aos nomes na seção Propriedades.
Função
Iniciamos a função
Superfície ou
Fragmento , amostrando a
textura da dissolução e obtendo o
valor do vermelho . PS Nossa textura é armazenada em
escala de cinza , ou seja, seus valores de
R ,
G e
B são iguais e você pode
escolher qualquer um deles . Por exemplo,
branco é
(1,1,1) ,
preto é
(0,0,0) .
No meu exemplo, eu uso um shader de superfície:
void surf (Input IN, inout SurfaceOutputStandard o) { half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).r;
E é isso aí! Podemos aplicar esse processo a qualquer shader existente e transformá-lo em um
shader de dissolução !
Aqui está o Surface Shader padrão do mecanismo Unity, transformado em um
shader de dissolução de dois lados : Shader "Custom/DissolveSurface" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 //Dissolve properties _DissolveTexture("Dissolve Texutre", 2D) = "white" {} _Amount("Amount", Range(0,1)) = 0 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 Cull Off //Fast way to turn your material double-sided CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; //Dissolve properties sampler2D _DissolveTexture; half _Amount; void surf (Input IN, inout SurfaceOutputStandard o) { //Dissolve function half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).r; clip(dissolve_value - _Amount); //Basic shader function fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } ENDCG } FallBack "Diffuse" }
Gráfico de sombreador
Se precisarmos criar esse efeito usando o Unity
Shader Graph , devemos usar o valor
AlphaClipThreshold (que funciona de maneira diferente do
clipe (x) do HLSL). Neste exemplo, criei um shader PBR.
A função
AlphaClipThreshold instrui o sombreador a descartar todos os pixels cujo valor é menor que seu valor
alfa . Por exemplo, se for
0,3f e nosso valor alfa for
0,2f , o sombreador
não renderizará esse pixel. A função
AlphaClipThreshold pode ser encontrada na
documentação do
Unity :
Nó mestre PBR e
Nó mestre apagado .
Aqui está o nosso shader finalizado:
Amostramos a
textura de dissolução e obtemos o
valor de vermelho e, em seguida, adicionamos ao valor
Amount (que é uma propriedade que adicionei para indicar o processo geral de execução, um valor de 1 significa dissolução completa) e a conectamos ao
AlphaClipThreshold .
Feito!Se você deseja aplicá-lo a qualquer sombreador existente,
copie as conexões do nó para
AlphaClipThreshold (não perca as propriedades necessárias!). Você também pode torná-lo
bilateral e obter resultados ainda mais bonitos!
Tonalizador de dissolução de contorno
E se você tentar adicionar
contornos a ele? Vamos fazer isso!
Não podemos trabalhar com pixels já dissolvidos, porque depois de soltá-
los, eles desaparecem para sempre . Em vez disso, podemos trabalhar com valores "quase dissolvidos"!
No
HLSL, isso é muito simples, basta adicionar algumas linhas de código após calcular o
clipe :
void surf (Input IN, inout SurfaceOutputStandard o) { //[...] //After our clip calculations if (dissolve_value - _Amount < .05f) //outline width = .05f o.Emission = fixed3(1, 1, 1); //emits white color //Your shader body, you can set the Albedo etc. //[...] }
Feito!Ao trabalhar com o
Shader Graph, a lógica é um pouco diferente. Aqui está o shader finalizado:
Podemos criar
efeitos muito
interessantes com um simples
shader de dissolução ; Você pode experimentar
diferentes texturas e
valores , além de criar outra coisa!
Parte 2: Shader de exploração mundial
Um shader de
exploração mundial (ou um "
shader de dissolução mundial ou
dissolução global ") nos permite ocultar igualmente todos os objetos na cena com base na distância da posição; agora criaremos esse shader no
Unity Shader Graph e o escreveremos em
HLSL .
Aqui está um exemplo do que criaremos:
Distância como parâmetro
Suponha que precisamos
dissolver um objeto em uma cena, se estiver
muito longe do jogador . Já anunciamos o parâmetro
_Amount , que controla o desaparecimento / dissolução do objeto, portanto, precisamos substituí-lo pela distância entre o objeto e o jogador.
Para fazer isso, precisamos assumir as posições de
jogador e
objeto .
Posição do Jogador
O processo será semelhante para o
Unity Shader Graph e o
HLSL : precisamos transferir a posição do jogador no código.
private void Update() {
Gráfico de sombreador
Posição e distância do objeto
Usando o Gráfico Shader, podemos usar os nós Posição e Distância.
PS Para que este sistema funcione com os Sprite Renderers, você precisa adicionar a propriedade _MainTex, fazer uma amostra e conectá-la ao albedo. Você pode ler meu tutorial de
shader difuso Sprites anterior (que usa gráfico de shader).
HLSL (superfície)
Posição do Objeto
No HLSL, podemos adicionar a variável
worldPos à nossa estrutura de
Entrada para obter as posições dos vértices dos objetos.
struct Input { float2 uv_MainTex; float3 worldPos;
Na
página de documentação do Unity, você pode descobrir quais outros parâmetros internos podem ser adicionados à estrutura de Entrada.
Aplicar distância
Precisamos usar a distância entre os objetos e o jogador como a quantidade de dissolução. Para fazer isso, você pode usar a função
distância interna (
documentação da Microsoft ).
void surf (Input IN, inout SurfaceOutputStandard o) { half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).x; float dist = distance(_PlayerPos, IN.worldPos); clip(dissolve_value - dist/ 6f);
Resultado (3D)
Resultado (2D)
Como você pode ver, os objetos se dissolvem “localmente”, não obtivemos um efeito homogêneo, porque obtemos o “valor de dissolução” da textura amostrada usando a UV de cada objeto. (Em 2D, isso é menos perceptível).
3D LocalUV dissolver o sombreador em HLSL
Shader "Custom/GlobalDissolveSurface" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 _DissolveTexture("Dissolve texture", 2D) = "white" {} _Radius("Distance", Float) = 1 //distance where we start to reveal the objects } SubShader{ Tags { "RenderType" = "Opaque" } LOD 200 Cull off //material is two sided CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 sampler2D _MainTex; sampler2D _DissolveTexture; //texture where we get the dissolve value struct Input { float2 uv_MainTex; float3 worldPos; //Built-in world position }; half _Glossiness; half _Metallic; fixed4 _Color; float3 _PlayerPos; //"Global Shader Variable", contains the Player Position float _Radius; void surf (Input IN, inout SurfaceOutputStandard o) { half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).x; float dist = distance(_PlayerPos, IN.worldPos); clip(dissolve_value - dist/ _Radius); fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } ENDCG } FallBack "Diffuse" }
Sprites Difusos - LocalUV Dissolve Shader em HLSL
Shader "Custom/GlobalDissolveSprites" { Properties { [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {} _Color("Tint", Color) = (1,1,1,1) [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 [HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1) [HideInInspector] _Flip("Flip", Vector) = (1,1,1,1) [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {} [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0 _DissolveTexture("Dissolve texture", 2D) = "white" {} _Radius("Distance", Float) = 1 //distance where we start to reveal the objects } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha CGPROGRAM #pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing #pragma multi_compile _ PIXELSNAP_ON #pragma multi_compile _ ETC1_EXTERNAL_ALPHA #include "UnitySprites.cginc" struct Input { float2 uv_MainTex; fixed4 color; float3 worldPos; //Built-in world position }; sampler2D _DissolveTexture; //texture where we get the dissolve value float3 _PlayerPos; //"Global Shader Variable", contains the Player Position float _Radius; void vert(inout appdata_full v, out Input o) { v.vertex = UnityFlipSprite(v.vertex, _Flip); #if defined(PIXELSNAP_ON) v.vertex = UnityPixelSnap(v.vertex); #endif UNITY_INITIALIZE_OUTPUT(Input, o); o.color = v.color * _Color * _RendererColor; } void surf(Input IN, inout SurfaceOutput o) { half dissolve_value = tex2D(_DissolveTexture, IN.uv_MainTex).x; float dist = distance(_PlayerPos, IN.worldPos); clip(dissolve_value - dist / _Radius); fixed4 c = SampleSpriteTexture(IN.uv_MainTex) * IN.color; o.Albedo = c.rgb * ca; o.Alpha = ca; } ENDCG } Fallback "Transparent/VertexLit" }
PS Para criar o último sombreador, copiei o sombreador padrão Unity Sprites-Diffuse e adicionei a parte "dissolver" descrita anteriormente nesta parte do artigo. Todos os shaders padrão podem ser encontrados
aqui .
Tornando o efeito homogêneo
Para tornar o efeito homogêneo, podemos usar coordenadas globais (posição no mundo) como coordenadas UV da textura de dissolução. Também é importante definir
Wrap = Repeat nos parâmetros de textura de dissolução para que possamos repetir a textura sem perceber (verifique se a textura é uniforme e se repete bem!)
HLSL (superfície)
half dissolve_value = tex2D(_DissolveTexture, IN.worldPos / 4).x;
Gráfico de sombreador
Resultado (2D)
Este é o resultado: podemos notar que a textura da dissolução agora é uniforme para o mundo inteiro.
Esse sombreador já é
ideal para jogos 2D , mas para
objetos 3D ele precisa ser
aprimorado .
O problema com objetos 3D
Como você pode ver, o sombreador não funciona para faces "não verticais" e distorce bastante a textura. É por isso que acontece. que as coordenadas UV precisam do valor float2 e, se passarmos por worldPos, ele receberá apenas X e Y.
Se eliminarmos esse problema aplicando cálculos para exibir a textura em todas as faces, chegaremos a um novo problema: ao escurecer, os objetos se cruzam entre si e não permanecem homogêneos.
Será difícil para os iniciantes entender a solução: é necessário livrar-se da textura, gerar ruído tridimensional no mundo e obter o "valor da dissolução". Neste post, não explicarei a geração de ruído 3D, mas você pode encontrar várias funções prontas para usar!
Aqui está um exemplo de sombreador:
https://github.com/keijiro/NoiseShader . Você também pode aprender como gerar ruído aqui:
https://thebookofshaders.com/11/ e aqui:
https://catlikecoding.com/unity/tutorials/noise/Vou definir minha função de superfície dessa maneira (assumindo que você já tenha escrito a parte do ruído):
void surf (Input IN, inout SurfaceOutputStandard o) { float dist = distance(_PlayerPos, IN.worldPos); //"abs" because you have to make sure that the noise is between the range [0,1] //you can remove "abs" if your noise function returns a value between [0,1] //also, replace "NOISE_FUNCTION_HERE" with your 3D noise function. half dissolve_value = abs(NOISE_FUNCTION_HERE(IN.worldPos)); if (dist > _Radius) { float clip_value = dissolve_value - ((dist - _Radius) / _Radius); clip(clip_value); if (clip_value < 0.05f) o.Emission = float3(1, 1, 1); } fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; }
Um breve lembrete do HLSL: antes de usar / chamar uma função, ela deve ser escrita / declarada.
PS Se você deseja criar um sombreador usando o Unity Shader Graph, precisa usar nós personalizados (e gerar ruído escrevendo o código HLSL neles). Falarei sobre nós personalizados em um tutorial futuro.
Resultado (3D)
Adicionando contornos
Para adicionar contornos, você precisa repetir o processo da parte anterior do tutorial.
Efeito invertido
E se queremos reverter esse efeito? (Objetos devem desaparecer se um jogador estiver por perto)
Basta mudar uma linha:
float dist = _Radius - distance(_PlayerPos, IN.worldPos);
(O mesmo processo se aplica ao Shader Graph).