Shaders de dissolução e exploração mundial

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; //Get how much we have to dissolve based on our dissolve texture clip(dissolve_value - _Amount); //Dissolve! //Your shader body, you can set the Albedo etc. //[...] } 

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 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() { //Updates the _PlayerPos variable in all the shaders //Be aware that the parameter name has to match the one in your shaders or it wont' work Shader.SetGlobalVector("_PlayerPos", transform.position); //"transform" is the transform of the Player } 



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; //add this and Unity will set it automatically }; 

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); //"6" is the maximum distance where your object will start showing //Set albedo, alpha, smoothness etc[...] } 

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; //I modified the worldPos to reduce the texture size 

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).

Source: https://habr.com/ru/post/pt427401/


All Articles