using System; using System.Collections.Generic; using System.Reflection; using Coffee.UIEffectInternal; using UnityEngine; using UnityEngine.Profiling; using UnityEngine.Rendering; using UnityEngine.UI; namespace Coffee.UIEffects { public sealed class UIEffectContext { private static readonly UIEffectContext s_DefaultContext = new UIEffectContext(); private static readonly List s_WorkingVertices = new List(1024 * 8); private static readonly int s_SrcBlend = Shader.PropertyToID("_SrcBlend"); private static readonly int s_DstBlend = Shader.PropertyToID("_DstBlend"); private static readonly int s_ToneIntensity = Shader.PropertyToID("_ToneIntensity"); private static readonly int s_ColorFilter = Shader.PropertyToID("_ColorFilter"); private static readonly int s_ColorValue = Shader.PropertyToID("_ColorValue"); private static readonly int s_ColorIntensity = Shader.PropertyToID("_ColorIntensity"); private static readonly int s_ColorGlow = Shader.PropertyToID("_ColorGlow"); private static readonly int s_SamplingIntensity = Shader.PropertyToID("_SamplingIntensity"); private static readonly int s_SamplingWidth = Shader.PropertyToID("_SamplingWidth"); private static readonly int s_SamplingScale = Shader.PropertyToID("_SamplingScale"); private static readonly int s_TransitionRate = Shader.PropertyToID("_TransitionRate"); private static readonly int s_TransitionReverse = Shader.PropertyToID("_TransitionReverse"); private static readonly int s_TransitionTex = Shader.PropertyToID("_TransitionTex"); private static readonly int s_TransitionTex_ST = Shader.PropertyToID("_TransitionTex_ST"); private static readonly int s_TransitionTex_Speed = Shader.PropertyToID("_TransitionTex_Speed"); private static readonly int s_TransitionWidth = Shader.PropertyToID("_TransitionWidth"); private static readonly int s_TransitionSoftness = Shader.PropertyToID("_TransitionSoftness"); private static readonly int s_TransitionRange = Shader.PropertyToID("_TransitionRange"); private static readonly int s_TransitionColorFilter = Shader.PropertyToID("_TransitionColorFilter"); private static readonly int s_TransitionColor = Shader.PropertyToID("_TransitionColor"); private static readonly int s_TransitionColorGlow = Shader.PropertyToID("_TransitionColorGlow"); private static readonly int s_TransitionPatternReverse = Shader.PropertyToID("_TransitionPatternReverse"); private static readonly int s_TransitionAutoPlaySpeed = Shader.PropertyToID("_TransitionAutoPlaySpeed"); private static readonly int s_TransitionGradientTex = Shader.PropertyToID("_TransitionGradientTex"); private static readonly int s_TargetColor = Shader.PropertyToID("_TargetColor"); private static readonly int s_TargetRange = Shader.PropertyToID("_TargetRange"); private static readonly int s_TargetSoftness = Shader.PropertyToID("_TargetSoftness"); private static readonly int s_ShadowColorFilter = Shader.PropertyToID("_ShadowColorFilter"); private static readonly int s_ShadowColor = Shader.PropertyToID("_ShadowColor"); private static readonly int s_ShadowBlurIntensity = Shader.PropertyToID("_ShadowBlurIntensity"); private static readonly int s_ShadowColorGlow = Shader.PropertyToID("_ShadowColorGlow"); private static readonly int s_EdgeWidth = Shader.PropertyToID("_EdgeWidth"); private static readonly int s_EdgeColorFilter = Shader.PropertyToID("_EdgeColorFilter"); private static readonly int s_EdgeColor = Shader.PropertyToID("_EdgeColor"); private static readonly int s_EdgeColorGlow = Shader.PropertyToID("_EdgeColorGlow"); private static readonly int s_EdgeShinyAutoPlaySpeed = Shader.PropertyToID("_EdgeShinyAutoPlaySpeed"); private static readonly int s_EdgeShinyRate = Shader.PropertyToID("_EdgeShinyRate"); private static readonly int s_EdgeShinyWidth = Shader.PropertyToID("_EdgeShinyWidth"); private static readonly int s_PatternArea = Shader.PropertyToID("_PatternArea"); private static readonly int s_DetailIntensity = Shader.PropertyToID("_DetailIntensity"); private static readonly int s_DetailThreshold = Shader.PropertyToID("_DetailThreshold"); private static readonly int s_DetailColor = Shader.PropertyToID("_DetailColor"); private static readonly int s_DetailTex = Shader.PropertyToID("_DetailTex"); private static readonly int s_DetailTex_ST = Shader.PropertyToID("_DetailTex_ST"); private static readonly int s_DetailTex_Speed = Shader.PropertyToID("_DetailTex_Speed"); private static readonly int s_GradationIntensity = Shader.PropertyToID("_GradationIntensity"); private static readonly int s_GradationColorFilter = Shader.PropertyToID("_GradationColorFilter"); private static readonly int s_GradationColor1 = Shader.PropertyToID("_GradationColor1"); private static readonly int s_GradationColor2 = Shader.PropertyToID("_GradationColor2"); private static readonly int s_GradationColor3 = Shader.PropertyToID("_GradationColor3"); private static readonly int s_GradationColor4 = Shader.PropertyToID("_GradationColor4"); private static readonly int s_GradationTex = Shader.PropertyToID("_GradationTex"); private static readonly int s_GradationTex_ST = Shader.PropertyToID("_GradationTex_ST"); private static readonly int s_GradationRadial = Shader.PropertyToID("_GradationRadial"); private static readonly int s_RootViewMatrix = Shader.PropertyToID("_RootViewMatrix"); private static readonly int s_GradViewMatrix = Shader.PropertyToID("_GradViewMatrix"); private static readonly int s_MirrorRootViewMatrix = Shader.PropertyToID("_MirrorRootViewMatrix"); private static readonly int s_MirrorGradViewMatrix = Shader.PropertyToID("_MirrorGradViewMatrix"); private static readonly int s_CanvasToWorldMatrix = Shader.PropertyToID("_CanvasToWorldMatrix"); private static readonly string[] s_ToneKeywords = { "", "TONE_GRAYSCALE", "TONE_SEPIA", "TONE_NEGATIVE", "TONE_RETRO", "TONE_POSTERIZE" }; private static readonly string[] s_ColorKeywords = { "", "COLOR_FILTER" }; private static readonly string[] s_SamplingKeywords = { "", "SAMPLING_BLUR_FAST", "SAMPLING_BLUR_MEDIUM", "SAMPLING_BLUR_DETAIL", "SAMPLING_PIXELATION", "SAMPLING_RGB_SHIFT", "SAMPLING_EDGE_LUMINANCE", "SAMPLING_EDGE_ALPHA" }; private static readonly string[] s_TransitionKeywords = { "", "TRANSITION_FADE", "TRANSITION_CUTOFF", "TRANSITION_DISSOLVE", "TRANSITION_SHINY", "TRANSITION_MASK", "TRANSITION_MELT", "TRANSITION_BURN", "TRANSITION_PATTERN", "TRANSITION_BLAZE" }; private static readonly string[] s_TargetKeywords = { "", "TARGET_HUE", "TARGET_LUMINANCE" }; private static readonly string[] s_EdgeKeywords = { "", "EDGE_PLAIN", "EDGE_SHINY" }; private static readonly string[] s_DetailKeywords = { "", "DETAIL_MASKING", "DETAIL_MULTIPLY", "DETAIL_ADDITIVE", "DETAIL_REPLACE", "DETAIL_MULTIPLY_ADDITIVE", "DETAIL_SUBTRACTIVE" }; private static readonly string[] s_GradationKeywords = { "", "GRADATION_GRADIENT", "GRADATION_COLOR2", "GRADATION_COLOR4" }; private static readonly Vector2[][] s_ShadowVectors = new[] { Array.Empty(), // None new[] { Vector2.one }, // Shadow new[] { Vector2.one, Vector2.right, Vector2.up }, // Shadow3 new[] { Vector2.one, -Vector2.one, new Vector2(1, -1), new Vector2(-1, 1) }, // Outline new[] { Vector2.one, -Vector2.one, new Vector2(1, -1), new Vector2(-1, 1), Vector2.right, Vector2.up, Vector2.left, Vector2.down } // Outline8 }; public static Func onModifyVertex; public ToneFilter m_ToneFilter; public float m_ToneIntensity; public ColorFilter m_ColorFilter; public float m_ColorIntensity; public Color m_Color; public bool m_ColorGlow; public SamplingFilter m_SamplingFilter; public float m_SamplingIntensity; public float m_SamplingWidth; public TransitionFilter m_TransitionFilter; public float m_TransitionRate; public bool m_TransitionReverse; public Texture m_TransitionTex; public Vector2 m_TransitionTexScale; public Vector2 m_TransitionTexOffset; public Vector2 m_TransitionTexSpeed; public float m_TransitionRotation; public bool m_TransitionKeepAspectRatio; public float m_TransitionWidth; public float m_TransitionSoftness; public MinMax01 m_TransitionRange; public ColorFilter m_TransitionColorFilter; public Color m_TransitionColor; public bool m_TransitionColorGlow; public bool m_TransitionPatternReverse; public float m_TransitionAutoPlaySpeed; public Gradient m_TransitionGradient; public TargetMode m_TargetMode; public Color m_TargetColor; public float m_TargetRange; public float m_TargetSoftness; public BlendMode m_SrcBlendMode; public BlendMode m_DstBlendMode; public ShadowMode m_ShadowMode; public Vector2 m_ShadowDistance; public int m_ShadowIteration; public float m_ShadowFade; public float m_ShadowMirrorScale; public float m_ShadowBlurIntensity; public ColorFilter m_ShadowColorFilter; public Color m_ShadowColor; public bool m_ShadowColorGlow; public EdgeMode m_EdgeMode; public float m_EdgeShinyRate; public float m_EdgeWidth; public ColorFilter m_EdgeColorFilter; public Color m_EdgeColor; public bool m_EdgeColorGlow; public float m_EdgeShinyWidth; public float m_EdgeShinyAutoPlaySpeed; public PatternArea m_PatternArea; public GradationMode m_GradationMode; public float m_GradationIntensity; public GradationColorFilter m_GradationColorFilter; public Color m_GradationColor1; public Color m_GradationColor2; public Color m_GradationColor3; public Color m_GradationColor4; public Gradient m_GradationGradient; public float m_GradationOffset; public float m_GradationScale; public float m_GradationRotation; public TextureWrapMode m_GradationWrapMode; public bool m_GradationReverse; public DetailFilter m_DetailFilter; public float m_DetailIntensity; public MinMax01 m_DetailThreshold; public Color m_DetailColor; public Texture m_DetailTex; public Vector2 m_DetailTexScale; public Vector2 m_DetailTexOffset; public Vector2 m_DetailTexSpeed; public Flip m_Flip; public bool willModifyMaterial => m_ToneFilter != ToneFilter.None || m_ColorFilter != ColorFilter.None || m_SamplingFilter != SamplingFilter.None || m_TransitionFilter != TransitionFilter.None || m_SrcBlendMode != BlendMode.One || m_DstBlendMode != BlendMode.OneMinusSrcAlpha || m_ShadowMode != ShadowMode.None || m_EdgeMode != EdgeMode.None || m_DetailFilter != DetailFilter.None || m_GradationMode != GradationMode.None; public bool willModifyVertex => willModifyMaterial; public bool useViewMatrix => m_TransitionFilter != TransitionFilter.None || m_GradationMode != GradationMode.None || m_DetailFilter != DetailFilter.None || m_EdgeMode == EdgeMode.Shiny; /// /// 特效配置的稳定指纹。自动遍历所有序列化配置字段生成,因此插件新增属性时无需手动维护字段清单。 /// 相同配置会得到相同指纹,从而可以共享同一个材质并参与合批。 /// public uint configId { get { unchecked { var h = (uint)2166136261; var fields = s_ConfigFields; for (var i = 0; i < fields.Length; i++) { var f = fields[i]; h = Mix(h, (uint)f.Name.GetHashCode()); h = Mix(h, HashValue(f.GetValue(this))); } // Avalanche 混淆(MurmurHash3 终混),让相近配置散列到不同指纹。 h ^= h >> 16; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; h ^= h >> 16; return h; } } } private static uint Mix(uint h, uint v) { h ^= v; h *= 16777619; return h; } /// /// 对任意配置值做内容哈希。值类型会逐字段展开,嵌套结构体(Color、Vector2/3/4、MinMax01 等) /// 无需单独写类型处理即可贡献全部数据。只有 UnityEngine.Object 和 Gradient 需要显式处理, /// 因为它们的默认哈希基于引用而非内容。 /// private static uint HashValue(object value) { if (value == null) return 0u; if (value is UnityEngine.Object obj) { return obj ? (uint)obj.GetInstanceID() : 0u; } if (value is Gradient gradient) { return GradientHash(gradient); } var type = value.GetType(); if (type.IsPrimitive || type.IsEnum) { return unchecked((uint)value.GetHashCode()); } if (type.IsValueType) { return HashFields(value, type); } return unchecked((uint)value.GetHashCode()); } private static uint HashFields(object boxed, Type type) { unchecked { if (!s_FieldCache.TryGetValue(type, out var fields)) { fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); s_FieldCache[type] = fields; } var h = (uint)2166136261; for (var i = 0; i < fields.Length; i++) { h = Mix(h, HashValue(fields[i].GetValue(boxed))); } return h; } } private static uint GradientHash(Gradient gradient) { unchecked { var h = (uint)2166136261; var colorKeys = gradient.colorKeys; for (var i = 0; i < colorKeys.Length; i++) { h = Mix(h, HashValue(colorKeys[i].color)); h = Mix(h, HashValue(colorKeys[i].time)); } var alphaKeys = gradient.alphaKeys; for (var i = 0; i < alphaKeys.Length; i++) { h = Mix(h, HashValue(alphaKeys[i].alpha)); h = Mix(h, HashValue(alphaKeys[i].time)); } return h; } } private static readonly FieldInfo[] s_ConfigFields = typeof(UIEffectContext) .GetFields(BindingFlags.Public | BindingFlags.Instance); private static readonly Dictionary s_FieldCache = new Dictionary(); private Texture2D _gradationRampTex; private Texture2D _transitionRampTex; private bool _isGradientDirty = true; private bool _isTransitionGradientDirty = true; private static readonly Color[] s_Colors = new Color[256]; private static readonly InternalObjectPool s_TexturePool = new InternalObjectPool( () => { var texture = new Texture2D(s_Colors.Length, 1, TextureFormat.RGBAFloat, false, false) { name = "GradationRamp", hideFlags = HideFlags.DontSave, wrapMode = TextureWrapMode.Repeat, anisoLevel = 0 }; return texture; }, texture => texture, _ => { }); private Texture2D gradationRampTex { get { if (m_GradationGradient == null) return null; if (_gradationRampTex == null) _gradationRampTex = s_TexturePool.Rent(); if (!_isGradientDirty) return _gradationRampTex; _isGradientDirty = false; var w = s_Colors.Length; for (var i = 0; i < w; i++) { var x = m_GradationReverse ? w - 1 - i : i; var t = (float)i / (w - 1); s_Colors[x] = ApplyColorSpace(m_GradationGradient.Evaluate(t)); } _gradationRampTex.wrapMode = m_GradationWrapMode; _gradationRampTex.filterMode = m_GradationGradient.mode == GradientMode.Blend ? FilterMode.Bilinear : FilterMode.Point; _gradationRampTex.SetPixels(s_Colors); _gradationRampTex.Apply(); return _gradationRampTex; } } private Texture2D transitionRampTex { get { if (m_TransitionGradient == null) return null; if (_transitionRampTex == null) _transitionRampTex = s_TexturePool.Rent(); if (!_isTransitionGradientDirty) return _transitionRampTex; _isTransitionGradientDirty = false; var w = s_Colors.Length; for (var i = 0; i < w; i++) { var t = (float)i / (w - 1); s_Colors[i] = ApplyColorSpace(m_TransitionGradient.Evaluate(t)); } s_Colors[w - 1].a = 0; _transitionRampTex.wrapMode = TextureWrapMode.Clamp; _transitionRampTex.filterMode = m_TransitionGradient.mode == GradientMode.Blend ? FilterMode.Bilinear : FilterMode.Point; _transitionRampTex.SetPixels(s_Colors); _transitionRampTex.Apply(); return _transitionRampTex; } } public void Reset() { _isGradientDirty = true; _isTransitionGradientDirty = true; s_TexturePool.Return(ref _gradationRampTex); s_TexturePool.Return(ref _transitionRampTex); CopyFrom(s_DefaultContext); } private void CopyFrom(UIEffectContext src) { var dst = this; dst.m_ToneFilter = src.m_ToneFilter; dst.m_ToneIntensity = src.m_ToneIntensity; dst.m_ColorFilter = src.m_ColorFilter; dst.m_Color = src.m_Color; dst.m_ColorIntensity = src.m_ColorIntensity; dst.m_ColorGlow = src.m_ColorGlow; dst.m_SamplingFilter = src.m_SamplingFilter; dst.m_SamplingIntensity = src.m_SamplingIntensity; dst.m_SamplingWidth = src.m_SamplingWidth; dst.m_TransitionFilter = src.m_TransitionFilter; dst.m_TransitionRate = src.m_TransitionRate; dst.m_TransitionReverse = src.m_TransitionReverse; dst.m_TransitionTex = src.m_TransitionTex; dst.m_TransitionTexScale = src.m_TransitionTexScale; dst.m_TransitionTexOffset = src.m_TransitionTexOffset; dst.m_TransitionTexSpeed = src.m_TransitionTexSpeed; dst.m_TransitionKeepAspectRatio = src.m_TransitionKeepAspectRatio; dst.m_TransitionRotation = src.m_TransitionRotation; dst.m_TransitionWidth = src.m_TransitionWidth; dst.m_TransitionSoftness = src.m_TransitionSoftness; dst.m_TransitionRange = src.m_TransitionRange; dst.m_TransitionColor = src.m_TransitionColor; dst.m_TransitionColorFilter = src.m_TransitionColorFilter; dst.m_TransitionColorGlow = src.m_TransitionColorGlow; dst.m_TransitionPatternReverse = src.m_TransitionPatternReverse; dst.m_TransitionAutoPlaySpeed = src.m_TransitionAutoPlaySpeed; dst.m_TransitionGradient = src.m_TransitionGradient; dst.m_TargetMode = src.m_TargetMode; dst.m_TargetColor = src.m_TargetColor; dst.m_TargetRange = src.m_TargetRange; dst.m_TargetSoftness = src.m_TargetSoftness; dst.m_SrcBlendMode = src.m_SrcBlendMode; dst.m_DstBlendMode = src.m_DstBlendMode; dst.m_ShadowMode = src.m_ShadowMode; dst.m_ShadowDistance = src.m_ShadowDistance; dst.m_ShadowIteration = src.m_ShadowIteration; dst.m_ShadowFade = src.m_ShadowFade; dst.m_ShadowMirrorScale = src.m_ShadowMirrorScale; dst.m_ShadowBlurIntensity = src.m_ShadowBlurIntensity; dst.m_ShadowColorFilter = src.m_ShadowColorFilter; dst.m_ShadowColor = src.m_ShadowColor; dst.m_ShadowColorGlow = src.m_ShadowColorGlow; dst.m_EdgeMode = src.m_EdgeMode; dst.m_EdgeShinyRate = src.m_EdgeShinyRate; dst.m_EdgeWidth = src.m_EdgeWidth; dst.m_EdgeColorFilter = src.m_EdgeColorFilter; dst.m_EdgeColor = src.m_EdgeColor; dst.m_EdgeColorGlow = src.m_EdgeColorGlow; dst.m_EdgeShinyAutoPlaySpeed = src.m_EdgeShinyAutoPlaySpeed; dst.m_EdgeShinyWidth = src.m_EdgeShinyWidth; dst.m_PatternArea = src.m_PatternArea; dst.m_GradationMode = src.m_GradationMode; dst.m_GradationIntensity = src.m_GradationIntensity; dst.m_GradationColorFilter = src.m_GradationColorFilter; dst.m_GradationColor1 = src.m_GradationColor1; dst.m_GradationColor2 = src.m_GradationColor2; dst.m_GradationColor3 = src.m_GradationColor3; dst.m_GradationColor4 = src.m_GradationColor4; dst.m_GradationGradient = src.m_GradationGradient; dst.m_GradationScale = src.m_GradationScale; dst.m_GradationOffset = src.m_GradationOffset; dst.m_GradationRotation = src.m_GradationRotation; dst.m_GradationWrapMode = src.m_GradationWrapMode; dst.m_GradationReverse = src.m_GradationReverse; dst.m_DetailFilter = src.m_DetailFilter; dst.m_DetailIntensity = src.m_DetailIntensity; dst.m_DetailColor = src.m_DetailColor; dst.m_DetailThreshold = src.m_DetailThreshold; dst.m_DetailTex = src.m_DetailTex; dst.m_DetailTexScale = src.m_DetailTexScale; dst.m_DetailTexOffset = src.m_DetailTexOffset; dst.m_DetailTexSpeed = src.m_DetailTexSpeed; dst.m_Flip = src.m_Flip; } public void SetGradationDirty() { _isGradientDirty = true; } public void SetTransitionGradationDirty() { _isTransitionGradientDirty = true; } public void ApplyToMaterial(Material material, float actualSamplingScale = 1f) { if (material == null) return; Profiler.BeginSample("(UIE)[UIEffect] ApplyToMaterial"); material.SetInt(s_SrcBlend, (int)m_SrcBlendMode); material.SetInt(s_DstBlend, (int)m_DstBlendMode); material.SetFloat(s_ToneIntensity, Mathf.Clamp01(m_ToneIntensity)); material.SetInt(s_ColorFilter, (int)m_ColorFilter); material.SetColor(s_ColorValue, ApplyColorSpace(m_Color)); material.SetFloat(s_ColorIntensity, Mathf.Clamp01(m_ColorIntensity)); material.SetInt(s_ColorGlow, m_ColorGlow ? 1 : 0); material.SetFloat(s_SamplingIntensity, Mathf.Clamp01(m_SamplingIntensity)); material.SetFloat(s_SamplingWidth, m_SamplingWidth); material.SetFloat(s_SamplingScale, actualSamplingScale); material.SetFloat(s_TransitionRate, Mathf.Clamp01(m_TransitionRate)); material.SetInt(s_TransitionReverse, m_TransitionReverse ? 1 : 0); material.SetTexture(s_TransitionTex, m_TransitionFilter != 0 ? m_TransitionTex : null); material.SetVector(s_TransitionTex_ST, new Vector4(m_TransitionTexScale.x, m_TransitionTexScale.y, m_TransitionTexOffset.x, m_TransitionTexOffset.y)); material.SetVector(s_TransitionTex_Speed, m_TransitionTexSpeed); material.SetFloat(s_TransitionWidth, Mathf.Clamp01(m_TransitionWidth)); material.SetFloat(s_TransitionSoftness, Mathf.Clamp01(m_TransitionSoftness)); material.SetVector(s_TransitionRange, new Vector2(m_TransitionRange.min, m_TransitionRange.max)); material.SetInt(s_TransitionColorFilter, (int)m_TransitionColorFilter); material.SetColor(s_TransitionColor, ApplyColorSpace(m_TransitionColor)); material.SetInt(s_TransitionColorGlow, m_TransitionColorGlow ? 1 : 0); material.SetInt(s_TransitionPatternReverse, m_TransitionPatternReverse ? 1 : 0); material.SetFloat(s_TransitionAutoPlaySpeed, m_TransitionAutoPlaySpeed); material.SetTexture(s_TransitionGradientTex, m_TransitionFilter == TransitionFilter.Blaze ? transitionRampTex : null); material.SetColor(s_TargetColor, ApplyColorSpace(m_TargetColor)); material.SetFloat(s_TargetRange, Mathf.Clamp01(m_TargetRange)); material.SetFloat(s_TargetSoftness, Mathf.Clamp01(m_TargetSoftness)); switch (m_SamplingFilter) { case SamplingFilter.BlurFast: case SamplingFilter.BlurMedium: case SamplingFilter.BlurDetail: material.SetFloat(s_ShadowBlurIntensity, Mathf.Clamp01(m_ShadowBlurIntensity)); break; default: material.SetFloat(s_ShadowBlurIntensity, Mathf.Clamp01(m_SamplingIntensity)); break; } material.SetInt(s_ShadowColorFilter, (int)m_ShadowColorFilter); material.SetColor(s_ShadowColor, ApplyColorSpace(m_ShadowColor)); material.SetInt(s_ShadowColorGlow, m_ShadowColorGlow ? 1 : 0); material.SetFloat(s_EdgeWidth, Mathf.Clamp01(m_EdgeWidth)); material.SetInt(s_EdgeColorFilter, (int)m_EdgeColorFilter); material.SetColor(s_EdgeColor, ApplyColorSpace(m_EdgeColor)); material.SetInt(s_EdgeColorGlow, m_EdgeColorGlow ? 1 : 0); material.SetFloat(s_EdgeShinyRate, Mathf.Clamp01(m_EdgeShinyRate)); material.SetFloat(s_EdgeShinyWidth, Mathf.Clamp01(m_EdgeShinyWidth)); material.SetFloat(s_EdgeShinyAutoPlaySpeed, m_EdgeShinyAutoPlaySpeed); material.SetInt(s_PatternArea, m_EdgeMode != EdgeMode.None ? (int)m_PatternArea : 0); material.SetTexture(s_GradationTex, m_GradationMode != 0 ? gradationRampTex : null); material.SetVector(s_GradationTex_ST, GetGradationScaleAndOffset()); material.SetFloat(s_GradationIntensity, m_GradationIntensity); material.SetInt(s_GradationColorFilter, (int)m_GradationColorFilter); material.SetColor(s_GradationColor1, ApplyColorSpace(m_GradationColor1)); material.SetColor(s_GradationColor2, ApplyColorSpace(m_GradationColor2)); material.SetColor(s_GradationColor3, ApplyColorSpace(m_GradationColor3)); material.SetColor(s_GradationColor4, ApplyColorSpace(m_GradationColor4)); material.SetInt(s_GradationRadial, m_GradationMode == GradationMode.Radial #pragma warning disable CS0612 // Type or member is obsolete || m_GradationMode == GradationMode.RadialDetail #pragma warning restore CS0612 || m_GradationMode == GradationMode.RadialGradient ? 1 : 0); material.SetFloat(s_DetailIntensity, Mathf.Clamp01(m_DetailIntensity)); material.SetColor(s_DetailColor, ApplyColorSpace(m_DetailColor)); material.SetVector(s_DetailThreshold, new Vector2(m_DetailThreshold.min, m_DetailThreshold.max)); material.SetTexture(s_DetailTex, m_DetailFilter != 0 ? m_DetailTex : null); material.SetVector(s_DetailTex_ST, new Vector4(m_DetailTexScale.x, m_DetailTexScale.y, m_DetailTexOffset.x, m_DetailTexOffset.y)); material.SetVector(s_DetailTex_Speed, m_DetailTexSpeed); SetKeyword(material, s_ToneKeywords, (int)m_ToneFilter); SetKeyword(material, s_ColorKeywords, m_ColorFilter != 0 || m_ShadowMode != 0 ? 1 : 0); SetKeyword(material, s_SamplingKeywords, (int)m_SamplingFilter); SetKeyword(material, s_TransitionKeywords, (int)m_TransitionFilter); SetKeyword(material, s_EdgeKeywords, (int)m_EdgeMode); SetKeyword(material, s_DetailKeywords, (int)m_DetailFilter); SetKeyword(material, s_TargetKeywords, (int)m_TargetMode); switch (m_GradationMode) { case GradationMode.None: SetKeyword(material, s_GradationKeywords, 0); break; case GradationMode.HorizontalGradient: case GradationMode.VerticalGradient: case GradationMode.AngleGradient: case GradationMode.RadialGradient: SetKeyword(material, s_GradationKeywords, 1); break; case GradationMode.Horizontal: case GradationMode.Vertical: case GradationMode.DiagonalToRightBottom: case GradationMode.DiagonalToLeftBottom: case GradationMode.Angle: case GradationMode.Radial: #pragma warning disable CS0612 // Type or member is obsolete case GradationMode.RadialDetail: #pragma warning restore CS0612 SetKeyword(material, s_GradationKeywords, 2); break; case GradationMode.Diagonal: SetKeyword(material, s_GradationKeywords, 3); break; } Profiler.EndSample(); } public void SetEnablePreview(bool enable, Material material) { if (material == null) return; material.SetVector(s_TransitionTex_Speed, enable ? (Vector4)m_TransitionTexSpeed : Vector4.zero); material.SetVector(s_DetailTex_Speed, enable ? (Vector4)m_DetailTexSpeed : Vector4.zero); material.SetFloat(s_TransitionAutoPlaySpeed, enable ? m_TransitionAutoPlaySpeed : 0); material.SetFloat(s_EdgeShinyAutoPlaySpeed, enable ? m_EdgeShinyAutoPlaySpeed : 0); } public void UpdateViewMatrix(Material material, RectTransform transitionRoot, Canvas canvas) { if (material == null) return; var size = transitionRoot.rect.size; var scale = new Vector3(1f / size.x, 1f / size.y, 1f); if (m_TransitionKeepAspectRatio && !Mathf.Approximately(scale.x, scale.y)) { scale.x = scale.y = Mathf.Min(scale.x, scale.y); } if (0 != (m_Flip & Flip.Effect)) { scale.x = 0 != (m_Flip & Flip.Horizontal) ? -scale.x : scale.x; scale.y = 0 != (m_Flip & Flip.Vertical) ? -scale.y : scale.y; } var pivot = new Vector3(0.5f, 0.5f); var offset = (transitionRoot.pivot - (Vector2)pivot) * size; var w2LMat = Matrix4x4.Translate(offset) * transitionRoot.worldToLocalMatrix; var rootRotation = Quaternion.Euler(0, 0, m_TransitionRotation); var rootScale = 1f / GetMultiplier(m_TransitionRotation); material.SetMatrix(s_RootViewMatrix, Matrix4x4.TRS(pivot, rootRotation, scale * rootScale) * w2LMat); var rotation = GetGradationRotation(); var gradRotation = Quaternion.Euler(0, 0, rotation); var gradScale = 1f / GetMultiplier(rotation); material.SetMatrix(s_GradViewMatrix, Matrix4x4.TRS(pivot, gradRotation, scale * gradScale) * w2LMat); if (m_ShadowMode == ShadowMode.Mirror) { var mirrorInv = 1f / m_ShadowMirrorScale; var mirrorOffset = new Vector3(0, size.y / 2 * (mirrorInv + 1) - m_ShadowDistance.y * mirrorInv, 0); var mirrorScale = new Vector3(1, mirrorInv, 1); var udScale = new Vector3(scale.x, -scale.y, scale.z); var mirrorTranslation = Matrix4x4.TRS(mirrorOffset, Quaternion.identity, mirrorScale) * w2LMat; material.SetMatrix( s_MirrorRootViewMatrix, Matrix4x4.TRS(pivot, rootRotation, udScale * rootScale) * mirrorTranslation ); material.SetMatrix( s_MirrorGradViewMatrix, Matrix4x4.TRS(pivot, gradRotation, udScale * gradScale) * mirrorTranslation ); } var useIdentity = canvas.renderMode == RenderMode.WorldSpace || (canvas.renderMode != RenderMode.ScreenSpaceOverlay && canvas.worldCamera); material.SetMatrix( s_CanvasToWorldMatrix, useIdentity ? Matrix4x4.identity : canvas.transform.localToWorldMatrix ); } private Vector4 GetGradationScaleAndOffset() { var gScale = 1 / m_GradationScale; switch (m_GradationMode) { case GradationMode.HorizontalGradient: case GradationMode.VerticalGradient: case GradationMode.AngleGradient: return new Vector4(gScale, 1, -0.5f * (gScale + 1) - m_GradationOffset, 0); case GradationMode.Radial: #pragma warning disable CS0612 // Type or member is obsolete case GradationMode.RadialDetail: #pragma warning restore CS0612 { return new Vector4(gScale, 1, m_GradationOffset, 0); } default: return new Vector4(gScale, 1, m_GradationOffset * (gScale + 1) / 2 - gScale * 0.5f + 0.5f, 0); } } private float GetGradationRotation() { switch (m_GradationMode) { case GradationMode.DiagonalToLeftBottom: return 135; case GradationMode.DiagonalToRightBottom: return 45; case GradationMode.Vertical: return 90; case GradationMode.VerticalGradient: return -90; case GradationMode.Angle: case GradationMode.AngleGradient: return m_GradationRotation; default: return 0; } } private static float GetMultiplier(float deg) { var rad = Mathf.Deg2Rad * deg; var sin = Mathf.Sin(rad); var cos = Mathf.Cos(rad); return Mathf.Max(Mathf.Abs(cos - sin), Mathf.Abs(cos + sin)); } #if UNITY_2021_2_OR_NEWER private static void SetKeyword(Material material, Span keywords, int index) #else private static void SetKeyword(Material material, string[] keywords, int index) #endif { var length = keywords.Length; if (index < length && !string.IsNullOrEmpty(keywords[index])) { material.EnableKeyword(keywords[index]); } for (var i = 0; i < length; i++) { if (i == index) continue; material.DisableKeyword(keywords[i]); } } public void ModifyMesh(Graphic graphic, Canvas canvas, RectTransform transitionRoot, VertexHelper vh, bool canModifyShape) { // Apply flip (without effect). ApplyFlipWithoutEffect(vh, m_Flip); var count = vh.currentIndexCount; if (!willModifyVertex || count == 0) return; var effectProxy = GraphicProxy.Find(graphic); var isText = effectProxy.IsText(graphic); effectProxy.OnPreModifyMesh(graphic, canvas); var verts = s_WorkingVertices; var expandSize = effectProxy.ModifyExpandSize(graphic, GetExpandSize(canModifyShape)); // Get the rectangle to calculate the normalized position. vh.GetUIVertexStream(verts); var bundleSize = isText ? 6 : count; // Modify vertices for each bundled-quad. for (var i = 0; i < count; i += bundleSize) { // min/max for bundled-quad UIVertexUtil.GetBounds(verts, i, bundleSize, out var bounds, out var uvMask); UIVertexUtil.Expand(verts, i, bundleSize, expandSize, bounds); for (var j = 0; j < bundleSize; j++) { var vt = verts[i + j]; if (onModifyVertex != null) { vt = onModifyVertex(vt, uvMask); } else { vt.uv1 = new Vector4(uvMask.xMin, uvMask.yMin, uvMask.xMax, uvMask.yMax); } verts[i + j] = vt; } } // Apply flip (with effect). ApplyFlipWithEffect(verts, m_Flip); // Apply shadow. ApplyShadow(verts, transitionRoot, m_Flip); vh.Clear(); vh.AddUIVertexTriangleStream(verts); } private void ApplyShadow(List verts, RectTransform transitionRoot, Flip flip) { var distance = m_ShadowDistance; if (distance == Vector2.zero) return; if ((flip & Flip.Shadow) != 0) { distance.x = (flip & Flip.Horizontal) != 0 ? -distance.x : distance.x; distance.y = (flip & Flip.Vertical) != 0 ? -distance.y : distance.y; } switch (m_ShadowMode) { case ShadowMode.Shadow: case ShadowMode.Shadow3: case ShadowMode.Outline: case ShadowMode.Outline8: ShadowUtil.DoShadow(verts, s_ShadowVectors[(int)m_ShadowMode], distance, m_ShadowIteration, m_ShadowFade); break; case ShadowMode.Mirror: ShadowUtil.DoMirror(verts, distance, m_ShadowMirrorScale, m_ShadowFade, transitionRoot); break; } } private static void ApplyFlipWithoutEffect(VertexHelper vh, Flip flip) { if ((flip & Flip.Effect) != 0) return; var flipHorizontal = 0 != (flip & Flip.Horizontal); var flipVertical = 0 != (flip & Flip.Vertical); if (!flipHorizontal && !flipVertical) return; UIVertexUtil.Flip(vh, flipHorizontal, flipVertical); } private static void ApplyFlipWithEffect(List verts, Flip flip) { if ((flip & Flip.Effect) == 0) return; var flipHorizontal = 0 != (flip & Flip.Horizontal); var flipVertical = 0 != (flip & Flip.Vertical); if (!flipHorizontal && !flipVertical) return; UIVertexUtil.Flip(verts, flipHorizontal, flipVertical); } private Vector4 GetExpandSize(bool canModifyShape) { if (!canModifyShape) return Vector4.zero; var expandSize = Vector4.zero; switch (m_SamplingFilter) { case SamplingFilter.BlurFast: expandSize += Vector4.one * 10; break; case SamplingFilter.BlurMedium: expandSize += Vector4.one * 15; break; case SamplingFilter.BlurDetail: expandSize += Vector4.one * 20; break; case SamplingFilter.RgbShift: expandSize.x += 40; expandSize.z += 40; break; } switch (m_TransitionFilter) { case TransitionFilter.Melt: expandSize.y += 40; break; case TransitionFilter.Burn: expandSize.w += 40; break; } return expandSize; } private static Color ApplyColorSpace(Color color) { return QualitySettings.activeColorSpace == ColorSpace.Linear ? color.linear : color; } } }