DC优化
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Coffee.UIEffectInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.UI;
|
||||
using UnityEditor;
|
||||
#if TIMELINE_ENABLE
|
||||
using UnityEngine.Timeline;
|
||||
#endif
|
||||
|
||||
[assembly: InternalsVisibleTo("UIEffect")]
|
||||
[assembly: InternalsVisibleTo("Coffee.UIEffect.Editor")]
|
||||
|
||||
namespace Coffee.UIEffects
|
||||
{
|
||||
[ExecuteAlways]
|
||||
[DisallowMultipleComponent]
|
||||
public abstract class UIEffectBase : UIBehaviour, IMeshModifier, IMaterialModifier, ICanvasRaycastFilter
|
||||
#if TIMELINE_ENABLE
|
||||
, ITimeControl
|
||||
#endif
|
||||
{
|
||||
private static readonly InternalObjectPool<UIEffectContext> s_ContextPool =
|
||||
new InternalObjectPool<UIEffectContext>(() => new UIEffectContext(), x => true, x => x.Reset());
|
||||
|
||||
private Graphic _graphic;
|
||||
private Material _material;
|
||||
private UIEffectContext _context;
|
||||
private Action _onBeforeCanvasRebuild;
|
||||
private Action _onAfterCanvasRebuild;
|
||||
private bool _canModifyMesh;
|
||||
private Matrix4x4 _prevTransformHash;
|
||||
private Canvas _canvas;
|
||||
private bool _canvasCached = false;
|
||||
private bool _isContextDirty;
|
||||
|
||||
public Material effectMaterial => _material;
|
||||
public Graphic graphic => _graphic != null ? _graphic : _graphic = GetComponent<Graphic>();
|
||||
public virtual uint effectId => (uint)GetHashCode();
|
||||
public virtual float actualSamplingScale => 1;
|
||||
public virtual bool canModifyShape => true;
|
||||
|
||||
protected Canvas canvas
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_canvasCached) return _canvas;
|
||||
|
||||
_canvasCached = true;
|
||||
_canvas = GetComponentInParent<Canvas>();
|
||||
if (_canvas == null || !_canvas.isActiveAndEnabled)
|
||||
{
|
||||
_canvas = null;
|
||||
_canvasCached = false;
|
||||
}
|
||||
|
||||
return _canvas;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual UIEffectContext context
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_context == null)
|
||||
{
|
||||
_context = s_ContextPool.Rent();
|
||||
UpdateContext(_context);
|
||||
}
|
||||
|
||||
return _context;
|
||||
}
|
||||
}
|
||||
|
||||
protected Material GetCurrentMaterial()
|
||||
{
|
||||
if (!isActiveAndEnabled) return null;
|
||||
|
||||
var g = graphic;
|
||||
if (g == null) return null;
|
||||
|
||||
var canvasRenderer = g.canvasRenderer;
|
||||
if (canvasRenderer == null || canvasRenderer.materialCount == 0) return null;
|
||||
|
||||
return canvasRenderer.GetMaterial();
|
||||
}
|
||||
|
||||
public virtual RectTransform transitionRoot => transform as RectTransform;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
_canModifyMesh = true;
|
||||
_onBeforeCanvasRebuild ??= OnBeforeCanvasRebuild;
|
||||
_onAfterCanvasRebuild ??= OnAfterCanvasRebuild;
|
||||
UIExtraCallbacks.onBeforeCanvasRebuild += _onBeforeCanvasRebuild;
|
||||
UIExtraCallbacks.onAfterCanvasRebuild += _onAfterCanvasRebuild;
|
||||
UpdateContext(context);
|
||||
SetMaterialDirty();
|
||||
SetVerticesDirty();
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
if (_onBeforeCanvasRebuild != null)
|
||||
{
|
||||
UIExtraCallbacks.onBeforeCanvasRebuild -= _onBeforeCanvasRebuild;
|
||||
}
|
||||
|
||||
if (_onAfterCanvasRebuild != null)
|
||||
{
|
||||
UIExtraCallbacks.onAfterCanvasRebuild -= _onAfterCanvasRebuild;
|
||||
}
|
||||
|
||||
MaterialRepository.Release(ref _material);
|
||||
SetMaterialDirty();
|
||||
SetVerticesDirty();
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
_onBeforeCanvasRebuild = null;
|
||||
_graphic = null;
|
||||
s_ContextPool.Return(ref _context);
|
||||
}
|
||||
|
||||
private void OnBeforeCanvasRebuild()
|
||||
{
|
||||
// If the mesh is not modified in the previous frame, try to modify it if possible.
|
||||
if (!_canModifyMesh && CanModifyMesh())
|
||||
{
|
||||
_canModifyMesh = true;
|
||||
SetVerticesDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterCanvasRebuild()
|
||||
{
|
||||
if (_material == null || graphic == null || canvas == null || context == null) return;
|
||||
|
||||
if (_isContextDirty)
|
||||
{
|
||||
_isContextDirty = false;
|
||||
ApplyContextToMaterial(GetCurrentMaterial());
|
||||
}
|
||||
|
||||
if (!context.useViewMatrix) return;
|
||||
|
||||
context.UpdateViewMatrix(GetCurrentMaterial(), transitionRoot, canvas.rootCanvas);
|
||||
}
|
||||
|
||||
protected override void OnCanvasHierarchyChanged()
|
||||
{
|
||||
_canvasCached = false;
|
||||
_canvas = null;
|
||||
}
|
||||
|
||||
public void ModifyMesh(Mesh mesh)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void ModifyMesh(VertexHelper vh)
|
||||
{
|
||||
if (!isActiveAndEnabled || context == null) return;
|
||||
|
||||
_canModifyMesh = CanModifyMesh();
|
||||
if (_canModifyMesh)
|
||||
{
|
||||
context.ModifyMesh(graphic, canvas, transitionRoot, vh, canModifyShape);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanModifyMesh()
|
||||
{
|
||||
if (graphic == null || !graphic.isActiveAndEnabled) return false;
|
||||
if (!isActiveAndEnabled) return false;
|
||||
|
||||
// The transitionRoot is same as the transform => true.
|
||||
var root = transitionRoot;
|
||||
if (transform == root) return true;
|
||||
|
||||
// The transitionRoot is not visible => false.
|
||||
var scale1 = root.lossyScale;
|
||||
var scale2 = transform.lossyScale;
|
||||
return !Mathf.Approximately(scale1.x * scale1.y * scale2.x * scale2.y, 0);
|
||||
}
|
||||
|
||||
public virtual Material GetModifiedMaterial(Material baseMaterial)
|
||||
{
|
||||
if (baseMaterial == null || !isActiveAndEnabled || context == null || !context.willModifyMaterial)
|
||||
{
|
||||
MaterialRepository.Release(ref _material);
|
||||
return baseMaterial;
|
||||
}
|
||||
|
||||
Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial");
|
||||
var samplingScaleId = (uint)(Mathf.InverseLerp(0.01f, 100, actualSamplingScale) * uint.MaxValue);
|
||||
// 使用特效配置哈希作为材质共享 key,相同配置的特效共享同一材质,从而可以合批。
|
||||
// root 仅在特效需要视图矩阵(transition/gradation/detail/shiny)时才影响材质。
|
||||
var configId = context.configId;
|
||||
var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u;
|
||||
var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
|
||||
if (!MaterialRepository.Valid(hash, _material))
|
||||
{
|
||||
Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial > Get or create material");
|
||||
MaterialRepository.Get(hash, ref _material, x => new Material(x)
|
||||
{
|
||||
shader = UIEffectProjectSettings.shaderRegistry.FindOptionalShader(x.shader,
|
||||
"(UIEffect)",
|
||||
"Hidden/{0} (UIEffect)",
|
||||
"Hidden/UI/Default (UIEffect)"),
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
}, baseMaterial);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
_material.CopyPropertiesFromMaterial(baseMaterial);
|
||||
ApplyContextToMaterial(_material);
|
||||
Profiler.EndSample();
|
||||
return _material;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate()
|
||||
{
|
||||
UpdateContext(context);
|
||||
SetVerticesDirty();
|
||||
SetMaterialDirty();
|
||||
}
|
||||
|
||||
protected override void Reset()
|
||||
{
|
||||
OnValidate();
|
||||
}
|
||||
|
||||
internal virtual void SetEnablePreviewIfSelected(GameObject[] selection)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override void OnRectTransformDimensionsChange()
|
||||
{
|
||||
if (!isActiveAndEnabled) return;
|
||||
SetVerticesDirty();
|
||||
}
|
||||
|
||||
protected override void OnDidApplyAnimationProperties()
|
||||
{
|
||||
UpdateContext(context);
|
||||
SetVerticesDirty();
|
||||
SetMaterialDirty();
|
||||
}
|
||||
|
||||
public virtual void SetVerticesDirty()
|
||||
{
|
||||
if (graphic != null)
|
||||
{
|
||||
graphic.SetVerticesDirty();
|
||||
GraphicProxy.Find(graphic).SetVerticesDirty(graphic, enabled);
|
||||
Misc.QueuePlayerLoopUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetMaterialDirty()
|
||||
{
|
||||
if (graphic != null)
|
||||
{
|
||||
graphic.SetMaterialDirty();
|
||||
SetMaterialContextDirty();
|
||||
Misc.QueuePlayerLoopUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetMaterialContextDirty()
|
||||
{
|
||||
_isContextDirty = true;
|
||||
}
|
||||
|
||||
internal void ReleaseMaterial()
|
||||
{
|
||||
MaterialRepository.Release(ref _material);
|
||||
}
|
||||
|
||||
internal abstract void UpdateContext(UIEffectContext c);
|
||||
|
||||
public virtual void ApplyContextToMaterial(Material material)
|
||||
{
|
||||
if (!isActiveAndEnabled || context == null || material == null) return;
|
||||
|
||||
_isContextDirty = false;
|
||||
context.ApplyToMaterial(material, actualSamplingScale);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Profiler.BeginSample("(Editor/UIE)[UIEffect] ApplyContextToMaterial Post Process");
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
SetEnablePreviewIfSelected(Selection.gameObjects);
|
||||
Misc.QueuePlayerLoopUpdate();
|
||||
}
|
||||
|
||||
UIEffectProjectSettings.shaderRegistry.RegisterVariant(material, "UI > UIEffect");
|
||||
Profiler.EndSample();
|
||||
#endif
|
||||
}
|
||||
|
||||
public abstract void SetRate(float rate, UIEffectTweener.CullingMask cullingMask);
|
||||
public abstract bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera);
|
||||
|
||||
public void SetTime(double _)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnControlTimeStart()
|
||||
{
|
||||
if (this != null)
|
||||
{
|
||||
enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnControlTimeStop()
|
||||
{
|
||||
if (this != null)
|
||||
{
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,976 @@
|
||||
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<UIVertex> s_WorkingVertices = new List<UIVertex>(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<Vector2>(), // 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<UIVertex, Rect, UIVertex> 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;
|
||||
|
||||
/// <summary>
|
||||
/// 特效配置的稳定指纹。自动遍历所有序列化配置字段生成,因此插件新增属性时无需手动维护字段清单。
|
||||
/// 相同配置会得到相同指纹,从而可以共享同一个材质并参与合批。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对任意配置值做内容哈希。值类型会逐字段展开,嵌套结构体(Color、Vector2/3/4、MinMax01 等)
|
||||
/// 无需单独写类型处理即可贡献全部数据。只有 UnityEngine.Object 和 Gradient 需要显式处理,
|
||||
/// 因为它们的默认哈希基于引用而非内容。
|
||||
/// </summary>
|
||||
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<Type, FieldInfo[]> s_FieldCache =
|
||||
new Dictionary<Type, FieldInfo[]>();
|
||||
|
||||
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<Texture2D> s_TexturePool = new InternalObjectPool<Texture2D>(
|
||||
() =>
|
||||
{
|
||||
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<string> 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<UIVertex> 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<UIVertex> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
# UIEffect 降 DC 改动总结
|
||||
|
||||
> 提交:`fa5f0cdb 降低DC的操作`(2026-08-26)
|
||||
> 涉及目录:`unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/`
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
UI_Effect 插件原本每个 `UIEffect` 组件都会生成**独立的材质实例**(缓存 key 使用 `effectId`,即组件的 `GetInstanceID()`,每个实例唯一),导致:
|
||||
|
||||
- 相同配置的特效无法共享材质 → 无法参与动态合批 → **Draw Call 偏高**;
|
||||
- 批量刷 UI 时材质创建开销大,伴随帧率波动。
|
||||
|
||||
目标:让**相同配置的 UIEffect 共享同一材质**,从而参与合批,降低 DC。
|
||||
|
||||
## 二、方案选型
|
||||
|
||||
| 方案 | 说明 | 结论 |
|
||||
| --- | --- | --- |
|
||||
| UIEffectReplica + Preset | 业务代码逐个改为副本组件,指向共享 preset | 需要改动大量业务文件、创建 preset 资产,侵入性大 |
|
||||
| **configId 配置指纹(最终采用)** | 在插件内部按"配置内容哈希"共享材质 | **业务代码零改动**,同配置自动共享合批 |
|
||||
|
||||
## 三、具体改动
|
||||
|
||||
### 1. `UIEffectContext.cs`(新增约 121 行)
|
||||
|
||||
核心:新增 `configId` 属性——特效配置的"稳定指纹"。
|
||||
|
||||
- **反射字段缓存**:`s_ConfigFields` 为 `static readonly FieldInfo[]`,`GetFields` 只在类型加载时执行一次,运行时无反射类型发现开销,避免批量刷新时的 GC 尖峰(这是此前"帧率不稳"问题的根因与修复点)。
|
||||
- **内容哈希 `HashValue`**:
|
||||
- `null` → 0;
|
||||
- `UnityEngine.Object` → `GetInstanceID()`(引用同一资产即同哈希);
|
||||
- `Gradient` → `GradientHash` 按 colorKeys / alphaKeys 逐 key 做内容哈希(不同实例但内容相同的渐变得到相同指纹);
|
||||
- 基元/枚举 → `GetHashCode()`;嵌套值类型(Color、Vector、MinMax01 等)→ `HashFields` 逐字段展开(`s_FieldCache` 按类型缓存)。
|
||||
- **雪崩混淆**:FNV-1a 混合后追加 MurmurHash3 终混,让相近配置散列开,降低哈希碰撞概率。
|
||||
|
||||
### 2. `UIEffectBase.cs`(`GetModifiedMaterial`)
|
||||
|
||||
材质缓存 key 由 `effectId` 改为配置指纹:
|
||||
|
||||
```csharp
|
||||
var configId = context.configId;
|
||||
var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u;
|
||||
var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- `rootId` **仅在特效需要视图矩阵时**(transition / gradation / detail / shiny)才参与哈希,普通灰度(ToneFilter)等不受 root 影响,可共享范围更大;
|
||||
- `Hash128(baseMaterial, configId, samplingScaleId, rootId)` 相同 → 命中 `MaterialRepository` 中同一材质实例 → 参与动态合批。
|
||||
|
||||
### 3. `ShaderVariantRegistry.cs`(Profiler 采样修复)
|
||||
|
||||
`RegisterVariant` / `GetVariant` 的**所有提前 return 分支**均补上 `Profiler.EndSample()`,保证 `BeginSample`/`EndSample` 严格配对,修复控制台的:
|
||||
|
||||
```
|
||||
Non-matching Profiler.EndSample (Every EndSample call must have a preceding BeginSample call...)
|
||||
```
|
||||
|
||||
## 四、业务侧影响
|
||||
|
||||
- **业务文件零改动**:`EquipBuffItem`、`MapBlockItem`、`PetItem`、`V_RechargeItem`、`FightLinkHeroItem`、`AvatarItem` 等继续正常使用 `UIEffect` 控制灰度,无需迁移到 `UIEffectReplica`。
|
||||
- 同屏所有**同配置**灰度特效(如大量 `ToneFilter.Grayscale` 的图标)自动共享一个材质并合批。
|
||||
|
||||
## 五、效果
|
||||
|
||||
- N 个相同配置的 UIEffect:N 个材质实例 → **1 个共享材质**,可被 UGUI 动态合批;
|
||||
- DC 显著下降(受益程度取决于面板内同配置特效数量)。
|
||||
|
||||
## 六、注意事项与局限
|
||||
|
||||
1. **配置不同的特效无法互相合批**——每种不同配置组合仍各占一个材质/DC,属预期行为;
|
||||
2. `configId` 在画布重建时计算,字段遍历存在少量装箱开销,已通过静态缓存降到最低(普通 UI 不会每帧触发);
|
||||
3. 新增 `UIEffectContext` 公共字段会自动纳入指纹(遍历 public instance 字段),**无需手动维护字段清单**;
|
||||
4. 验证方式:打开 Frame Debugger / Profiler,确认同配置 UI 元素合并为同一批次;确认控制台不再出现 `Non-matching Profiler.EndSample`。
|
||||
@@ -0,0 +1,156 @@
|
||||
diff --git a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs b/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs
|
||||
index c3ceb2fd..210257d0 100644
|
||||
--- a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs
|
||||
+++ b/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs
|
||||
@@ -196,8 +196,11 @@ namespace Coffee.UIEffects
|
||||
|
||||
Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial");
|
||||
var samplingScaleId = (uint)(Mathf.InverseLerp(0.01f, 100, actualSamplingScale) * uint.MaxValue);
|
||||
- var rootId = (uint)(transitionRoot != null ? transitionRoot.GetHashCode() : 0);
|
||||
- var hash = new Hash128((uint)baseMaterial.GetHashCode(), effectId, samplingScaleId, rootId);
|
||||
+ // 使用特效配置哈希作为材质共享 key,相同配置的特效共享同一材质,从而可以合批。
|
||||
+ // root 仅在特效需要视图矩阵(transition/gradation/detail/shiny)时才影响材质。
|
||||
+ var configId = context.configId;
|
||||
+ var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u;
|
||||
+ var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
|
||||
if (!MaterialRepository.Valid(hash, _material))
|
||||
{
|
||||
Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial > Get or create material");
|
||||
diff --git a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs b/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs
|
||||
index c315f4cb..48b6ae4b 100644
|
||||
--- a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs
|
||||
+++ b/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
+using System.Reflection;
|
||||
using Coffee.UIEffectInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
@@ -263,6 +264,126 @@ namespace Coffee.UIEffects
|
||||
|| m_DetailFilter != DetailFilter.None
|
||||
|| m_EdgeMode == EdgeMode.Shiny;
|
||||
|
||||
+ /// <summary>
|
||||
+ /// 特效配置的稳定指纹。自动遍历所有序列化配置字段生成,因此插件新增属性时无需手动维护字段清单。
|
||||
+ /// 相同配置会得到相同指纹,从而可以共享同一个材质并参与合批。
|
||||
+ /// </summary>
|
||||
+ 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;
|
||||
+ }
|
||||
+
|
||||
+ /// <summary>
|
||||
+ /// 对任意配置值做内容哈希。值类型会逐字段展开,嵌套结构体(Color、Vector2/3/4、MinMax01 等)
|
||||
+ /// 无需单独写类型处理即可贡献全部数据。只有 UnityEngine.Object 和 Gradient 需要显式处理,
|
||||
+ /// 因为它们的默认哈希基于引用而非内容。
|
||||
+ /// </summary>
|
||||
+ 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<Type, FieldInfo[]> s_FieldCache =
|
||||
+ new Dictionary<Type, FieldInfo[]>();
|
||||
+
|
||||
private Texture2D _gradationRampTex;
|
||||
private Texture2D _transitionRampTex;
|
||||
private bool _isGradientDirty = true;
|
||||
@@ -0,0 +1,286 @@
|
||||
# 移植与验证手册
|
||||
|
||||
> 配套包:`DC优化归档/` | 验证过的环境:Unity 团结引擎(Tuanjie)+ UI Effect **5.11.3** + URP
|
||||
> 改动量:2 个文件、126 行新增、2 行删除。业务代码 **0 改动**。
|
||||
|
||||
---
|
||||
|
||||
## 第一章 动手前的前提核对(3 项,缺一不可)
|
||||
|
||||
### 1.1 UIEffect 必须是"本地源码包",不能是 PackageCache 只读包
|
||||
|
||||
打开目标工程的 `Packages/manifest.json`,找 `com.coffee.ui-effect`:
|
||||
|
||||
```jsonc
|
||||
// ✅ 可以改:file: 指向工程内源码目录
|
||||
"com.coffee.ui-effect": "file:../my_libs/unityLib/UIEffect-main/Packages/src",
|
||||
|
||||
// ❌ 改了白改:版本号形式会被拉进 Library/PackageCache/(只读,Unity 重新解包会覆盖)
|
||||
"com.coffee.ui-effect": "5.11.3"
|
||||
```
|
||||
|
||||
如果是 ❌ 形式,先转成本地包:
|
||||
1. 从 [github.com/mob-sakai/ParticleEffectForUGUI 同作者的 UIEffect 仓库](https://github.com/mob-sakai/UIEffect) 下载对应版本源码;
|
||||
2. 放到工程内目录(如 `<项目>/unity/my_libs/unityLib/UIEffect-main/Packages/src`);
|
||||
3. `manifest.json` 改成 `file:` 相对路径;
|
||||
4. Unity 重新编译,确认 `Library/PackageCache/` 下的 `com.coffee.ui-effect@*` 不再被引用。
|
||||
|
||||
> T5 的实际路径:`d:\Project\T5\T5_game\unity\my_libs\unityLib\UIEffect-main\Packages\src`
|
||||
> 需要改的两个文件都在它的 `Runtime/` 下。
|
||||
|
||||
### 1.2 版本核对:确认锚点代码存在
|
||||
|
||||
补丁靠"上下文行"定位,版本差异大就会 apply 失败。打开目标工程的两个文件,确认下面两段存在:
|
||||
|
||||
**`Runtime/UIEffectContext.cs`** —— 找到 `useViewMatrix` 属性(补丁插在它后面):
|
||||
```csharp
|
||||
public bool useViewMatrix => m_TransitionFilter != TransitionFilter.None
|
||||
|| m_GradationMode != GradationMode.None
|
||||
|| m_DetailFilter != DetailFilter.None
|
||||
|| m_EdgeMode == EdgeMode.Shiny;
|
||||
```
|
||||
|
||||
**`Runtime/UIEffectBase.cs`** —— 找到 `GetModifiedMaterial()` 里的这两行(补丁要替换它们):
|
||||
```csharp
|
||||
var rootId = (uint)(transitionRoot != null ? transitionRoot.GetHashCode() : 0);
|
||||
var hash = new Hash128((uint)baseMaterial.GetHashCode(), effectId, samplingScaleId, rootId);
|
||||
```
|
||||
|
||||
两段都在 → 走**方式 A**(git apply)。
|
||||
`useViewMatrix` 不存在(更老的版本)→ 走**方式 C**(手工贴),并按 §3.3 的降级写法处理 `rootId`。
|
||||
|
||||
### 1.3 记录基线 DC 数字
|
||||
|
||||
改之前先量一次,否则改完无法证明有效:
|
||||
- 打开目标面板(选 UIEffect 用得最多的那个,T5 用的是 `UI_LevelGiftPanel`);
|
||||
- `Window → Analysis → Frame Debugger`,Enable;
|
||||
- 记下左侧事件树里 `Canvas.BuildBatch` 下的 `Draw Mesh` 数量(或右下角 Batches 数字);
|
||||
- 截图存档。
|
||||
|
||||
---
|
||||
|
||||
## 第二章 三种移植方式
|
||||
|
||||
### 方式 A:git apply(推荐,最快最安全)
|
||||
|
||||
补丁里的路径是 `a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/...`,
|
||||
即**相对目标工程 git 根目录**。T5 的 git 根是 `<项目>/T5_game`(不是 `<项目>`),别站错目录。
|
||||
|
||||
```powershell
|
||||
# 站到目标工程的 git 根目录
|
||||
cd <目标工程 git 根目录>
|
||||
|
||||
# 先干跑,看能不能对上(不写文件)
|
||||
git apply --check --3way "d:\Project\T5\DC优化归档\01_UIEffect材质共享降DC\uieffect_configId.patch"
|
||||
|
||||
# 没问题再真打
|
||||
git apply --3way "d:\Project\T5\DC优化归档\01_UIEffect材质共享降DC\uieffect_configId.patch"
|
||||
|
||||
# 确认只动了这 2 个文件
|
||||
git status --porcelain
|
||||
```
|
||||
|
||||
**目标工程 UIEffect 放在别的路径**(比如 `Assets/Plugins/UIEffect/`)时,用 `--directory` 或 `-p` 调整剥层级,例如:
|
||||
|
||||
```powershell
|
||||
# 补丁里 a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/X.cs
|
||||
# 目标工程实际是 Assets/Plugins/UIEffect/Runtime/X.cs → 剥掉前 5 层再拼目标前缀
|
||||
git apply -p5 --directory="Assets/Plugins/UIEffect" --3way "<补丁路径>"
|
||||
```
|
||||
|
||||
`--check` 报错 `does not match index` → 目标文件已被改过或版本不同 → 转方式 B/C。
|
||||
|
||||
### 方式 B:直接覆盖两个 .cs(兜底,要求版本一致)
|
||||
|
||||
```powershell
|
||||
$dst = "<目标工程>\unity\my_libs\unityLib\UIEffect-main\Packages\src\Runtime"
|
||||
Copy-Item "d:\Project\T5\DC优化归档\01_UIEffect材质共享降DC\UIEffectContext.cs" $dst -Force
|
||||
Copy-Item "d:\Project\T5\DC优化归档\01_UIEffect材质共享降DC\UIEffectBase.cs" $dst -Force
|
||||
```
|
||||
|
||||
覆盖前**务必先备份原文件**(或确认在 git 里可回退):这两个文件是完整成品,覆盖会连带丢掉目标工程对它们的任何其它本地修改。
|
||||
|
||||
> `.meta` 文件不用管:这是"修改已存在的文件",不是"新增资源",guid 不变,prefab/场景引用不受影响。
|
||||
|
||||
### 方式 C:手工贴代码块(版本不同时的唯一选择)
|
||||
|
||||
三处改动,照着贴:
|
||||
|
||||
**C-1 `UIEffectContext.cs` 文件头加 using**
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection; // ← 新增这一行
|
||||
using Coffee.UIEffectInternal;
|
||||
using UnityEngine;
|
||||
```
|
||||
|
||||
**C-2 `UIEffectContext.cs` 在 `useViewMatrix` 属性之后插入 121 行**
|
||||
完整代码见本包 `01_UIEffect材质共享降DC/UIEffectContext.cs` 的 **267~385 行**(`configId` 属性 + `Mix` / `HashValue` / `HashFields` / `GradientHash` + `s_ConfigFields` / `s_FieldCache` 两个静态缓存)。
|
||||
整块是纯新增,不改任何原有代码,粘到 `useViewMatrix` 与 `private Texture2D _gradationRampTex;` 之间即可。
|
||||
|
||||
**C-3 `UIEffectBase.cs` 的 `GetModifiedMaterial()` 换 key**(T5 里约 196~203 行)
|
||||
```csharp
|
||||
Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial");
|
||||
var samplingScaleId = (uint)(Mathf.InverseLerp(0.01f, 100, actualSamplingScale) * uint.MaxValue);
|
||||
// 使用特效配置哈希作为材质共享 key,相同配置的特效共享同一材质,从而可以合批。
|
||||
// root 仅在特效需要视图矩阵(transition/gradation/detail/shiny)时才影响材质。
|
||||
var configId = context.configId;
|
||||
var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u;
|
||||
var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
|
||||
if (!MaterialRepository.Valid(hash, _material))
|
||||
```
|
||||
|
||||
注意三个容易漏的细节:
|
||||
- `baseMaterial.GetHashCode()` 也要一起改成 `baseMaterial.GetInstanceID()`(`GetHashCode` 对 Object 不是稳定 ID);
|
||||
- 只改 `hash` 那一行、不改 `rootId`,会让 transition 类特效在不同 root 下错误共享材质(视觉错位);
|
||||
- `effectId` 属性**保留不删**,它是 public API,`UIEffectReplica` 还在 override 它。
|
||||
|
||||
---
|
||||
|
||||
## 第三章 移植后必做的 5 项检查
|
||||
|
||||
### 3.1 编译通过 + 无警告
|
||||
Unity Console 干净。报 `UIEffectContext does not contain a definition for 'useViewMatrix'` → 见 §3.3。
|
||||
|
||||
### 3.2 业务代码有没有绕过 context 直接改材质(**最重要**)
|
||||
|
||||
材质从"每实例独有"变成"同配置共享"后,**任何直接改 `effectMaterial` 的代码都会污染所有共享者**。
|
||||
|
||||
```powershell
|
||||
# 在目标工程业务脚本目录执行,命中即需逐个确认
|
||||
cd <目标工程>\unity\Assets
|
||||
Select-String -Path (Get-ChildItem -Recurse -Filter *.cs).FullName `
|
||||
-Pattern "effectMaterial|GetComponent<UIEffect>|UIEffect\[\]" | Select-Object Path, LineNumber, Line
|
||||
```
|
||||
|
||||
判定规则:
|
||||
- 只读(`var m = e.effectMaterial; if (m == null)`)→ 安全;
|
||||
- 写入(`e.effectMaterial.SetColor/SetFloat/SetTexture/...`)→ **危险**,必须改成"改 UIEffect 组件的序列化字段(走 context)"或"给该实例单独指定不同配置";
|
||||
- T5 实测:业务代码 0 命中,无需处理。
|
||||
|
||||
### 3.3 `useViewMatrix` 不存在的降级写法(老版本插件)
|
||||
|
||||
```csharp
|
||||
var configId = context.configId;
|
||||
// 老版本没有 useViewMatrix:保守起见让 root 始终参与,共享范围变小但结果一定正确
|
||||
var rootId = transitionRoot != null ? (uint)transitionRoot.GetInstanceID() : 0u;
|
||||
var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
|
||||
```
|
||||
或者自己在 `UIEffectContext` 里补一个等价属性(判断条件见 §1.2 的代码块)。
|
||||
|
||||
### 3.4 UIEffectReplica / UIEffectPreset 兼容性
|
||||
|
||||
已核对源码(`UIEffectReplica.cs` 的 `context` override),补丁后两条路径行为不变:
|
||||
|
||||
| Replica 工作模式 | context 来源 | 补丁前共享依据 | 补丁后共享依据 | 结论 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 跟随场景内的 target | `target.context`(同一对象) | `effectId = target.effectId` | 同一个 context → configId 必然相同 | 仍共享 |
|
||||
| 跟随 preset(或 target 不在场景) | 自己的 `base.context` | `effectId = preset.GetHashCode()` | preset 配置相同 → configId 相同 | 仍共享 |
|
||||
|
||||
如果目标工程用了 Replica,改完抽查一处 Replica 的显示是否正常即可(T5 只有 1 个 prefab 涉及)。
|
||||
|
||||
### 3.5 IL2CPP / 高 stripping 下的一致性(出真机包前做)
|
||||
|
||||
`configId` 依赖反射遍历字段。字段本身被 Unity 序列化系统引用,正常不会被裁剪,但**换平台后建议验一次**:
|
||||
|
||||
```csharp
|
||||
// 临时加在 UIEffectBase.GetModifiedMaterial 里,真机跑一次看日志
|
||||
Debug.Log($"[UIE] configId={configId} hash={hash} name={name}");
|
||||
```
|
||||
同一配置的元素在真机上应打出**相同的 configId**,且与编辑器一致。验完删掉这行日志。
|
||||
|
||||
---
|
||||
|
||||
## 第四章 控制台报错处理
|
||||
|
||||
### 4.1 `Non-matching Profiler.EndSample`
|
||||
|
||||
```
|
||||
Non-matching Profiler.EndSample (Every EndSample call must have a preceding BeginSample call...)
|
||||
```
|
||||
|
||||
成因:`ShaderVariantRegistry.cs` 的 `RegisterVariant` / `GetVariant` 里有**提前 return 分支没配对 EndSample**。
|
||||
|
||||
处理:文件在 `Runtime/Internal/Utilities/ShaderVariantRegistry.cs`。检查每个 `Profiler.BeginSample(...)` 之后的**所有** `return` 路径,return 前补 `Profiler.EndSample();`。
|
||||
|
||||
> T5 的 5.11.3 版本该文件配对本身已正确(`RegisterVariant` 在 301 行 Begin,307/327/333 三处 End 覆盖全部分支;`GetVariant` 在 338 行 Begin,358/370 两处 End),**无需修改**,所以本包没有归档这个补丁。
|
||||
> T11 当时是旧版本才需要补。
|
||||
|
||||
### 4.2 `NullReferenceException` 出现在 UIEffect 相关调用栈
|
||||
|
||||
九成是**删了 UIEffect 组件但业务代码还在 `GetComponent<UIEffect>()` 后用**。
|
||||
血泪教训(T5 实测踩过):**删任何 UI 组件前,先 grep 业务代码有没有引用它**,确认无引用再删。
|
||||
本补丁不删组件,正常不会触发;若你在做 DC 优化时顺手清理了"看起来没用的" UIEffect,请务必回查。
|
||||
|
||||
---
|
||||
|
||||
## 第五章 验证方法(怎么证明 DC 真的降了)
|
||||
|
||||
### 5.1 Frame Debugger 对比(最直接)
|
||||
|
||||
1. 打开同一个面板(跟 §1.3 基线同一个);
|
||||
2. `Window → Analysis → Frame Debugger` → Enable;
|
||||
3. 看 `Canvas.BuildBatch` 下的 `Draw Mesh` 数量,与基线对比;
|
||||
4. **关键验证点**:在事件树里点几个原本各自独占一批的灰度图标,看右下角 **Material / Texture** 是否变成同一个(材质名相同即共享成功)。
|
||||
|
||||
预期:同配置特效密集的面板,批次数量级下降(T5 等级礼包面板 ~101 → 30~40)。
|
||||
如果只降了一两个批次,说明该面板里 UIEffect 配置本来就各不相同(属正常,见 §7 原因 1)。
|
||||
|
||||
### 5.2 材质实例数对比(更硬的证据)
|
||||
|
||||
Play 模式下执行(临时挂个测试按钮或用 Console 执行):
|
||||
```csharp
|
||||
var all = Resources.FindObjectsOfTypeAll<Material>();
|
||||
Debug.Log($"UIEffect 材质数: {all.Count(m => m.shader != null && m.shader.name.Contains("UIEffect"))}");
|
||||
```
|
||||
补丁前:数量 ≈ 场景内 UIEffect 组件数;补丁后:数量 ≈ **不同配置的种类数**。
|
||||
|
||||
### 5.3 视觉回归(必做)
|
||||
|
||||
逐个检查这几类特效显示是否正常——它们走的是 `rootId` 参与哈希的分支,是本次改动唯一有行为差异的地方:
|
||||
- Transition(过渡/溶解)
|
||||
- Gradation(渐变)
|
||||
- Detail Filter(细节滤镜)
|
||||
- Edge Mode = Shiny(发光边缘)
|
||||
|
||||
普通灰度(ToneFilter.Grayscale)不涉及 root,风险最低。
|
||||
|
||||
---
|
||||
|
||||
## 第六章 回滚
|
||||
|
||||
```powershell
|
||||
cd <目标工程 git 根目录>
|
||||
|
||||
# 方式 A/B 打的补丁:一条命令还原这两个文件
|
||||
git checkout -- "unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs" `
|
||||
"unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs"
|
||||
```
|
||||
路径不同的按实际改。回滚后回 Unity 编译即可,不涉及资源/序列化数据,**无副作用**(材质缓存 key 是运行时的,不落盘)。
|
||||
|
||||
---
|
||||
|
||||
## 第七章 "改完了 DC 还是没降"的 7 个原因
|
||||
|
||||
按命中概率排序,逐条排:
|
||||
|
||||
1. **配置本来就各不相同**:一个面板里 10 个 UIEffect 用了 10 种参数组合 → 10 个材质,属预期。想再降只能统一美术配置(比如灰度就用同一套参数)。
|
||||
2. **中间夹了不能合批的元素**:合批要求"同一 Canvas + 同材质 + 同纹理 + 层级相邻"。UIEffect 元素之间插了别的图(不同图集)→ 批次被打断,即使材质相同也合不了。
|
||||
3. **Mask / RectMask2D 切割**:每个 Mask 内部独立成批,跨 Mask 永不合批。
|
||||
4. **不同图集(SpriteAtlas)**:材质相同但纹理不同 → 不合批。Frame Debugger 里看纹理名 `sactx-...-atlas-<hash>`,hash 不同就是不同图集。
|
||||
5. **Canvas 被拆成多个**(子物体挂了 `Canvas` 组件 / `SubCanvas`)→ 各自建批。
|
||||
6. **TextMeshPro 与 Image 混排**:TMP 用字体图集,跟普通 Image 不同纹理,天然分批。
|
||||
7. **补丁没生效**:改的是 `Library/PackageCache/` 下的副本(§1.1 没做对),或 Unity 没重新编译(改完必须等编译完成、必要时 `Assets → Reimport All`)。
|
||||
|
||||
排查工具与看图技巧见 `05_排查方法论/DC与帧率排查经验.md`。
|
||||
|
||||
---
|
||||
|
||||
## 第八章 升级插件时的注意事项
|
||||
|
||||
UIEffect 版本升级会**覆盖这两个文件**,补丁丢失。所以:
|
||||
|
||||
1. 补丁改动务必**提交进 git**,commit message 写明"UIEffect configId 降 DC 补丁"(T5 是 `c61b9012`,T11 是 `fa5f0cdb`);
|
||||
2. 升级插件后重跑本手册第一章的锚点核对,再按方式 A/C 重新打;
|
||||
3. 本包 `01_UIEffect材质共享降DC/` 里的两个成品 `.cs` 是 5.11.3 版本,**不要**用它覆盖更高版本的插件文件。
|
||||
Reference in New Issue
Block a user