Skip to content

Instantly share code, notes, and snippets.

@rumaniel
Last active November 11, 2025 05:25
Show Gist options
  • Select an option

  • Save rumaniel/a8b929b8791f8759b19068ec00c36c32 to your computer and use it in GitHub Desktop.

Select an option

Save rumaniel/a8b929b8791f8759b19068ec00c36c32 to your computer and use it in GitHub Desktop.
To Applying Sprite Clipging
using UnityEngine;
using UnityEngine.EventSystems;
[ExecuteInEditMode]
public class SpritePanel : UIBehaviour
{
public enum Clipping
{
None,
TextureMask,
SoftClip
};
[SerializeField] private Clipping _clipping;
[SerializeField] private Vector4 _clipRange = new Vector4(0f, 0f, 300f, 200f);
[SerializeField] private Vector2 _clipSoftness = new Vector2(0f, 0f);
[SerializeField] private Texture2D _clipTexture = null;
[SerializeField] private float _cutoff = -1f;
[SerializeField] private float _cutoffSoftness = 1f;
[SerializeField] private Material _material;
[SerializeField] private Color _color = Color.white;
private bool rebuildMaterial;
public Clipping clipping
{
get => _clipping;
set
{
if (_clipping != value)
{
_clipping = value;
}
}
}
public Vector4 clipRange
{
get => _clipRange;
set
{
if (_clipRange != value)
{
_clipRange = value;
}
}
}
public Vector2 clipSoftness
{
get => _clipSoftness;
set
{
if (_clipSoftness != value)
{
_clipSoftness = value;
}
}
}
public Texture2D clipTexture
{
get => _clipTexture;
set
{
if (_clipTexture != value)
{
_clipTexture = value;
}
}
}
public float cutoff
{
get => _cutoff;
set
{
if (_cutoff != value)
{
_cutoff = value;
}
}
}
public float cutoffSoftness
{
get => _cutoffSoftness;
set
{
if (_cutoffSoftness != value)
{
_cutoffSoftness = value;
}
}
}
public Color color
{
get => _color;
set
{
if (_color != value)
{
_color = value;
}
}
}
public Material material
{
get => _material;
set
{
if (_material != value)
{
_material = value;
rebuildMaterial = true;
}
}
}
public Material dynamicMaterial { get; protected set; }
public SpriteGroup spriteGroup = null;
protected override void Awake()
{
RebuildMaterial();
}
protected override void OnEnable()
{
UpdateSpriteGroup();
}
protected override void OnTransformParentChanged()
{
if (gameObject.activeSelf)
UpdateSpriteGroup();
}
private void UpdateSpriteGroup()
{
spriteGroup = gameObject.GetComponentInParent<SpriteGroup>();
}
protected override void OnDestroy()
{
UnityUtil.DestroyObject(dynamicMaterial);
dynamicMaterial = null;
}
private void RebuildMaterial()
{
UnityUtil.DestroyObject(dynamicMaterial);
dynamicMaterial = new Material(material);
dynamicMaterial.name = material.name;
dynamicMaterial.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
dynamicMaterial.CopyPropertiesFromMaterial(material);
string[] keywords = material.shaderKeywords;
for (int i = 0; i < keywords.Length; ++i)
dynamicMaterial.EnableKeyword(keywords[i]);
}
protected virtual void LateUpdate()
{
if (material != null && rebuildMaterial)
{
RebuildMaterial();
rebuildMaterial = false;
}
UpdateDrawCall();
}
public void UpdateDrawCall()
{
UpdateDrawCall(dynamicMaterial, 0);
}
public void UpdateDrawCall(Material mat, int depth)
{
if (mat == null) return;
if (_clipping != Clipping.None)
{
Vector3 pos = transform.position;
Vector3 scale = transform.lossyScale;
Vector4 cr = _clipRange;
cr.x *= scale.x;
cr.y *= scale.y;
cr.z *= 0.5f * scale.x;
cr.w *= 0.5f * scale.y;
if (_clipping == Clipping.SoftClip)
{
Vector2 sharpness = new Vector2(1000.0f, 1000.0f);
if (_clipSoftness.x > 0f) sharpness.x = cr.z / _clipSoftness.x;
if (_clipSoftness.y > 0f) sharpness.y = cr.w / _clipSoftness.y;
mat.SetVector(SpriteShaderUtils.CLIP_ARGS_PROPERTY_ID[depth], new Vector4(sharpness.x, sharpness.y, 0f, 0f));
}
else if (_clipping == Clipping.TextureMask)
{
float sharpness = 1f;
if (_cutoffSoftness > 0f) sharpness = 1f / _cutoffSoftness;
mat.SetTexture(SpriteShaderUtils.CLIP_TEX_PROPERTY_ID[depth], _clipTexture);
mat.SetFloat(SpriteShaderUtils.CLIP_CUTOFF_PROPERTY_ID[depth], _cutoff);
mat.SetFloat(SpriteShaderUtils.CLIP_CUTOFF_SHARPNESS_PROPERTY_ID[depth], sharpness);
}
mat.SetVector(SpriteShaderUtils.CLIP_PIVOT_PROPERTY_ID[depth], new Vector4(pos.x, pos.y, pos.z, 0f));
mat.SetMatrix(SpriteShaderUtils.CLIP_ROTATATION_PROPERTY_ID[depth], Matrix4x4.TRS(Vector3.zero, Quaternion.Inverse(transform.rotation), Vector3.one));
mat.SetVector(SpriteShaderUtils.CLIP_RANGE_PROPERTY_ID[depth], new Vector4(-cr.x / cr.z, -cr.y / cr.w, 1f / cr.z, 1f / cr.w));
var parent = transform.parent;
if (parent == null) return;
var panel = parent.GetComponentInParent<SpritePanel>();
if (panel != null)
panel.UpdateDrawCall(mat, depth + 1);
}
if (depth == 0)
{
if (spriteGroup == null)
mat.SetColor(SpriteShaderUtils.COLOR_PROPERTY_ID, _color);
else
mat.SetColor(SpriteShaderUtils.COLOR_PROPERTY_ID, _color * spriteGroup.color);
}
}
}
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(SpritePanel))]
public class SpritePanelInspector : Editor
{
private SpritePanel panel;
private void OnEnable()
{
panel = target as SpritePanel;
}
public override void OnInspectorGUI()
{
Material mat = (Material)EditorGUILayout.ObjectField("Material", panel.material, typeof(Material), false);
if (panel.material != mat)
{
panel.material = mat;
}
panel.color = EditorGUILayout.ColorField("Color", panel.color);
SpritePanel.Clipping clipping = (SpritePanel.Clipping)EditorGUILayout.EnumPopup("Clipping", panel.clipping);
if (panel.clipping != clipping)
{
panel.clipping = clipping;
}
if (panel.clipping != SpritePanel.Clipping.None)
{
Vector4 range = panel.clipRange;
GUILayout.BeginHorizontal();
GUILayout.Space(80f);
Vector2 pos = EditorGUILayout.Vector2Field("Center", new Vector2(range.x, range.y), GUILayout.MinWidth(20f));
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Space(80f);
Vector2 size = EditorGUILayout.Vector2Field("Size", new Vector2(range.z, range.w), GUILayout.MinWidth(20f));
GUILayout.EndHorizontal();
range.x = pos.x;
range.y = pos.y;
range.z = size.x;
range.w = size.y;
if (panel.clipRange != range)
{
panel.clipRange = range;
}
if (panel.clipping == SpritePanel.Clipping.SoftClip)
{
GUILayout.BeginHorizontal();
GUILayout.Space(80f);
Vector2 soft = EditorGUILayout.Vector2Field("Softness", panel.clipSoftness, GUILayout.MinWidth(20f));
GUILayout.EndHorizontal();
if (panel.clipSoftness != soft)
{
panel.clipSoftness = soft;
}
}
else if (panel.clipping == SpritePanel.Clipping.TextureMask)
{
GUILayout.BeginHorizontal();
GUILayout.Space(80f);
float cutoff = EditorGUILayout.FloatField("Cutoff", panel.cutoff);
cutoff = Mathf.Clamp(cutoff, -1f, 1f);
GUILayout.EndHorizontal();
if (panel.cutoff != cutoff)
{
panel.cutoff = cutoff;
}
GUILayout.BeginHorizontal();
GUILayout.Space(80f);
float soft = EditorGUILayout.FloatField("Cutoff Softness", panel.cutoffSoftness);
soft = Mathf.Clamp(soft, 0.001f, 1f);
GUILayout.EndHorizontal();
if (panel.cutoffSoftness != soft)
{
panel.cutoffSoftness = soft;
}
GUILayout.BeginHorizontal();
GUILayout.Space(80f);
EditorGUIUtility.labelWidth = 0;
EditorGUIUtility.fieldWidth = 15;
Texture2D tex = (Texture2D)EditorGUILayout.ObjectField("Clip Texture", panel.clipTexture,
typeof(Texture2D), false);
GUILayout.EndHorizontal();
if (panel.clipTexture != tex)
{
panel.clipTexture = tex;
}
}
if (GUILayout.Button("Fit"))
{
size = panel.GetComponent<RectTransform>().sizeDelta;
panel.clipRange = new Vector4(0f, 0f, size.x, size.y);
}
}
}
}
using UnityEngine;
public static class SpriteShaderUtils
{
public static int COLOR_PROPERTY_ID;
public static int[] CLIP_PIVOT_PROPERTY_ID = new int[4];
public static int[] CLIP_RANGE_PROPERTY_ID = new int[4];
public static int[] CLIP_ROTATATION_PROPERTY_ID = new int[4];
public static int[] CLIP_ARGS_PROPERTY_ID = new int[4];
public static int[] CLIP_TEX_PROPERTY_ID = new int[4];
public static int[] CLIP_CUTOFF_PROPERTY_ID = new int[4];
public static int[] CLIP_CUTOFF_SHARPNESS_PROPERTY_ID = new int[4];
static SpriteShaderUtils()
{
GetShaderPropertyIDs();
}
public static void GetShaderPropertyIDs()
{
COLOR_PROPERTY_ID = Shader.PropertyToID("_Color");
for (int i = 0; i < 4; ++i)
{
string postFix = "";
if (i > 0) postFix = i.ToString();
CLIP_PIVOT_PROPERTY_ID[i] = Shader.PropertyToID("_ClipPivot" + postFix);
CLIP_RANGE_PROPERTY_ID[i] = Shader.PropertyToID("_ClipRange" + postFix);
CLIP_ROTATATION_PROPERTY_ID[i] = Shader.PropertyToID("_ClipRotation" + postFix);
CLIP_ARGS_PROPERTY_ID[i] = Shader.PropertyToID("_ClipArgs" + postFix);
CLIP_TEX_PROPERTY_ID[i] = Shader.PropertyToID("_ClipTex" + postFix);
CLIP_CUTOFF_PROPERTY_ID[i] = Shader.PropertyToID("_Cutoff" + postFix);
CLIP_CUTOFF_SHARPNESS_PROPERTY_ID[i] = Shader.PropertyToID("_CutoffSharpness" + postFix);
}
}
}
Shader "UI/Soft Clip"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_ClipPivot ("Clip Pivot", Vector) = (0,0,0,0)
_ClipRange ("Clip Range", Vector) = (0,0,1,1)
_ClipArgs ("Clip Arguments", Vector) = (1000,1000,0,1)
_ColorMask("Color Mask", Float) = 15
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Cull Off
Lighting Off
ZWrite Off
ZTest[unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipPivot;
float4x4 _ClipRotation;
float4 _ClipRange;
float4 _ClipArgs;
sampler2D _MainTex;
struct appdata_ct
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f_ct
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
half2 texcoord : TEXCOORD0;
float2 worldPos : TEXCOORD1;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f_ct vert(appdata_ct IN)
{
v2f_ct OUT;
UNITY_SETUP_INSTANCE_ID (IN);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color;
OUT.worldPos = _ClipPivot.xy - mul(unity_ObjectToWorld, IN.vertex).xy;
OUT.worldPos = mul(_ClipRotation, float4(OUT.worldPos, 0, 0)).xy * _ClipRange.zw + _ClipRange.xy;
return OUT;
}
fixed4 frag(v2f_ct IN) : SV_Target
{
// Softness factor
fixed2 factor = (float2(1, 1) - abs(IN.worldPos)) * _ClipArgs.xy;
fixed fade = clamp(min(factor.x, factor.y), 0, 1);
half4 c = IN.color * (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);
c.a *= fade;
c.rgb *= c.a;
return c;
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment