pic

引言

在游戏特效制作中,像素扰动和故障效果(Glitch Effect)能够为游戏增添独特的视觉风格。本文将深入分析一个基于URP的像素扰动Shader,它实现了RGB色散、噪波扰动、像素化等丰富的特效效果。

效果预览

这个Shader主要包含以下特性:

  • 基于噪波图的像素扰动

  • RGB通道分离(色散效果)

  • 可控制的动画时间系统

  • 像素化扰动强度控制

  • 模板缓冲区支持

  • URP渲染管线兼容

Shader完整代码

Shader "Roulette/FX/GlitchEffect2"
{
	Properties
	{
		[HDR]_Tex01Color("第一层贴图颜色", Color) = (1,1,1,0)
		_MainTex("第一层贴图", 2D) = "white" {}
		[Toggle(_USETIMEANI_ON)] _UseTimeAni("关闭动画时间", Float) = 0
		[Toggle(_USETIME_ON)] _UseTime("开启时间随机播放", Float) = 0
		_TimeScale("时间快慢", Float) = 1
		_TimeScale1("时间频率", Float) = 1
		_RGBDistance1("RGB色散分离距离", Vector) = (1,0,0,0)
		[Header(Noise)]_NoiseTex("像素图", 2D) = "white" {}
		_NoiseScale("像素图强度", Range( -1 , 1)) = 0
		_NoiseMask("像素图遮罩", 2D) = "white" {}
		_Distance1("像素图第一层强度", Float) = 0.67
		_NoiseTilling1("像素图第一层平鋪Z范围", Vector) = (1,10,0,0)
		_Distance2("像素图第二层强度", Float) = 1
		_NoiseTilling3("像素图第二层平鋪Z范围", Vector) = (1,10,0,0)
		_Alpha("Alpha", Range( 0 , 1)) = 1
		
		_StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255
		[HideInInspector]_ClipRect("_clipRect",vector)=(0,0,0,0)
		[Enum(Null,0,RGB,14,RGBA,15)] _ColorMask("Color Mask",int)=15
		[ToggleUI] _MASKCLIP_ON("Mask Clip On", int) = 0
	}

	SubShader
	{
		LOD 0
		
		Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="Transparent" "Queue"="Transparent" "UniversalMaterialType"="Unlit" }

		Cull Back
		//AlphaToMask Off
		
		HLSLINCLUDE
		#pragma target 3.0
		#pragma prefer_hlslcc gles
		#pragma only_renderers d3d11 gles3 metal vulkan // ensure rendering platforms toggle list is visible

		#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
		#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Filtering.hlsl"
		
		ENDHLSL
		
		Pass
		{
			Name "Forward"
			Tags { "LightMode"="UniversalForwardOnly" }

			//Blend One Zero, One Zero
			Blend SrcAlpha OneMinusSrcAlpha
			ZWrite Off
			ZTest LEqual
			Stencil
	        {
	            Ref [_Stencil]
	            Comp [_StencilComp]
	            Pass [_StencilOp]
	            ReadMask [_StencilReadMask]
	            WriteMask [_StencilWriteMask]
	        }
			Offset 0 , 0
			//ColorMask RGBA
			ColorMask [_ColorMask]
			HLSLPROGRAM

			#pragma vertex vert
			#pragma fragment frag
	
			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl"
			
			#pragma shader_feature_local _USETIMEANI_ON
			#pragma shader_feature_local _USETIME_ON


			struct VertexInput
			{
				float4 positionOS : POSITION;
			
				float4 ase_texcoord : TEXCOORD0;
		
			};

			struct VertexOutput
			{
				float4 positionCS : SV_POSITION;
				float2 ase_texcoord3 : TEXCOORD0;
				half2 vpos:TEXCOORD1;
			};

			CBUFFER_START(UnityPerMaterial)
			float4 _Tex01Color;
			float4 _NoiseTex_ST;
			float4 _NoiseTilling1;
			float4 _NoiseMask_ST;
			float4 _NoiseTilling3;
			float4 _MainTex_ST;
			float2 _RGBDistance1;
			float _TimeScale;
			float _TimeScale1;
			float _Distance1;
			float _Distance2;
			float _NoiseScale;
			float _Alpha;
			half _MASKCLIP_ON;
			float4 _ClipRect;
			CBUFFER_END

			sampler2D _MainTex;
			sampler2D _NoiseTex;
			sampler2D _NoiseMask;
			
			VertexOutput VertexFunction( VertexInput v  )
			{
				VertexOutput o = (VertexOutput)0;
				
				o.ase_texcoord3.xy = v.ase_texcoord.xy;
				
				float3 positionWS = TransformObjectToWorld( v.positionOS.xyz );
				float4 positionCS = TransformWorldToHClip( positionWS );

				o.positionCS = positionCS;
				if(_MASKCLIP_ON)
				{
					o.vpos = v.positionOS.xy;
				}
				return o;
			}

		
			VertexOutput vert ( VertexInput v )
			{
				return VertexFunction( v );
			}
	
			inline float UnityGet2DClipping (in float2 position, in float4 clipRect)
			{
				float2 inside = step(clipRect.xy, position.xy) * step(position.xy, clipRect.zw);
				return inside.x * inside.y;
			}
			half4 frag ( VertexOutput IN) : SV_Target
			{
				
				float mulTime137 = _TimeParameters.x * _TimeScale;
				float mulTime140 = _TimeParameters.x * _TimeScale1;
				float Time153 = ( saturate( sin( mulTime137 ) ) * saturate( cos( mulTime140 ) ) );
				float2 uv_NoiseTex = IN.ase_texcoord3.xy * _NoiseTex_ST.xy + _NoiseTex_ST.zw;
				float pixelWidth219 =  1.0f / _NoiseTilling1.x;
				float pixelHeight219 = 1.0f / _NoiseTilling1.y;
				half2 pixelateduv219 = half2((int)(uv_NoiseTex.x / pixelWidth219) * pixelWidth219, (int)(uv_NoiseTex.y / pixelHeight219) * pixelHeight219);
				float2 uv_NoiseMask = IN.ase_texcoord3.xy * _NoiseMask_ST.xy + _NoiseMask_ST.zw;
				float temp_output_266_0 = floor( tex2D( _NoiseMask, uv_NoiseMask ).r );
				float2 appendResult218 = (float2(saturate( ( (_NoiseTilling1.z + (tex2D( _NoiseTex, pixelateduv219 ).r - 0.0) * (1.0 - _NoiseTilling1.z) / (1.0 - 0.0)) * temp_output_266_0 * _Distance1 ) ) , 0.0));
				float pixelWidth255 =  1.0f / _NoiseTilling3.x;
				float pixelHeight255 = 1.0f / _NoiseTilling3.y;
				half2 pixelateduv255 = half2((int)(uv_NoiseTex.x / pixelWidth255) * pixelWidth255, (int)(uv_NoiseTex.y / pixelHeight255) * pixelHeight255);
				float2 appendResult263 = (float2(( temp_output_266_0 * (_NoiseTilling3.z + (tex2D( _NoiseTex, pixelateduv255 ).r - 0.0) * (1.0 - _NoiseTilling3.z) / (1.0 - 0.0)) * _Distance2 ) , 0.0));
				float2 Noise241 = ( ( appendResult218 + appendResult263 ) * _NoiseScale );
				#ifdef _USETIMEANI_ON
				float2 staticSwitch278 = Noise241;
				#else
				float2 staticSwitch278 = ( Time153 * Noise241 );
				#endif
				float2 uv_Tex2 = IN.ase_texcoord3.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				float2 temp_output_242_0 = ( staticSwitch278 + uv_Tex2 );
				#ifdef _USETIME_ON
				float staticSwitch157 = Time153;
				#else
				float staticSwitch157 = 1.0;
				#endif
				float2 temp_output_66_0 = ( _RGBDistance1 * float2( 0.1,0.1 ) * staticSwitch157 );
				float2 G76 = ( temp_output_242_0 + temp_output_66_0 );
				float4 tex2DNode100 = tex2D( _MainTex, G76 );
				float2 B77 = ( temp_output_242_0 + ( temp_output_66_0 * float2( -1,-1 ) ) );
				float4 tex2DNode101 = tex2D( _MainTex, B77 );
				float2 R75 = temp_output_242_0;
				float4 tex2DNode102 = tex2D( _MainTex, R75 );
				float4 appendResult108 = (float4(( tex2DNode100.r * tex2DNode100.a ) , ( tex2DNode101.g * tex2DNode101.a ) , ( tex2DNode102.b * tex2DNode102.a ) , saturate( ( ( tex2DNode100.a + tex2DNode101.a + tex2DNode102.a ) / 3.0 ) )));
				float4 Tex01RG130 = appendResult108;
				
				float3 Color = ( _Tex01Color * Tex01RG130 ).rgb;
				float Alpha = ( (Tex01RG130).w * _Alpha );
				if(_MASKCLIP_ON)
				{
					float c = UnityGet2DClipping(IN.vpos.xy, _ClipRect);
					Alpha = c * Alpha;
				}
				return half4( Color, Alpha );
			}
			ENDHLSL
		}
		
	}
	
	//CustomEditor "UnityEditor.ShaderGraphUnlitGUI"
	FallBack "Hidden/Shader Graph/FallbackError"
	
	Fallback Off
}

 

核心原理解析

1. 时间控制系统

hlsl
float mulTime137 = _TimeParameters.x * _TimeScale;
float mulTime140 = _TimeParameters.x * _TimeScale1;
float Time153 = ( saturate( sin( mulTime137 ) ) * saturate( cos( mulTime140 ) ) );

Shader使用两个时间维度:

  • _TimeScale:控制动画整体速度

  • _TimeScale1:控制频率变化

  • 通过sin和cos函数的乘积,创造出更自然的扰动节奏

2. 像素化噪波采样

hlsl
float pixelWidth219 = 1.0f / _NoiseTilling1.x;
float pixelHeight219 = 1.0f / _NoiseTilling1.y;
half2 pixelateduv219 = half2(
    (int)(uv_NoiseTex.x / pixelWidth219) * pixelWidth219,
    (int)(uv_NoiseTex.y / pixelHeight219) * pixelHeight219
);

通过整数除法实现像素化效果:

  • 将UV坐标按指定精度进行量化

  • _NoiseTilling1.x_NoiseTilling1.y控制水平和垂直方向的像素块大小

  • 创造出数字感的像素块效果

3. 双层扰动系统

Shader实现了两层不同的扰动效果:

第一层扰动

hlsl
float2 appendResult218 = (float2(saturate( 
    (_NoiseTilling1.z + (tex2D(_NoiseTex, pixelateduv219).r - 0.0) * 
    (1.0 - _NoiseTilling1.z) / (1.0 - 0.0)) * 
    temp_output_266_0 * _Distance1 
), 0.0));

第二层扰动

hlsl
float2 appendResult263 = (float2(
    (temp_output_266_0 * (_NoiseTilling3.z + 
    (tex2D(_NoiseTex, pixelateduv255).r - 0.0) * 
    (1.0 - _NoiseTilling3.z) / (1.0 - 0.0)) * _Distance2
), 0.0));

两层扰动叠加:

  • 通过_Distance1_Distance2分别控制两层强度

  • 使用遮罩图控制扰动区域

  • 噪波的R通道作为扰动强度值

4. RGB色散效果

hlsl
float2 temp_output_66_0 = (_RGBDistance1 * float2(0.1,0.1) * staticSwitch157);
float2 G76 = (temp_output_242_0 + temp_output_66_0);  // 绿色通道偏移
float2 B77 = (temp_output_242_0 + (temp_output_66_0 * float2(-1,-1)));  // 蓝色通道反向偏移
float2 R75 = temp_output_242_0;  // 红色通道不偏移

通过RGB通道的错位采样实现色散:

  • 红通道保持原位置

  • 绿通道正向偏移

  • 蓝通道反向偏移

  • _RGBDistance1控制分离强度

5. 通道合成

hlsl
float4 appendResult108 = (float4(
    (tex2DNode100.r * tex2DNode100.a),  // 红色分量
    (tex2DNode101.g * tex2DNode101.a),  // 绿色分量
    (tex2DNode102.b * tex2DNode102.a),  // 蓝色分量
    saturate(((tex2DNode100.a + tex2DNode101.a + tex2DNode102.a) / 3.0))  // 透明度
));

各通道乘以对应Alpha值,实现透明区域的正确融合。

参数详解

基础参数

  • _Tex01Color:基础颜色(HDR支持)

  • _MainTex:主纹理

  • _Alpha:整体透明度控制

时间控制

  • _UseTimeAni:关闭动画时间

  • _UseTime:开启时间随机播放

  • _TimeScale:动画速度

  • _TimeScale1:频率控制

噪波系统

  • _NoiseTex:扰动图

  • _NoiseScale:整体强度

  • _NoiseMask:区域遮罩

  • _Distance1:第一层强度

  • _NoiseTilling1:第一层平铺和Z范围

  • _Distance2:第二层强度

  • _NoiseTilling3:第二层平铺和Z范围

RGB控制

  • _RGBDistance1:RGB分离距离

应用技巧

1. 故障文字效果

设置较小的_NoiseTilling值,配合适中的_RGBDistance1,可以制作出故障文字效果。

2. 数字信号干扰

增大_NoiseScale,使用高对比度的噪波图,模拟数字信号干扰。

3. 区域控制

通过_NoiseMask控制扰动区域,可以实现局部特效,如边缘扰动等。

 

总结

这个像素扰动Shader通过UV偏移和通道分离,实现了丰富的视觉效果。

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容