Expectations
Recently I created a portal particle system in Unity for a game that I am developing. I am really pleased with the result and I want to share with you how to create one yourselves. Here is the final effect that we will be making today.
What do we need
We need four textures
T_Portalbackground
T_PortalCircle
T_PortalDust
T_PortalNoise
With the first 3 we are going to create the particle system itself, which consists of 5 sub-particle systems. With the noise texture (the one with RGB colors) we are going to distort each sub-system to achieve the final result.
We are also going to need 2 custom shaders – Additive and Multiplicative Particle Distortion Shaders. They are very cheap and optimized especially for mobile devices. The game I am working on is a mobile game after all.
Here is the Additive Shader
Shader "Custom/Mobile/Particles/Additive Distortion" { Properties{ _MainTex("Main Texture", 2D) = "white" {} _NoiseTex("Noise Texture", 2D) = "white" {} _IntensityAndScrolling("Intensity (XY), Scrolling (ZW)", Vector) = (0.1,0.1,0.1,0.1) } SubShader{ Tags { "IgnoreProjector" = "True" "Queue" = "Transparent" "RenderType" = "Transparent" } Pass { Blend One One ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex; uniform sampler2D _NoiseTex; uniform float4 _MainTex_ST; uniform float4 _NoiseTex_ST; uniform float4 _IntensityAndScrolling; struct VertexInput { float4 vertex : POSITION; float2 texcoord0 : TEXCOORD0; float2 texcoord1 : TEXCOORD1; float4 vertexColor : COLOR; }; struct VertexOutput { float4 pos : SV_POSITION; float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 vertexColor : COLOR; }; VertexOutput vert(VertexInput v) { VertexOutput o; o.uv0 = TRANSFORM_TEX(v.texcoord0, _MainTex); o.uv1 = TRANSFORM_TEX(v.texcoord1, _NoiseTex); o.uv1 += _Time.yy * _IntensityAndScrolling.zw; o.vertexColor = v.vertexColor; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); return o; } float4 frag(VertexOutput i) : COLOR { float4 noiseTex = tex2D(_NoiseTex, i.uv1); float2 offset = (noiseTex.rg * 2 - 1) * _IntensityAndScrolling.rg; float2 uvNoise = i.uv0 + offset; float4 mainTex = tex2D(_MainTex, uvNoise); float3 emissive = (mainTex.rgb * i.vertexColor.rgb) * (mainTex.a * i.vertexColor.a); return fixed4(emissive, 1); } ENDCG } } FallBack "Mobile/Particles/Additive" }
And the Multiplicative Shader
Shader "Custom/Mobile/Particles/Multiply Distortion" { Properties{ _MainTex("Main Texture", 2D) = "white" {} _NoiseTex("Noise Texture", 2D) = "white" {} _IntensityAndScrolling("Intensity (XY), Scrolling (ZW)", Vector) = (0.1,0.1,0.1,0.1) } SubShader{ Tags { "IgnoreProjector" = "True" "Queue" = "Transparent" "RenderType" = "Transparent" } Pass { Blend DstColor Zero ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex; uniform sampler2D _NoiseTex; uniform float4 _MainTex_ST; uniform float4 _NoiseTex_ST; uniform float4 _IntensityAndScrolling; struct VertexInput { float4 vertex : POSITION; float2 texcoord0 : TEXCOORD0; float2 texcoord1 : TEXCOORD1; float4 vertexColor : COLOR; }; struct VertexOutput { float4 pos : SV_POSITION; float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 vertexColor : COLOR; }; VertexOutput vert(VertexInput v) { VertexOutput o; o.uv0 = TRANSFORM_TEX(v.texcoord0, _MainTex); o.uv1 = TRANSFORM_TEX(v.texcoord1, _NoiseTex); o.uv1 += _Time.yy * _IntensityAndScrolling.zw; o.vertexColor = v.vertexColor; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); return o; } float4 frag(VertexOutput i) : COLOR { float4 noiseTex = tex2D(_NoiseTex, i.uv1); float2 offset = (noiseTex.rg * 2 - 1) * _IntensityAndScrolling.rg; float2 uvNoise = i.uv0 + offset; float4 mainTex = tex2D(_MainTex, uvNoise); float3 emissive = lerp(float3(1,1,1), mainTex.rgb * i.vertexColor.rgb, mainTex.a * i.vertexColor.a); return fixed4(emissive, 1); } ENDCG } } FallBack "Mobile/Particles/Multiply" }
As you can see, they are almost exactly the same. The difference is only in the blending and the calculation of the emissive color in the fragment function.
Step By Step
First we are going to make the particle system with the built-in mobile additive and multiplicative shaders, and then we are going to add distortion with our custom shaders. I want to do this, so you can see how big the difference is.
Background
Using an additive shader we start by emitting a single particle with the first texture. The exact color is “FFFFFF4B“. It should look like this.
Nice start huh?
Black Hole
Yeah, you hear right. We are going to create a black hole.
By using a multiplicative shader we are going to emit a single smaller black circle over the white one. The exact color is “000000AF“. Something like this.
Rotating Frame
Next using an additive shader and the second texture we emit 3 circles – each with different start rotation and angular velocity. The exact color is “FFFFFF80“”. It looks like this.
Pulsing Circles
Again using an additive shader and the second texture we start emitting pulsing circles from the center towards the frame. Each circle has different start rotation and no angular velocity. The rate is 3 particles per 2 seconds. Again the color of each circle is “FFFFFF80“.
Small Sucked Particles
Using an additive shader and the third texture we add small particles that are sucked into the portal. Again the color is “FFFFFF80“.
Adding Distortion
The only thing that’s left is adding distortion to each sub-particle system with our custom additive and multiplicative shaders and the noise texture. The only thing we don’t distort are the sucked particles.
Hi, very interesting “how to”
I have a doubt: waht do you mean with “Using an additive shader we start by emitting a single particle with the first texture.”?
I’m missing lots of parameters to correctly setup the particle system.
Create a material and use the custom additive distortion shader that I provided. On the Renderer Property of the particle system link the material.
As for the emission – go to the Emission Property of the particle system and create a single burst (Time: 0, Min: 1, Max: 1).
Set the Start Lifetime to something very big so that the burst never dies.
The idea of the post was to give a guide on how to create a similar particle system. If I go into details the post will become huge. If you have more questions I’d gladly help π
Hey, great tutorial!
I’m having trouble with getting the distortion effect to loop with the particles. Right now it will run through the distortion once, and then the particles will just be normal circles after that, no distortion. Is there something I’m missing?
Thanks!
That’s pretty strange mate. The distortion shaders are distorted depending on the current time.
There is no reason for them to stop working.
Hmm, yeah, real strange. Everything looks pretty similar to your example except for the distortion just stops for some reason. I’ve tried it with other textures and materials, same thing. Super weird.
Hey, just a quick update on this. The issue seems to be related to the noise texture? I swapped out the one from this tutorial with a different one and it seems to be repeating as intended. Weird.
Then the problem is related to the texture settings.
In the Wrap Mode you have to change it from Clamp to Repeat.
This way when the shader offsets the UV coordinates, the texture won’t clamp but will repeat itself.