LogoLogo
  • Home
  • Projects
  • About
  • Contact

Dissolving Particles with Custom Vertex Streams

Devon O. · February 03, 2019 · Shaders, Unity3D · 0 comments
12

Here’s a quick little tip/idea to demonstrate using custom vertex streams and custom data in your Unity3D Particle Systems to create some pretty interesting VFX such as dissolving particles. Doing some googling, there didn’t seem to be a lot of information out there about custom data in particle systems, so hopefully this will help out a little.

From a high level, what this entails is creating a particle system that will automatically send some data to the shader of the particle material via the shader’s vertex stream. So, as you might guess, the first thing we’ll need is a shader that will be able to handle the data sent. Even sooner, though, let’s get a couple textures together. For the particle we can just use a basic particle png that has a radial gradient that goes from white in the center to full full alpha along the outer edge. We’ll also need a dissolve texture, which in this case can just be some quick perlin noise. You can make both in Photoshop or Gimp in about 5 minutes or just nab them here if you’d like to just try this out quickly.

Of course, since the effect we’ll be looking at here is a dissolve effect, we’ll need a dissolve shader. Let’s start by making a very basic one that doesn’t work with the particle system, just to see what I’m talking about by a dissolve shader.

You can check out the shader below. I won’t go into a lot of detail, but it’s an unlit shader that allows transparency and uses an alpha blend mode. It has two textures – the main texture and the dissolve texture. The real magic happens with the step() operation. In CG, step() is essentially a boolean operation. Which is to say, step(x, y) will return either a 1 if y>=x or 0, otherwise. So, in this basic dissolve shader, we are multiplying the output alpha by either 1 or 0 depending on a comparison between the red component of our dissolve texture and a _Cutoff value.

Basic Dissolve Shader
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
*    Copyright (c) 2019 Devon O. Wolfgang
*
*    Permission is hereby granted, free of charge, to any person obtaining a copy
*    of this software and associated documentation files (the "Software"), to deal
*    in the Software without restriction, including without limitation the rights
*    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*    copies of the Software, and to permit persons to whom the Software is
*    furnished to do so, subject to the following conditions:
*
*    The above copyright notice and this permission notice shall be included in
*    all copies or substantial portions of the Software.
*
*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*    THE SOFTWARE.
*/
 
Shader "Dissolve/BasicDissolve"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DissolveTex ("Dissolve Texture", 2D) = "white" {}
        _Cutoff ("Cutoff", Range(0, 1)) = 0
    }
    SubShader
    {
        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off Lighting Off ZWrite Off
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
 
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
 
            sampler2D _DissolveTex;
            fixed _Cutoff;
 
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 dis = tex2D(_DissolveTex, i.uv);
                col.w *= step(_Cutoff, dis.x);
                return col;
            }
            ENDCG
        }
    }
}

If you attach this shader to a material, add the basic particle image as the main texture and the dissolve .png image as the dissolve texture, then slide the Cutoff range slider back and forth, you’ll see something like this in the material preview:

 

That then is the effect we’ll be seeing in our particles. For the shader used by the particle system though, we’ll have to make a few changes. Remove the _Cutoff property from the shader and, for now, just comment out the line that uses it in the fragment shader.

Because we are reading the vertex stream from the particle system and the particle system will supply the particle color, we’ll add a fixed4 color : COLOR; definition to both the appdata and v2f structs. Now create a particle system using the material that is using that shader. I just made a simple one using a cone shape with a small radius and angle, an emission of only 5 and a start rotation that ranges between the 2 constants -180 and 180. Here’s the important part though – under the Custom Data module set Custom 1 to be a Vector type with 1 as the number of components and set its value as a curve that slopes upwards from 0 to 1. This is now our cutoff value in the dissolve shader. Finally, in the Renderer module of the particle system, tick the box to activate Custom Vertex Streams. If you take a look at the info box you can see the vertex stream data being sent to the shader. Our shader isn’t using the normal data, so, if you’d like, you can highlight that bit and use the minus button to remove it (not necessary, but it keeps it a bit cleaner). The important thing to note though is that Custom1.x (the custom data we just set a second ago) is being sent to TEXCOORD0.z. If we go back and look at our appdata struct we’ll see that TEXCOORD0 is our uv property and is only a float2 data type. If we want to have a z component, then, let’s set that to a float3 data type and rename it to something more meaningful, like uvAndCutoff. In the v2f struct, we’ll leave uv as a float2 property but add a new float property named cutoff. The line we commented out earlier can be added back, but the _Cutoff property can now be replaced with the i.cutoff property.

Now in the vertex shader we’ll set the v2f properties which will get passed to the fragment shader from our appdata properties from the vertex stream as usual. The final shader used for the particle looks like this:

Particle Dissolve Shader
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
*    Copyright (c) 2019 Devon O. Wolfgang
*
*    Permission is hereby granted, free of charge, to any person obtaining a copy
*    of this software and associated documentation files (the "Software"), to deal
*    in the Software without restriction, including without limitation the rights
*    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*    copies of the Software, and to permit persons to whom the Software is
*    furnished to do so, subject to the following conditions:
*
*    The above copyright notice and this permission notice shall be included in
*    all copies or substantial portions of the Software.
*
*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*    THE SOFTWARE.
*/
 
Shader "Dissolve/DissolveParticle"
{
 
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DissolveTex ("Dissolve Texture", 2D) = "white" {}
    }
 
    SubShader
    {
        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off Lighting Off ZWrite Off
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
 
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float3 uvAndCutoff : TEXCOORD0;
                fixed4 color : COLOR;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float cutoff : TEXCOORD1;
            };
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
 
            sampler2D _DissolveTex;
 
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uvAndCutoff.xy, _MainTex);
                o.color = v.color;
                o.cutoff = v.uvAndCutoff.z;
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * i.color;
                fixed4 dis = tex2D(_DissolveTex, i.uv);
                col.w *= step(i.cutoff, dis.x);
                return col;
            }
            ENDCG
        }
    }
    FallBack "Mobile/Particles/Alpha Blended"
}

Assuming all went well, you’ll wind up with a particle system that now looks something like this:

So what could you do with something like that? Well, explosions, rocket streams, sparky fires, and even liquids seem like prime candidates. Really though, the cool part is just being able to send particle system data to shaders which really opens a huge world of possibilities in the vis fx realm. Hopefully this can at least provide an idea as a starting point.

  Facebook   Pinterest   Twitter   Google+
particles
  • Blist
    February 24, 2008 · 0 comments
    1476
    3
    Read more
  • Update to Website Generation Tool
    May 03, 2009 · 7 comments
    2241
    2
    Read more
  • Magnify – a jQuery Plugin
    December 11, 2011 · 5 comments
    2545
    9
    Read more

Leave a Comment! Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Devon O. Wolfgang

AIR | Unity3D | AR/VR

Unity Certified Developer

Technical Reviewer of “The Essential Guide to Flash CS4 AIR Development” and “Starling Game Development Essentials”

Reviewer of “The Starling Handbook”

Unity Engineer at Touch Press.

Categories
  • Actionscript (95)
  • AIR (16)
  • Flash (99)
  • Games (7)
  • Liberty (13)
  • Life (53)
  • Shaders (20)
  • Unity3D (21)
Recent Comments
  • MainDepth on Unity Ripple or Shock Wave Effect
  • Devon O. on Unity Ripple or Shock Wave Effect
  • Feral_Pug on Unity Ripple or Shock Wave Effect
  • bavvireal on Unity3D Endless Runner Part I – Curved Worlds
  • Danielius Vargonas on Custom Post Processing with the LWRP
Archives
  • December 2020 (1)
  • December 2019 (1)
  • September 2019 (1)
  • February 2019 (2)
  • December 2018 (1)
  • July 2018 (1)
  • June 2018 (1)
  • May 2018 (2)
  • January 2018 (1)
  • December 2017 (2)
  • October 2017 (1)
  • September 2017 (2)
  • January 2017 (1)
  • July 2016 (1)
  • December 2015 (2)
  • March 2015 (1)
  • September 2014 (1)
  • January 2014 (1)
  • August 2013 (1)
  • July 2013 (1)
  • May 2013 (1)
  • March 2013 (2)
  • December 2012 (1)
  • November 2012 (1)
  • September 2012 (3)
  • June 2012 (2)
  • May 2012 (1)
  • April 2012 (1)
  • December 2011 (2)
  • October 2011 (3)
  • September 2011 (1)
  • August 2011 (1)
  • July 2011 (1)
  • May 2011 (2)
  • April 2011 (2)
  • March 2011 (1)
  • February 2011 (1)
  • January 2011 (2)
  • December 2010 (3)
  • October 2010 (5)
  • September 2010 (1)
  • July 2010 (2)
  • May 2010 (5)
  • April 2010 (2)
  • March 2010 (7)
  • February 2010 (5)
  • January 2010 (5)
  • December 2009 (3)
  • November 2009 (1)
  • October 2009 (5)
  • September 2009 (5)
  • August 2009 (1)
  • July 2009 (1)
  • June 2009 (2)
  • May 2009 (6)
  • April 2009 (4)
  • March 2009 (2)
  • February 2009 (4)
  • January 2009 (1)
  • December 2008 (5)
  • November 2008 (2)
  • September 2008 (1)
  • August 2008 (6)
  • July 2008 (6)
  • June 2008 (9)
  • May 2008 (4)
  • April 2008 (3)
  • March 2008 (4)
  • February 2008 (9)
  • January 2008 (7)
  • December 2007 (6)
Copyright © 2021 Devon O. Wolfgang