Shaders de disolución y exploración mundial

Parte 1: sombreador de disolución


El sombreador de disolución devuelve un hermoso efecto, además, es fácil de crear y comprender; Hoy lo haremos en Unity Shader Graph , y también escribiremos en HLSL .

Aquí hay un ejemplo de lo que crearemos:



Como funciona


Para crear un sombreador de disolución , tendremos que trabajar con el valor AlphaClipThreshold en el sombreador "Shader Graph" o usar la función HLSL llamada clip .

Esencialmente, le decimos al sombreador que no renderice el píxel en función de la textura y el valor pasado. Necesitamos saber lo siguiente: las partes blancas se disuelven más rápido .

Utilizaremos la siguiente textura:


Puedes crear tu propia línea recta, triángulos, ¡pero cualquier cosa! Solo recuerda que las partes blancas se disuelven más rápido .

Creé esta textura en Photoshop usando el filtro Nubes.

Incluso si solo está interesado en el Shader Graph y no sabe nada sobre HLSL, le recomiendo leer esta parte, porque es útil para entender cómo funciona el Unity Shader Graph en su interior.



Hlsl


En HLSL, usamos la función clip (x) . La función clip (x) descarta todos los píxeles con un valor inferior a cero . Por lo tanto, si llamamos clip (-1) , estaremos seguros de que el sombreador nunca representará este píxel. Puede leer más sobre el clip en Microsoft Docs .

Las propiedades


El sombreador necesita dos propiedades, Disolver textura y Cantidad (que indicará el proceso de ejecución general). Al igual que con otras propiedades y variables, puede llamarlas como desee.

Properties { //Your other properties //[...] //Dissolve shader properties _DissolveTexture("Dissolve Texture", 2D) = "white" {} _Amount("Amount", Range(0,1)) = 0 } 

Asegúrese de agregar lo siguiente después de CGPROGRAM SubShader (en otras palabras, declarar variables):

 sampler2D _DissolveTexture; half _Amount; 

Además, no lo olvides. que sus nombres deben coincidir con los nombres en la sección Propiedades.

Función


Comenzamos la función de Superficie o Fragmento muestreando la textura de la disolución y obteniendo el valor del rojo . PD Nuestra textura se almacena en escala de grises , es decir, sus valores de R , G y B son iguales, y puede elegir cualquiera de ellos . Por ejemplo, el blanco es (1,1,1) , el negro es (0,0,0) .

En mi ejemplo, uso un sombreador de superficie:

 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. //[...] } 

¡Y eso es todo! ¡Podemos aplicar este proceso a cualquier sombreador existente y convertirlo en un sombreador de disolución !

Aquí está el Surface Shader estándar del motor Unity, convertido en un shader de disolución de dos 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


Si necesitamos crear este efecto usando el Gráfico Unity Shader , entonces debemos usar el valor AlphaClipThreshold (que funciona de manera diferente al clip (x) de HLSL). En este ejemplo, creé un sombreador PBR.

La función AlphaClipThreshold le indica al sombreador que descarte todos los píxeles cuyo valor sea menor que su valor Alpha . Por ejemplo, si es 0.3f , y nuestro valor alfa es 0.2f , entonces el sombreador no representará este píxel. La función AlphaClipThreshold se puede encontrar en la documentación de Unity : PBR Master Node y Unlit Master Node .

Aquí está nuestro sombreador terminado:


Muestramos la textura de disolución y obtenemos el valor de rojo , y luego lo agregamos al valor de Cantidad (que es la propiedad que agregué para indicar el proceso de ejecución general, el valor 1 significa disolución completa) y lo conectamos a AlphaClipThreshold . Hecho

Si desea aplicarlo a cualquier sombreador existente, simplemente copie las conexiones de nodo a AlphaClipThreshold (¡no se pierda las propiedades necesarias!). ¡También puedes hacerlo a dos caras y obtener un resultado aún más hermoso!



Shader de disolución de contorno


¿Y si intentas agregarle contornos ? ¡Hagámoslo!


No podemos trabajar con píxeles ya disueltos, porque después de soltarlos desaparecen para siempre . En cambio, ¡podemos trabajar con valores "casi disueltos"!

En HLSL, esto es muy simple, solo agregue algunas líneas de código después de calcular el clip :

 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. //[...] } 

Hecho

Cuando se trabaja con Shader Graph, la lógica es ligeramente diferente. Aquí está el sombreador terminado:




Podemos crear efectos muy geniales con un simple sombreador de disolución ; ¡Puedes experimentar con diferentes texturas y valores , así como crear algo más!

Parte 2: sombreador de exploración mundial


Un sombreador de exploración mundial (o un " sombreador de disolución mundial , o disolución global ") nos permite ocultar igualmente todos los objetos de la escena en función de su distancia a la posición; ahora crearemos dicho sombreador en el Gráfico de sombreador de unidad y lo escribiremos en HLSL .

Aquí hay un ejemplo de lo que crearemos:




Distancia como parámetro


Supongamos que necesitamos disolver un objeto en una escena si está demasiado lejos del jugador . Ya hemos anunciado el parámetro _Amount , que controla la desaparición / disolución del objeto, por lo que debemos reemplazarlo con la distancia entre el objeto y el jugador.

Para hacer esto, necesitamos tomar las posiciones de Jugador y Objeto .

Posición del jugador


El proceso será similar tanto para Unity Shader Graph como para HLSL : necesitamos transferir la posición del jugador en el 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


Posición del objeto y distancia al mismo


Usando el gráfico de sombreador, podemos usar los nodos de posición y distancia.



PD: para que este sistema funcione con Sprite Renderers, debe agregar la propiedad _MainTex, probarla y conectarla a albedo. Puedes leer mi tutorial anterior sobre sombreadores difusos de Sprites (que usa el gráfico de sombreadores).



HLSL (superficie)


Posición del objeto


En HLSL, podemos agregar la variable worldPos a nuestra estructura de entrada para obtener las posiciones de los vértices de los objetos.

 struct Input { float2 uv_MainTex; float3 worldPos; //add this and Unity will set it automatically }; 

En la página de documentación de Unity, puede averiguar qué otros parámetros incorporados está permitido agregar a la estructura de entrada.

Aplicar distancia


Necesitamos usar la distancia entre los objetos y el jugador como la cantidad de disolución. Para hacer esto, puede usar la función de distancia integrada ( documentación de 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 puede ver, los objetos se disuelven "localmente", no obtuvimos un efecto homogéneo, porque obtenemos el "valor de disolución" de la textura muestreada usando la radiación UV de cada objeto. (En 2D, esto es menos notable).



3D LocalUV Dissolve Shader en 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 en 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" } 

PD Para crear el último sombreador, copié el sombreador Unity Sprites-Diffuse estándar y agregué la parte "disolver" descrita anteriormente en esta parte del artículo. Todos los sombreadores estándar se pueden encontrar aquí .



Haciendo el efecto homogéneo


Para que el efecto sea homogéneo, podemos usar coordenadas globales (posición en el mundo) como las coordenadas UV de la textura de disolución. También es importante establecer Wrap = Repeat en los parámetros de textura de disolución para que podamos repetir la textura sin darnos cuenta (¡asegúrese de que la textura sea perfecta y se repita bien!)


HLSL (superficie)


 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 es el resultado: podemos notar que la textura de la disolución ahora es uniforme para todo el mundo.

Este sombreador ya es ideal para juegos en 2D , pero para objetos en 3D necesita ser mejorado .

El problema con los objetos 3D.



Como puede ver, el sombreador no funciona para caras "no verticales" y distorsiona enormemente la textura. Por eso resulta. que las coordenadas UV necesitan el valor float2, y si pasamos worldPos, entonces recibe solo X e Y.

Si solucionamos este problema aplicando cálculos para mostrar la textura en todas las caras, llegaremos a un nuevo problema: al oscurecer, los objetos se cruzarán entre sí y no permanecerán homogéneos.

Será difícil para los principiantes comprender la solución: es necesario deshacerse de la textura, generar ruido tridimensional en el mundo y obtener el "valor de disolución". En esta publicación no explicaré la generación de ruido 3D, ¡pero puedes encontrar un montón de funciones listas para usar!

Aquí hay un ejemplo de sombreador de ruido: https://github.com/keijiro/NoiseShader . También puede aprender cómo generar ruido aquí: https://thebookofshaders.com/11/ y aquí: https://catlikecoding.com/unity/tutorials/noise/

Estableceré mi función de superficie de esta manera (suponiendo que ya haya escrito la parte de ruido):

 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; } 

Un breve recordatorio de HLSL: antes de usar / llamar a una función, debe escribirse / declararse.

PD: si desea crear un sombreador utilizando el Gráfico de sombreador de Unity, debe usar Nodos personalizados (y generar ruido escribiendo código HLSL en ellos). Hablaré sobre Nodos personalizados en un tutorial futuro.

Resultado (3D)





Agregar contornos


Para agregar contornos, debe repetir el proceso de la parte anterior del tutorial.




Efecto invertido


¿Y si queremos revertir este efecto? (Los objetos deberían desaparecer si un jugador está cerca)

Es suficiente para nosotros cambiar una línea:

 float dist = _Radius - distance(_PlayerPos, IN.worldPos); 

(El mismo proceso se aplica a Shader Graph).

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


All Articles