引言
在游戏特效制作中,像素扰动和故障效果(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. 时间控制系统
float mulTime137 = _TimeParameters.x * _TimeScale; float mulTime140 = _TimeParameters.x * _TimeScale1; float Time153 = ( saturate( sin( mulTime137 ) ) * saturate( cos( mulTime140 ) ) );
Shader使用两个时间维度:
-
_TimeScale:控制动画整体速度 -
_TimeScale1:控制频率变化 -
通过sin和cos函数的乘积,创造出更自然的扰动节奏
2. 像素化噪波采样
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实现了两层不同的扰动效果:
第一层扰动:
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));
第二层扰动:
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色散效果
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. 通道合成
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偏移和通道分离,实现了丰富的视觉效果。




-DrawMe.png)



暂无评论内容