Skip to content

Instantly share code, notes, and snippets.

@BrianMacIntosh
Created March 4, 2021 18:03
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BrianMacIntosh/23275e79bb7489b42ad4ac0916a56824 to your computer and use it in GitHub Desktop.
Save BrianMacIntosh/23275e79bb7489b42ad4ac0916a56824 to your computer and use it in GitHub Desktop.
Shader for Unity UI that uses a four-color gradient, and custom Image component for using the shader with Sliced images.
using UnityEngine;
using UnityEngine.UI;
// Based on the Unity built-in image source (Unity Reference-Only License, https://unity3d.com/legal/licenses/Unity_Reference_Only_License)
// Modified to add full-mesh UVs by Brian MacIntosh
namespace UI
{
/// <summary>
/// Image override that fills the second UV channel with uniform UVs over the entire image.
/// These are the same as the normal UVs for Simple images, but different for Tiled and Sliced images.
/// </summary>
/// <remarks>Much code duplicated from UnityEngine.UI.Image.</remarks>
public class Image2 : Image
{
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (canvas)
{
canvas.additionalShaderChannels |= AdditionalCanvasShaderChannels.TexCoord1;
}
}
#endif
protected override void OnPopulateMesh(VertexHelper toFill)
{
if (overrideSprite == null)
{
base.OnPopulateMesh(toFill);
return;
}
switch (type)
{
case Type.Simple:
base.OnPopulateMesh(toFill);
return;
case Type.Sliced:
GenerateSlicedSprite(toFill);
return;
case Type.Tiled:
//TODO:
base.OnPopulateMesh(toFill);
return;
case Type.Filled:
//TODO?
base.OnPopulateMesh(toFill);
return;
}
}
static readonly Vector2[] s_VertScratch = new Vector2[4];
static readonly Vector2[] s_UVScratch = new Vector2[4];
static readonly Vector2[] s_UV2Scratch = new Vector2[4];
private void GenerateSlicedSprite(VertexHelper toFill)
{
// COPIED from base class Image
if (!hasBorder)
{
//HACK:
base.OnPopulateMesh(toFill);
return;
}
Vector4 outer, inner, padding, border;
if (overrideSprite != null)
{
outer = UnityEngine.Sprites.DataUtility.GetOuterUV(overrideSprite);
inner = UnityEngine.Sprites.DataUtility.GetInnerUV(overrideSprite);
padding = UnityEngine.Sprites.DataUtility.GetPadding(overrideSprite);
border = overrideSprite.border;
}
else
{
outer = Vector4.zero;
inner = Vector4.zero;
padding = Vector4.zero;
border = Vector4.zero;
}
Rect rect = GetPixelAdjustedRect();
Vector4 adjustedBorders = GetAdjustedBorders(border / pixelsPerUnit, rect);
padding = padding / pixelsPerUnit;
s_VertScratch[0] = new Vector2(padding.x, padding.y);
s_VertScratch[3] = new Vector2(rect.width - padding.z, rect.height - padding.w);
s_VertScratch[1].x = adjustedBorders.x;
s_VertScratch[1].y = adjustedBorders.y;
s_VertScratch[2].x = rect.width - adjustedBorders.z;
s_VertScratch[2].y = rect.height - adjustedBorders.w;
for (int i = 0; i < 4; ++i)
{
s_VertScratch[i].x += rect.x;
s_VertScratch[i].y += rect.y;
}
s_UVScratch[0] = new Vector2(outer.x, outer.y);
s_UVScratch[1] = new Vector2(inner.x, inner.y);
s_UVScratch[2] = new Vector2(inner.z, inner.w);
s_UVScratch[3] = new Vector2(outer.z, outer.w);
// ADDED: Produce UV2 based on vertex positions
Vector2 vertexSize = s_VertScratch[3] - s_VertScratch[0];
s_UV2Scratch[0] = Vector3.zero;
s_UV2Scratch[1] = (s_VertScratch[1] - s_VertScratch[0]) / vertexSize;
s_UV2Scratch[2] = (s_VertScratch[2] - s_VertScratch[0]) / vertexSize;
s_UV2Scratch[3] = (s_VertScratch[3] - s_VertScratch[0]) / vertexSize;
// END ADDED
toFill.Clear();
for (int x = 0; x < 3; ++x)
{
int x2 = x + 1;
for (int y = 0; y < 3; ++y)
{
if (!fillCenter && x == 1 && y == 1)
continue;
int y2 = y + 1;
// MODIFIED: Added UV2s
AddQuad(toFill,
new Vector2(s_VertScratch[x].x, s_VertScratch[y].y),
new Vector2(s_VertScratch[x2].x, s_VertScratch[y2].y),
color,
new Vector2(s_UVScratch[x].x, s_UVScratch[y].y),
new Vector2(s_UVScratch[x2].x, s_UVScratch[y2].y),
new Vector2(s_UV2Scratch[x].x, s_UV2Scratch[y].y),
new Vector2(s_UV2Scratch[x2].x, s_UV2Scratch[y2].y));
}
}
}
static void AddQuad(VertexHelper vertexHelper, Vector2 posMin, Vector2 posMax, Color32 color,
Vector2 uvMin, Vector2 uvMax,
Vector2 uv2Min, Vector2 uv2Max)
{
int startIndex = vertexHelper.currentVertCount;
// MODIFIED to add uv2
vertexHelper.AddVert(new Vector3(posMin.x, posMin.y, 0), color, new Vector2(uvMin.x, uvMin.y), new Vector2(uv2Min.x, uv2Min.y), Vector3.forward, Vector4.zero);
vertexHelper.AddVert(new Vector3(posMin.x, posMax.y, 0), color, new Vector2(uvMin.x, uvMax.y), new Vector2(uv2Min.x, uv2Max.y), Vector3.forward, Vector4.zero);
vertexHelper.AddVert(new Vector3(posMax.x, posMax.y, 0), color, new Vector2(uvMax.x, uvMax.y), new Vector2(uv2Max.x, uv2Max.y), Vector3.forward, Vector4.zero);
vertexHelper.AddVert(new Vector3(posMax.x, posMin.y, 0), color, new Vector2(uvMax.x, uvMin.y), new Vector2(uv2Max.x, uv2Min.y), Vector3.forward, Vector4.zero);
vertexHelper.AddTriangle(startIndex, startIndex + 1, startIndex + 2);
vertexHelper.AddTriangle(startIndex + 2, startIndex + 3, startIndex);
}
private Vector4 GetAdjustedBorders(Vector4 border, Rect adjustedRect)
{
// COPIED from base class Image
Rect originalRect = rectTransform.rect;
for (int axis = 0; axis <= 1; axis++)
{
float borderScaleRatio;
// The adjusted rect (adjusted for pixel correctness)
// may be slightly larger than the original rect.
// Adjust the border to match the adjustedRect to avoid
// small gaps between borders (case 833201).
if (originalRect.size[axis] != 0)
{
borderScaleRatio = adjustedRect.size[axis] / originalRect.size[axis];
border[axis] *= borderScaleRatio;
border[axis + 2] *= borderScaleRatio;
}
// If the rect is smaller than the combined borders, then there's not room for the borders at their normal size.
// In order to avoid artefacts with overlapping borders, we scale the borders down to fit.
float combinedBorders = border[axis] + border[axis + 2];
if (adjustedRect.size[axis] < combinedBorders && combinedBorders != 0)
{
borderScaleRatio = adjustedRect.size[axis] / combinedBorders;
border[axis] *= borderScaleRatio;
border[axis + 2] *= borderScaleRatio;
}
}
return border;
}
}
}
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
// Modified to add gradient color by Brian MacIntosh
Shader "UI/Gradient2"
{
Properties
{
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
_TopLeftColor("Gradient Top Left", Color) = (1,1,1,1)
_TopRightColor("Gradient Top Right", Color) = (1,1,1,1)
_BottomLeftColor("Gradient Bottom Left", Color) = (1,1,1,1)
_BottomRightColor("Gradient Bottom Right", Color) = (1,1,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
_ColorMask("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "Transparent"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
Stencil
{
Ref[_Stencil]
Comp[_StencilComp]
Pass[_StencilOp]
ReadMask[_StencilReadMask]
WriteMask[_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest[unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask[_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile __ UNITY_UI_CLIP_RECT
#pragma multi_compile __ UNITY_UI_ALPHACLIP
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
float2 texcoord1 : TEXCOORD1; // uv2 is filled by Image2
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float2 texcoord2 : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
};
sampler2D _MainTex;
fixed4 _TopLeftColor;
fixed4 _TopRightColor;
fixed4 _BottomLeftColor;
fixed4 _BottomRightColor;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float4 _MainTex_ST;
v2f vert(appdata_t v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.worldPosition = v.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
OUT.texcoord2 = v.texcoord1;
OUT.color = v.color;
return OUT;
}
fixed4 frag(v2f IN) : SV_Target
{
half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
fixed4 topColor = _TopRightColor * IN.texcoord2.x + _TopLeftColor * (1 - IN.texcoord2.x);
fixed4 bottomColor = _BottomRightColor * IN.texcoord2.x + _BottomLeftColor * (1 - IN.texcoord2.x);
color = color * (topColor * IN.texcoord2.y + bottomColor * (1 - IN.texcoord2.y));
#ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
#endif
#ifdef UNITY_UI_ALPHACLIP
clip(color.a - 0.001);
#endif
return color;
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment