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;
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 (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" }
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) {
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 "
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 of what 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.
private void Update() {
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.
struct Input { float2 uv_MainTex; float3 worldPos;
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);
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 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 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 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 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;
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 receives only X and Y.
If this problem is eliminated by applying calculations to display the texture on all faces, we will come to a new problem: when darkened, objects will intersect with each other, and will 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 define my surface function as follows (assuming that you have already written the part with noise):
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; }
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 a player is nearby)
We just need to change one line:
float dist = _Radius - distance(_PlayerPos, IN.worldPos);
(The same process applies to Shader Graph).