How To Create A Fancy Portal Effect In Unity

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.

Advertisement

7 thoughts on “How To Create A Fancy Portal Effect In Unity”

  1. 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.

    1. 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 πŸ™‚

  2. 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!

      1. 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.

      2. 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.

      3. 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.

Leave a Reply to Denis Rizov Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s