2025-10-03 00:02:43 -04:00
/// Credit Brad Nelson (playemgames - bitbucket)
/// Modified Gradient effect script from http://answers.unity3d.com/questions/1086415/gradient-text-in-unity-522-basevertexeffect-is-obs.html
/// <summary>
/// -Uses Unity's Gradient class to define the color
/// -Offset is now limited to -1,1
/// -Multiple color blend modes
///
/// Remember that for radial and diamond gradients, colors are applied per-vertex so if you have multiple points on your gradient where the color changes and there aren't enough vertices, you won't see all of the colors.
/// </summary>
2026-03-20 11:56:50 -04:00
2025-10-03 00:02:43 -04:00
using System ;
using System.Collections.Generic ;
namespace UnityEngine.UI.Extensions
{
[AddComponentMenu("UI/Effects/Extensions/Gradient2")]
public class Gradient2 : BaseMeshEffect
{
2026-03-20 11:56:50 -04:00
public enum Blend
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
Override ,
Add ,
Multiply
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
public enum Type
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
Horizontal ,
Vertical ,
Radial ,
Diamond
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
[SerializeField] private Type _gradientType ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
[SerializeField] private Blend _blendMode = Blend . Multiply ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
[SerializeField]
[ Tooltip (
"Add vertices to display complex gradients. Turn off if your shape is already very complex, like text." ) ]
private bool _modifyVertices = true ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
[SerializeField] [ Range ( - 1 , 1 ) ] private float _offset ;
[SerializeField] [ Range ( 0.1f , 10 ) ] private float _zoom = 1f ;
[SerializeField] private UnityEngine . Gradient _effectGradient = new ( )
{ colorKeys = new [ ] { new GradientColorKey ( Color . black , 0 ) , new GradientColorKey ( Color . white , 1 ) } } ;
2025-10-03 00:02:43 -04:00
public override void ModifyMesh ( VertexHelper helper )
{
if ( ! IsActive ( ) | | helper . currentVertCount = = 0 )
return ;
2026-03-20 11:56:50 -04:00
var _vertexList = new List < UIVertex > ( ) ;
2025-10-03 00:02:43 -04:00
helper . GetUIVertexStream ( _vertexList ) ;
2026-03-20 11:56:50 -04:00
var nCount = _vertexList . Count ;
2025-10-03 00:02:43 -04:00
switch ( GradientType )
{
case Type . Horizontal :
case Type . Vertical :
2026-03-20 11:56:50 -04:00
{
var bounds = GetBounds ( _vertexList ) ;
var min = bounds . xMin ;
var w = bounds . width ;
Func < UIVertex , float > GetPosition = v = > v . position . x ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
if ( GradientType = = Type . Vertical )
{
min = bounds . yMin ;
w = bounds . height ;
GetPosition = v = > v . position . y ;
}
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
var width = w = = 0f ? 0f : 1f / w / Zoom ;
var zoomOffset = ( 1 - 1 / Zoom ) * 0.5f ;
var offset = Offset * ( 1 - zoomOffset ) - zoomOffset ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
if ( ModifyVertices ) SplitTrianglesAtGradientStops ( _vertexList , bounds , zoomOffset , helper ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
var vertex = new UIVertex ( ) ;
for ( var i = 0 ; i < helper . currentVertCount ; i + + )
{
helper . PopulateUIVertex ( ref vertex , i ) ;
vertex . color = BlendColor ( vertex . color ,
EffectGradient . Evaluate ( ( GetPosition ( vertex ) - min ) * width - offset ) ) ;
helper . SetUIVertex ( vertex , i ) ;
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
}
2025-10-03 00:02:43 -04:00
break ;
case Type . Diamond :
2026-03-20 11:56:50 -04:00
{
var bounds = GetBounds ( _vertexList ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
var height = bounds . height = = 0f ? 0f : 1f / bounds . height / Zoom ;
var radius = bounds . center . y / 2f ;
var center = ( Vector3 . right + Vector3 . up ) * radius + Vector3 . forward * _vertexList [ 0 ] . position . z ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
if ( ModifyVertices )
{
helper . Clear ( ) ;
for ( var i = 0 ; i < nCount ; i + + ) helper . AddVert ( _vertexList [ i ] ) ;
var centralVertex = new UIVertex ( ) ;
centralVertex . position = center ;
centralVertex . normal = _vertexList [ 0 ] . normal ;
centralVertex . uv0 = new Vector2 ( 0.5f , 0.5f ) ;
centralVertex . color = Color . white ;
helper . AddVert ( centralVertex ) ;
for ( var i = 1 ; i < nCount ; i + + ) helper . AddTriangle ( i - 1 , i , nCount ) ;
helper . AddTriangle ( 0 , nCount - 1 , nCount ) ;
}
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
var vertex = new UIVertex ( ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
for ( var i = 0 ; i < helper . currentVertCount ; i + + )
{
helper . PopulateUIVertex ( ref vertex , i ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
vertex . color = BlendColor ( vertex . color , EffectGradient . Evaluate (
Vector3 . Distance ( vertex . position , center ) * height - Offset ) ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
helper . SetUIVertex ( vertex , i ) ;
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
}
2025-10-03 00:02:43 -04:00
break ;
case Type . Radial :
2026-03-20 11:56:50 -04:00
{
var bounds = GetBounds ( _vertexList ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
var width = bounds . width = = 0f ? 0f : 1f / bounds . width / Zoom ;
var height = bounds . height = = 0f ? 0f : 1f / bounds . height / Zoom ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
if ( ModifyVertices )
{
helper . Clear ( ) ;
var radiusX = bounds . width / 2f ;
var radiusY = bounds . height / 2f ;
var centralVertex = new UIVertex ( ) ;
centralVertex . position = Vector3 . right * bounds . center . x + Vector3 . up * bounds . center . y +
Vector3 . forward * _vertexList [ 0 ] . position . z ;
centralVertex . normal = _vertexList [ 0 ] . normal ;
centralVertex . uv0 = new Vector2 ( 0.5f , 0.5f ) ;
centralVertex . color = Color . white ;
var steps = 64 ;
for ( var i = 0 ; i < steps ; i + + )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var curVertex = new UIVertex ( ) ;
var angle = i * 360f / steps ;
var cosX = Mathf . Cos ( Mathf . Deg2Rad * angle ) ;
var cosY = Mathf . Sin ( Mathf . Deg2Rad * angle ) ;
curVertex . position = Vector3 . right * cosX * radiusX + Vector3 . up * cosY * radiusY +
Vector3 . forward * _vertexList [ 0 ] . position . z ;
curVertex . normal = _vertexList [ 0 ] . normal ;
curVertex . uv0 = new Vector2 ( ( cosX + 1 ) * 0.5f , ( cosY + 1 ) * 0.5f ) ;
curVertex . color = Color . white ;
helper . AddVert ( curVertex ) ;
}
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
helper . AddVert ( centralVertex ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
for ( var i = 1 ; i < steps ; i + + ) helper . AddTriangle ( i - 1 , i , steps ) ;
helper . AddTriangle ( 0 , steps - 1 , steps ) ;
}
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
var vertex = new UIVertex ( ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
for ( var i = 0 ; i < helper . currentVertCount ; i + + )
{
helper . PopulateUIVertex ( ref vertex , i ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
vertex . color = BlendColor ( vertex . color , EffectGradient . Evaluate (
Mathf . Sqrt (
Mathf . Pow ( Mathf . Abs ( vertex . position . x - bounds . center . x ) * width , 2f ) +
Mathf . Pow ( Mathf . Abs ( vertex . position . y - bounds . center . y ) * height , 2f ) ) * 2f - Offset ) ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
helper . SetUIVertex ( vertex , i ) ;
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
}
2025-10-03 00:02:43 -04:00
break ;
}
}
2026-03-20 11:56:50 -04:00
private Rect GetBounds ( List < UIVertex > vertices )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var left = vertices [ 0 ] . position . x ;
var right = left ;
var bottom = vertices [ 0 ] . position . y ;
var top = bottom ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
for ( var i = vertices . Count - 1 ; i > = 1 ; - - i )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var x = vertices [ i ] . position . x ;
var y = vertices [ i ] . position . y ;
2025-10-03 00:02:43 -04:00
if ( x > right ) right = x ;
else if ( x < left ) left = x ;
if ( y > top ) top = y ;
else if ( y < bottom ) bottom = y ;
}
return new Rect ( left , bottom , right - left , top - bottom ) ;
}
2026-03-20 11:56:50 -04:00
private void SplitTrianglesAtGradientStops ( List < UIVertex > _vertexList , Rect bounds , float zoomOffset ,
VertexHelper helper )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var stops = FindStops ( zoomOffset , bounds ) ;
2025-10-03 00:02:43 -04:00
if ( stops . Count > 0 )
{
helper . Clear ( ) ;
2026-03-20 11:56:50 -04:00
var nCount = _vertexList . Count ;
for ( var i = 0 ; i < nCount ; i + = 3 )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var positions = GetPositions ( _vertexList , i ) ;
var originIndices = new List < int > ( 3 ) ;
var starts = new List < UIVertex > ( 3 ) ;
var ends = new List < UIVertex > ( 2 ) ;
2025-10-03 00:02:43 -04:00
2026-03-20 11:56:50 -04:00
for ( var s = 0 ; s < stops . Count ; s + + )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var initialCount = helper . currentVertCount ;
var hadEnds = ends . Count > 0 ;
var earlyStart = false ;
2025-10-03 00:02:43 -04:00
// find any start vertices for this stop
2026-03-20 11:56:50 -04:00
for ( var p = 0 ; p < 3 ; p + + )
2025-10-03 00:02:43 -04:00
if ( ! originIndices . Contains ( p ) & & positions [ p ] < stops [ s ] )
{
// make sure the first index crosses the stop
2026-03-20 11:56:50 -04:00
var p1 = ( p + 1 ) % 3 ;
2025-10-03 00:02:43 -04:00
var start = _vertexList [ p + i ] ;
if ( positions [ p1 ] > stops [ s ] )
{
originIndices . Insert ( 0 , p ) ;
starts . Insert ( 0 , start ) ;
earlyStart = true ;
}
else
{
originIndices . Add ( p ) ;
starts . Add ( start ) ;
}
}
// bail if all before or after the stop
if ( originIndices . Count = = 0 )
continue ;
if ( originIndices . Count = = 3 )
break ;
// report any start vertices
foreach ( var start in starts )
helper . AddVert ( start ) ;
// make two ends, splitting at the stop
ends . Clear ( ) ;
2026-03-20 11:56:50 -04:00
foreach ( var index in originIndices )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var oppositeIndex = ( index + 1 ) % 3 ;
2025-10-03 00:02:43 -04:00
if ( positions [ oppositeIndex ] < stops [ s ] )
oppositeIndex = ( oppositeIndex + 1 ) % 3 ;
2026-03-20 11:56:50 -04:00
ends . Add (
CreateSplitVertex ( _vertexList [ index + i ] , _vertexList [ oppositeIndex + i ] , stops [ s ] ) ) ;
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
2025-10-03 00:02:43 -04:00
if ( ends . Count = = 1 )
{
2026-03-20 11:56:50 -04:00
var oppositeIndex = ( originIndices [ 0 ] + 2 ) % 3 ;
ends . Add ( CreateSplitVertex ( _vertexList [ originIndices [ 0 ] + i ] ,
_vertexList [ oppositeIndex + i ] , stops [ s ] ) ) ;
2025-10-03 00:02:43 -04:00
}
// report end vertices
foreach ( var end in ends )
helper . AddVert ( end ) ;
// make triangles
if ( hadEnds )
{
helper . AddTriangle ( initialCount - 2 , initialCount , initialCount + 1 ) ;
helper . AddTriangle ( initialCount - 2 , initialCount + 1 , initialCount - 1 ) ;
if ( starts . Count > 0 )
{
if ( earlyStart )
helper . AddTriangle ( initialCount - 2 , initialCount + 3 , initialCount ) ;
else
helper . AddTriangle ( initialCount + 1 , initialCount + 3 , initialCount - 1 ) ;
}
}
else
{
2026-03-20 11:56:50 -04:00
var vertexCount = helper . currentVertCount ;
2025-10-03 00:02:43 -04:00
helper . AddTriangle ( initialCount , vertexCount - 2 , vertexCount - 1 ) ;
if ( starts . Count > 1 )
helper . AddTriangle ( initialCount , vertexCount - 1 , initialCount + 1 ) ;
}
starts . Clear ( ) ;
}
// clean up after looping through gradient stops
if ( ends . Count > 0 )
{
// find any final vertices after the gradient stops
if ( starts . Count = = 0 )
2026-03-20 11:56:50 -04:00
for ( var p = 0 ; p < 3 ; p + + )
2025-10-03 00:02:43 -04:00
if ( ! originIndices . Contains ( p ) & & positions [ p ] > stops [ stops . Count - 1 ] )
{
2026-03-20 11:56:50 -04:00
var p1 = ( p + 1 ) % 3 ;
var end = _vertexList [ p + i ] ;
2025-10-03 00:02:43 -04:00
if ( positions [ p1 ] > stops [ stops . Count - 1 ] )
starts . Insert ( 0 , end ) ;
else
starts . Add ( end ) ;
}
// report final vertices
foreach ( var start in starts )
helper . AddVert ( start ) ;
// make final triangle(s)
2026-03-20 11:56:50 -04:00
var vertexCount = helper . currentVertCount ;
2025-10-03 00:02:43 -04:00
if ( starts . Count > 1 )
{
helper . AddTriangle ( vertexCount - 4 , vertexCount - 2 , vertexCount - 1 ) ;
helper . AddTriangle ( vertexCount - 4 , vertexCount - 1 , vertexCount - 3 ) ;
}
else if ( starts . Count > 0 )
{
helper . AddTriangle ( vertexCount - 3 , vertexCount - 1 , vertexCount - 2 ) ;
}
}
else
{
// if the triangle wasn't split, add it as-is
helper . AddVert ( _vertexList [ i ] ) ;
helper . AddVert ( _vertexList [ i + 1 ] ) ;
helper . AddVert ( _vertexList [ i + 2 ] ) ;
2026-03-20 11:56:50 -04:00
var vertexCount = helper . currentVertCount ;
2025-10-03 00:02:43 -04:00
helper . AddTriangle ( vertexCount - 3 , vertexCount - 2 , vertexCount - 1 ) ;
}
}
}
}
2026-03-20 11:56:50 -04:00
private float [ ] GetPositions ( List < UIVertex > _vertexList , int index )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var positions = new float [ 3 ] ;
2025-10-03 00:02:43 -04:00
if ( GradientType = = Type . Horizontal )
{
positions [ 0 ] = _vertexList [ index ] . position . x ;
positions [ 1 ] = _vertexList [ index + 1 ] . position . x ;
positions [ 2 ] = _vertexList [ index + 2 ] . position . x ;
}
else
{
positions [ 0 ] = _vertexList [ index ] . position . y ;
positions [ 1 ] = _vertexList [ index + 1 ] . position . y ;
positions [ 2 ] = _vertexList [ index + 2 ] . position . y ;
}
2026-03-20 11:56:50 -04:00
2025-10-03 00:02:43 -04:00
return positions ;
}
2026-03-20 11:56:50 -04:00
private List < float > FindStops ( float zoomOffset , Rect bounds )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
var stops = new List < float > ( ) ;
2025-10-03 00:02:43 -04:00
var offset = Offset * ( 1 - zoomOffset ) ;
var startBoundary = zoomOffset - offset ;
2026-03-20 11:56:50 -04:00
var endBoundary = 1 - zoomOffset - offset ;
2025-10-03 00:02:43 -04:00
foreach ( var color in EffectGradient . colorKeys )
{
if ( color . time > = endBoundary )
break ;
if ( color . time > startBoundary )
stops . Add ( ( color . time - startBoundary ) * Zoom ) ;
}
2026-03-20 11:56:50 -04:00
2025-10-03 00:02:43 -04:00
foreach ( var alpha in EffectGradient . alphaKeys )
{
if ( alpha . time > = endBoundary )
break ;
if ( alpha . time > startBoundary )
stops . Add ( ( alpha . time - startBoundary ) * Zoom ) ;
}
2026-03-20 11:56:50 -04:00
var min = bounds . xMin ;
var size = bounds . width ;
2025-10-03 00:02:43 -04:00
if ( GradientType = = Type . Vertical )
{
min = bounds . yMin ;
size = bounds . height ;
}
stops . Sort ( ) ;
2026-03-20 11:56:50 -04:00
for ( var i = 0 ; i < stops . Count ; i + + )
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
stops [ i ] = stops [ i ] * size + min ;
2025-10-03 00:02:43 -04:00
if ( i > 0 & & Math . Abs ( stops [ i ] - stops [ i - 1 ] ) < 2 )
{
stops . RemoveAt ( i ) ;
- - i ;
}
}
return stops ;
}
2026-03-20 11:56:50 -04:00
private UIVertex CreateSplitVertex ( UIVertex vertex1 , UIVertex vertex2 , float stop )
2025-10-03 00:02:43 -04:00
{
if ( GradientType = = Type . Horizontal )
{
2026-03-20 11:56:50 -04:00
var sx = vertex1 . position . x - stop ;
var dx = vertex1 . position . x - vertex2 . position . x ;
var dy = vertex1 . position . y - vertex2 . position . y ;
var uvx = vertex1 . uv0 . x - vertex2 . uv0 . x ;
var uvy = vertex1 . uv0 . y - vertex2 . uv0 . y ;
var ratio = sx / dx ;
var splitY = vertex1 . position . y - dy * ratio ;
var splitVertex = new UIVertex ( ) ;
2025-10-03 00:02:43 -04:00
splitVertex . position = new Vector3 ( stop , splitY , vertex1 . position . z ) ;
splitVertex . normal = vertex1 . normal ;
2026-03-20 11:56:50 -04:00
splitVertex . uv0 = new Vector2 ( vertex1 . uv0 . x - uvx * ratio , vertex1 . uv0 . y - uvy * ratio ) ;
2025-10-03 00:02:43 -04:00
splitVertex . color = Color . white ;
return splitVertex ;
}
else
{
2026-03-20 11:56:50 -04:00
var sy = vertex1 . position . y - stop ;
var dy = vertex1 . position . y - vertex2 . position . y ;
var dx = vertex1 . position . x - vertex2 . position . x ;
var uvx = vertex1 . uv0 . x - vertex2 . uv0 . x ;
var uvy = vertex1 . uv0 . y - vertex2 . uv0 . y ;
var ratio = sy / dy ;
var splitX = vertex1 . position . x - dx * ratio ;
var splitVertex = new UIVertex ( ) ;
2025-10-03 00:02:43 -04:00
splitVertex . position = new Vector3 ( splitX , stop , vertex1 . position . z ) ;
splitVertex . normal = vertex1 . normal ;
2026-03-20 11:56:50 -04:00
splitVertex . uv0 = new Vector2 ( vertex1 . uv0 . x - uvx * ratio , vertex1 . uv0 . y - uvy * ratio ) ;
2025-10-03 00:02:43 -04:00
splitVertex . color = Color . white ;
return splitVertex ;
}
}
2026-03-20 11:56:50 -04:00
private Color BlendColor ( Color colorA , Color colorB )
2025-10-03 00:02:43 -04:00
{
switch ( BlendMode )
{
default : return colorB ;
case Blend . Add : return colorA + colorB ;
case Blend . Multiply : return colorA * colorB ;
}
}
2026-03-20 11:56:50 -04:00
#region Properties
public Blend BlendMode
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
get = > _blendMode ;
set
{
_blendMode = value ;
graphic . SetVerticesDirty ( ) ;
}
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
public UnityEngine . Gradient EffectGradient
2025-10-03 00:02:43 -04:00
{
2026-03-20 11:56:50 -04:00
get = > _effectGradient ;
set
{
_effectGradient = value ;
graphic . SetVerticesDirty ( ) ;
}
}
public Type GradientType
{
get = > _gradientType ;
set
{
_gradientType = value ;
graphic . SetVerticesDirty ( ) ;
}
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
public bool ModifyVertices
{
get = > _modifyVertices ;
set
{
_modifyVertices = value ;
graphic . SetVerticesDirty ( ) ;
}
}
public float Offset
{
get = > _offset ;
set
{
_offset = Mathf . Clamp ( value , - 1f , 1f ) ;
graphic . SetVerticesDirty ( ) ;
}
}
public float Zoom
{
get = > _zoom ;
set
{
_zoom = Mathf . Clamp ( value , 0.1f , 10f ) ;
graphic . SetVerticesDirty ( ) ;
}
}
#endregion
2025-10-03 00:02:43 -04:00
}
}