[shader] game special effects - simple energy shield

Posted by smoothrider on Sat, 25 Dec 2021 03:29:35 +0100

This article refers to:
[1]Unity shader shield effect, author: QQ_ sixteen million nine hundred and eighty-two thousand three hundred and twenty-three

[2]UnityShader - energy mask of screen space (simulating the energy mask of watchman Winston), author: Porco_

Have you ever played crash 3? This is the shield in the game

Full of sense of technology and extremely comfortable visual effects, this is still a domestic mobile game for 16 years. MIHA, you cow!
Unfortunately, if you want to copy such a shield effect, you need not only to model yourself, but also UV relocation. For a guy like me who just wants to care about how shader writes, it looks like his skull hurts.

But the effect is so delicious. I'll imitate the source code of article [2] and change it blindly, and then match it with the idea of [1]. Hey, change a fairly passable energy mask effect.

Final effect (with bloom post-processing):

OK, from now on, write an ordinary transparent shader:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/EnergyShield"
{
    Properties
    {
        _MainTex ("Main post map", 2D) = "white" {}
		_MaskTex ("Mask Mapping", 2D) = "white"{}
		_BaseColor ("Color 1", Color) = (1,1,1,1)
		_HighLightColor ("Color 2",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector" = "true"}
        LOD 100

		CGINCLUDE
		
		#include "UnityCG.cginc"
		struct a2v
		{
			float4 vertex : POSITION;
			float3 normal : NORMAL;
			float2 uv : TEXCOORD0;
		};

		struct v2f
		{
			float4 pos : SV_POSITION;
			float2 uv : TEXCOORD0;
		};

		sampler2D _MainTex;
		float4 _MainTex_ST;
		sampler2D _MaskTex;
		float4 _MaskTex_ST;
		fixed4 _Color;
		fixed4 _HighLightColor;

		v2f vert(a2v v)
		{
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			return o;
		}

		fixed4 frag(v2f i) : SV_TARGET0
		{
			return lerp(_Color,_HighLightColor,0.4);
		}

		ENDCG


        Pass
        {
			ZWrite Off
			//Blend SrcAlpha OneMinusSrcAlpha
            Blend One One
			CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            ENDCG
        }
    }
}


This is the first task that Pass needs to complete, drawing a transparent bottom ball.
The second Pass will complete the peripheral honeycomb effect.

The first is the vertex shader, in which the vertices protrude outward in the normal direction in model space.

v2f outvert(a2v v)
		{
			v2f o;
			v.vertex += float4(v.normal,1.0) * 0.05;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv = TRANSFORM_TEX(v.uv,_MainTex);
			return o;
		}

The second is the slice shader, which realizes the streamer effect by offsetting the sampling mask map.

fixed4 outfrag(v2f i) : SV_TARGET0
		{
			i.uv.x += _Time.x;	//Let the map offset laterally to form the "rotation" effect of the ball in the figure
			float mask = tex2D(_MaskTex,i.uv + float2(0,_Time.y * 0.5)).r;	//mask graph sampling, there's nothing to say
			float4 texColor = tex2D(_MainTex,i.uv);
			float main = 1 - texColor.a;	//Inverts the transparency of the main map
			fixed4 finalColor = lerp(_Color,_HighLightColor,main);
			finalColor = lerp(fixed4(0,0,0,1),finalColor,mask);
			return finalColor * 4;	//Multiply by a number to make the color brighter
		}

And the last Pass block:
Note that the mixing factor here is one one instead of srcalpha and oneminussrcalpha, which we usually use. The combination of Blend one one and the default BlendOp Max instruction is brighter than the ordinary mixing using transparency channel factor.

        Pass
        {
			ZWrite Off
			Blend one one
            CGPROGRAM
            #pragma vertex outvert
            #pragma fragment outfrag
            
            ENDCG
        }

PS: actually, these words bothered me for a while.

			float4 texColor = tex2D(_MainTex,i.uv);
			float main = 1 - texColor.a;	//Inverts the transparency of the main map
			fixed4 finalColor = lerp(_Color,_HighLightColor,main);

Because the inside of the lerp function is like this:

float lerp(float min,float max,float t)
{
	return min + (max-min) * t ;
}

Obviously, it's just a numerical calculation. Why can we get the effect of the whole map sampling by passing its A-channel sampling value into t of lerp? (the following figure shows the effect of changing the main of the code to 0.3)

However, the final effect can be obtained by changing to the value of channel a??
Then I thought it was complicated.. Because what the slice shader does is slice by slice. The map has a slice element at the corresponding position. For example, if the value of channel a at map p is 1 and the value passed to t is 1, the color effect will be returned, while the value of channel a at q is 0, which means that the slice element is completely transparent. In this way, the "honeycomb effect" is naturally obtained only through lerp.

The complete code is as follows:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/EnergyShield"
{
    Properties
    {
        _MainTex ("Main post map", 2D) = "white" {}
		_MaskTex ("Mask Mapping", 2D) = "white"{}
		_BaseColor ("Color 1", Color) = (1,1,1,1)
		_HighLightColor ("Color 2",Color) = (1,1,1,1)
		_BasicColorLerp ("Bottom ball color brightness",Range(0.0 , 1.0)) = 0.1
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector" = "true"}
        LOD 100

		CGINCLUDE
		
		#include "UnityCG.cginc"
		struct a2v
		{
			float4 vertex : POSITION;
			float3 normal : NORMAL;
			float2 uv : TEXCOORD0;
		};

		struct v2f
		{
			float4 pos : SV_POSITION;
			float2 uv : TEXCOORD0;
		};

		sampler2D _MainTex;
		float4 _MainTex_ST;
		sampler2D _MaskTex;
		float4 _MaskTex_ST;
		fixed4 _Color;
		fixed4 _HighLightColor;
		fixed _BasicColorLerp;

		v2f vert(a2v v)
		{
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv = TRANSFORM_TEX(v.uv,_MainTex);
			return o;
		}

		fixed4 frag(v2f i) : SV_TARGET0
		{
			return lerp(_Color,_HighLightColor,_BasicColorLerp);
		}


		v2f outvert(a2v v)
		{
			v2f o;
			v.vertex += float4(v.normal,1.0) * 0.05;
			o.pos = UnityObjectToClipPos(v.vertex);
			o.uv = TRANSFORM_TEX(v.uv,_MainTex);
			return o;
		}

		fixed4 outfrag(v2f i) : SV_TARGET0
		{
			i.uv.x += _Time.x;
			float mask = tex2D(_MaskTex,i.uv + float2(0,_Time.y * 0.5)).r;
			float4 texColor = tex2D(_MainTex,i.uv);
			float main = 1 - texColor.a;
			fixed4 finalColor = lerp(_Color,_HighLightColor,main);
			finalColor = lerp(fixed4(0,0,0,0),finalColor,mask);
			return finalColor * 4;
		}


		ENDCG


        Pass
        {
			ZWrite Off
			//Blend SrcAlpha OneMinusSrcAlpha
            Blend One One
			CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            ENDCG
        }

        Pass
        {
			ZWrite Off
			//BlendOp Max
			Blend one one
			//Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex outvert
            #pragma fragment outfrag
            
            ENDCG
        }
    }
}

Final effect (no Bloom):

(with Bloom):

Poster:

Mask texture:

Material parameters:

Topics: Unity Computer Graphics Shader