My take on shaders: Teleportation dissolve

While my jaw dropped from browsing Taizyd Korambayil‘s website, my eye got specifically stuck on one effect: the teleportation shader made in UE4. And as I was reading the breakdown I was like “I’ve done this part, I’ve done that part, I’ve certainly done that part” and I was then at the bottom of the page, which made me realize that I could probably recreate that effect in Unity. Also, coincidentally enough, that effect could be an extension of the directional dissolve shader I (somewhat) recently posted. With a bunch of trial and error, I made something similar to the effect and, as I usually do, I just tweeted about it and then pushed it to the queue of possible blog posts. And now we’re here, with yet another dissolve shader!

As I started working on this shader using the directional dissolve as my basis, I suggest you checked that out first, as most of the post will refer to that shader.

Here’s the code for the effect:

Shader "Custom/TeleportationDissolve" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Normal("Normal", 2D) = "bump" {}
		_DissolveAmount("Dissolve amount", Range(-3,3)) = 0
		_Direction("Direction", vector) = (0, 1, 0, 0)
		[HDR]_Emission("Emission", Color) = (1,1,1,1)
		_NoiseSize("Noise size", float ) = 1
	}
	SubShader {
		Tags { "RenderType"="Opaque" "DisableBatching" = "True"}
		LOD 200
		Cull off

		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard addshadow vertex:vert

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;
		sampler2D _Normal;

		struct Input {
			float2 uv_MainTex;
			float2 uv_Normal;
			float3 worldPosAdj;
		};

		fixed4 _Color;
		float _DissolveAmount;
		fixed4 _Emission;
		float3 _Direction;
		float _NoiseSize;

		float random (float2 input) { 
            return frac(sin(dot(input, float2(12.9898,78.233)))* 43758.5453123);
        }

		void vert (inout appdata_full v, out Input o) {
        	UNITY_INITIALIZE_OUTPUT(Input,o);
            o.worldPosAdj =  mul (unity_ObjectToWorld, v.vertex.xyz);
			half test = ((dot(o.worldPosAdj, float3(0, -1, 0)) + 1) / 2) - _DissolveAmount;
			float squaresStep = step(test, random(floor(o.uv_MainTex * _NoiseSize) * _DissolveAmount));
			v.vertex.xyz += _Direction * squaresStep * random(v.vertex.xy) * abs(test);
      	}

		// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
		// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
		// #pragma instancing_options assumeuniformscaling
		UNITY_INSTANCING_BUFFER_START(Props)
			// put more per-instance properties here
		UNITY_INSTANCING_BUFFER_END(Props)


		void surf (Input IN, inout SurfaceOutputStandard o) {
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			//Clipping
			half test = ((dot(IN.worldPosAdj, float3(0, 1, 0)) + 1) / 2) - _DissolveAmount;
			float squares = random(floor(IN.uv_MainTex * _NoiseSize));
			float squaresStep = step(squares * _DissolveAmount, test);
			clip(squaresStep - 0.01);
			//Emission noise
			half emissionRing = step(squares, _DissolveAmount);
			o.Normal = UnpackNormal(tex2D(_Normal, IN.uv_Normal));
			o.Albedo = c.rgb;
			o.Emission = _Emission * emissionRing;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

First of all, this effect uses the “square” noise emission in a specific way, so the emission threshold property that existed in the directional dissolve shader is not needed here. Other than that, the properties remain pretty much the same.

The changes begin in the vertex shader, where some code is added in order to displace the vertices towards the teleportation direction. In line 45 I calculate the dot product of the “adjusted” world position of the vertex (discussed in the directional dissolve shader) with the world downwards direction to determine whether that vertex should be displaced or not. The larger the “_DissolveAmount” value is, the further the vertex displacement moves “upwards”. In line 46 I check if the generated “square” noise is larger than the “test” value, in which case the vertex should be displaced.

In line 47 I displace the vertices towards the given direction according to the factor calculated in line 46 and the absolute value of the test calculated in line 45. I multiply that with a random value based on the position of the vertex, so that I don’t displace every vertex by the same amount and it just seems like the model is floating away. The random value is what gives that cool spiky effect.

Moving on the surface shader, in line 61 I do exactly the same calculation as the one in line 45, however this time I’m using the world upwards direction instead of downwards. This is because the “test” here is used to clip the model’s pixels from the bottom of the model upwards, while in the vertex displacement it is used to offset the vertices of the model starting from the top and moving to the bottom (despite the offset being upwards). In line 62 I calculate the “square” noise as per usual, and in line 63 I do the same “step” calculation as in line 46, but reversed this time. It doesn’t really matter that it’s reversed, it’s just so that in line 64 I use “squareStep” instead of “(1 – squareStep)”. Speaking of line 64, this is where I actually remove the pixels in case “squareStep” is 0.

Finally, in line 66 I calculate the emission value. Here I just cover the model with emissive squares as the “_DissolveAmount” value gets bigger, which also has the effect of clipping the model and also displacing its vertices.

Conclusion

Yeah, this tutorial was kinda short and maybe weird, but the thing is that I mostly used trial and error to get the directional dissolve shader behave like the teleportation effect shown by Taizyd. And it might sound lazy, but it’s what I’m suggesting you do too. I can’t say for sure that’s how it works for other programmers, but for me at least, most new effects are a bunch of trial and error and tweaking values and lines of code until it works (even if I’m not sure how and why). I very rarely think of something which works out of the box. To sum up, just keep tweaking stuff and you’ll be fine.

See you in the next one!




Disclaimer

The code in the shader tutorials is under the CC0 license, so you can freely use it in your commercial or non-commercial projects without the need for any attribution/credits.

These posts will never go behind a paywall. But if you really really super enjoyed this post, consider buying me a coffee, or becoming my patron to get exciting benefits!

Become a Patron!

Or don't, I'm not the boss of you.

Comments

  1. Fran

    Brilliant post & shader, as always.
    I’m hoping someday you do some background shaders that bring life to loading, menu and game scenes.

    Thanks for this blogs,
    Fran

Comments are closed.