Shaders of dissolution and exploration of the world

Original author: Federico Febucci
  • Transfer

Part 1: Dissolve Shader


The dissolution shader returns a beautiful effect, and it is also easy to create and understand; today we will make it in the Unity Shader Graph , and also write to HLSL .

Here is an example of what we will create:



How it works


To create a dissolve shader, we’ll have to work with the AlphaClipThreshold value in the Shader Graph shader or use the HLSL function called clip .

In fact, we will order the shader not to render a pixel based on the texture and the transmitted value . We need to know the following: the white parts dissolve faster .

We will use this texture:


You can create your own - straight lines, triangles, whatever you like! Just remember that the white parts dissolve faster .

I created this texture in Photoshop using the “Clouds” filter.

Even if you are only interested in Shader Graph and you know nothing about HLSL, I still recommend reading this part, because it is useful to understand how Unity Shader Graph works inside.



HLSL


In HLSL, we use the clip (x) function . The clip (x) function discards all pixels with a value less than zero . Therefore, if we call clip (-1) , we will be sure that the shader will never render this pixel. More information about the clip can be found in Microsoft Docs .

Properties


The shader needs two properties, Dissolve Texture and Amount (which will indicate the overall process of execution). As is the case with other properties and variables, you can call them as you please.

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

Do not forget to add the following after CGPROGRAM SubShader (in other words, declare variables):

sampler2D _DissolveTexture;
half _Amount;

Also, don't forget. that their names must match the names in the Properties section.

Function


We start the Surface or Fragment function by sampling the dissolve texture and get the red value . PS Our texture is preserved in grayscale , that is, its values R , G and B are equal, and you can choose any of them . For example, white is (1,1,1) , black is (0,0,0) .

In my example, I use a surface shader:

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

And that's it! We can apply this process to any available shader and turn it into a dissolution shader !

Here is the standard Surface Shader of the Unity engine, turned into a two-sided dissolution shader:

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 (InputIN, 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 = c.a;
 }
 ENDCG
 }
 FallBack "Diffuse"
}



Shader graph


If we need to create this effect using the Unity Shader Graph , then we need to use the AlphaClipThreshold value (which works differently than the clip (x) from HLSL). In this example, I created a PBR shader.

The AlphaClipThreshold function tells the shader to discard all pixels whose value is less than its Alpha value . For example, if it is 0.3f , and our alpha value is 0.2f , then the shader will not render this pixel. You can read about the AlphaClipThreshold function in the Unity documentation : PBR Master Node and Unlit Master Node..

Here is our ready shader:


We sample the dissolution texture and get the red value , then add it to the Amount value (which is a property that I added to indicate the overall process, value 1 means complete dissolution) and connect it to AlphaClipThreshold . Done!

If you want to apply it to any existing shader, just copy the node connections in AlphaClipThreshold (do not miss the necessary properties!). You can also make it two-way and get an even more beautiful result!



Dissolution shader with contours


And if you try to add contours to it ? Let's do it!


We cannot work with the already dissolved pixels, because after dropping them they disappear forever . Instead, we can work with “almost dissolving” values!

In HLSL, this is very simple, just add a few lines of code after calculating the clip :

void surf (Input IN, inout SurfaceOutputStandard o) {
 //[...]//After our clip calculationsif (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. //[...]
 }

Done!

When working with Shader Graph, the logic is slightly different. Here is the finished shader:




We can create very cool effects with a simple dissolution shader ; You can experiment with different textures and values , as well as come up with something else!

Part 2: World Explorer Shader


The world exploration shader (or the world dissolution shader , or global dissolution ) allows us to hide all the objects of the scene equally based on their distance from the position; now we will create such a shader in the Unity Shader Graph and write it to HLSL .

Here is an example we will create:




Distance as parameter


Suppose we need to dissolve an object in the scene if it is too far from the player . We have already announced the _Amount parameter , which controls the process of disappearing / dissolving an object, so we need to replace it with the distance between the object and the player.

For this we need to take the position of Player and Object .

Player position


The process will be the same for Unity Shader Graph , and for HLSL : we need to transfer the player's position in the code.

privatevoidUpdate()
{
    //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
}



Shader graph


The position of the object and the distance to it


With the Shader Graph, we can use the Position and Distance nodes.



PS In order for this system to work with Sprite Renderers, you need to add the _MainTex property, sample it and connect it with albedo. You can read my previous tutorial Sprites diffuse shader (which uses shader graph).



HLSL (surface)


Object position


In HLSL, we can add the worldPos variable to our Input structure to get the vertex positions of the objects.

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

On the Unity documentation page, you can find out what other built-in parameters you can add to the Input structure.

Apply distance


We need to use the distance between the objects and the player as the dilution value. For this, you can use the built-in distance function ( Microsoft documentation ).

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

Result (3D)



Result (2D)



As you can see, the objects dissolve “locally”, we did not get a uniform effect, because we get the “dissolution value” from the texture sampled with the UV of each object. (In 2D this is less noticeable).



3D LocalUV Dissolve Shader on 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 startto 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 (InputIN, 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 = c.a;
 }
 ENDCG
 }
 FallBack "Diffuse"
}

Sprites Diffuse - LocalUV Dissolve Shader on 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 startto 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, outInput 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(InputIN, 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 * c.a;
 o.Alpha = c.a;
 }
 ENDCG
 }
 Fallback "Transparent/VertexLit"
}

PS To create the last shader, I copied the standard Unity Sprites-Diffuse shader and added the “dissolve” part described earlier in this part of the article. All standard shaders can be found here .



Making the effect uniform


To make the effect homogeneous, we can use global coordinates (world position) as the UV coordinates of the dissolution texture. It is also important to set Wrap = Repeat in the dissolution texture parameters so that we can repeat the texture without noticing it (make sure the texture is seamless and repeats well!)


HLSL (surface)


half dissolve_value = tex2D(_DissolveTexture, IN.worldPos / 4).x; //I modified the worldPos to reduce the texture size

Shader graph



Result (2D)



This is the result: we can see that the dissolution texture is now uniform for the whole world.

This shader is already ideal for 2D games , but for 3D objects it needs to be improved .

The problem with 3D-objects



As you can see, the shader does not work for “non-vertical” edges, and it distorts the texture strongly. So it turns out because. that UV coordinates need a value of float2, and if we pass worldPos, then it gets only X and Y.

If we fix this problem, apply calculations to display the texture on all faces, then we will come to a new problem: when darkened, objects will intersect friend and not remain homogeneous.

The solution will be difficult to understand for beginners: you need to get rid of the texture, generate three-dimensional noise in the world and get the “value of dissolution” from it. In this post I will not explain the generation of 3D noise, but you can find a bunch of ready-to-use functions!

Here is an example of a noise shader: https://github.com/keijiro/NoiseShader. You can also learn how to generate noise here: https://thebookofshaders.com/11/ and here: https://catlikecoding.com/unity/tutorials/noise/

I will set my surface function this way (assuming you already wrote a part with noise):

void surf (InputIN, inout SurfaceOutputStandard o) {
 float dist = distance(_PlayerPos, IN.worldPos);
 //"abs" because you have to make sure that the noise isbetween the range [0,1]
 //you can remove "abs" if your noise functionreturns a valuebetween [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 = c.a;
}

A brief reminder of HLSL: Before using / calling a function, it must be written / declared.

PS If you want to create a shader using Unity Shader Graph, then you need to use Custom Nodes (and generate noise by writing the code in HLSL). I will tell about Custom Nodes in the future tutorial.

Result (3D)





Adding contours


To add contours, you need to repeat the process from the previous part of the tutorial.




Inverted effect


And if we want to reverse this effect? (Objects should disappear if there is a player nearby)

It is enough for us to change one line:

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

(The same process applies to Shader Graph).


Also popular now: