Files
Cielonos/Assets/OtherPlugins/Le Tai's Asset/TranslucentImage/Script/TranslucentImage.cs
SoulliesOfficial f26f9fd374 爆更
2026-03-20 12:07:44 -04:00

420 lines
12 KiB
C#

using System;
using System.Diagnostics.CodeAnalysis;
using LeTai.Common;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace LeTai.Asset.TranslucentImage
{
/// <summary>
/// Dynamic blur-behind UI element
/// </summary>
[HelpURL("https://leloctai.com/asset/translucentimage/docs/articles/customize.html#translucent-image")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
public partial class TranslucentImage : Image, IActiveRegionProvider, IMeshModifier
{
/// <summary>
/// Source of the blurred background for this image
/// </summary>
public TranslucentImageSource source
{
get => _source;
set
{
_source = value;
// We need a separated variable, as the backing field is set before the setter is called from EditorProperty
if (_source == _sourcePrev)
return;
DisconnectSource(_sourcePrev);
ConnectSource(_source);
_sourcePrev = source;
}
}
[Obsolete("Use foregroundOpacity instead")]
public float spriteBlending
{
get => foregroundOpacity;
set => foregroundOpacity = value;
}
/// <summary>
/// How much Sprite and Color contribute to the Image. Use this instead of Color.alpha
/// </summary>
public float foregroundOpacity
{
get => _foregroundOpacity;
set
{
_foregroundOpacity = value;
SetVerticesDirty();
}
}
/// <summary>
/// (De)Saturate the image, 1 is normal, 0 is grey scale, below zero make the image negative
/// </summary>
public float vibrancy
{
get => _vibrancy;
set
{
_vibrancy = value;
SetVerticesDirty();
}
}
/// <summary>
/// In Normal Background Mode: Brighten/darken the background. In Colorful Background Mode: Set the background overall brightness.
/// </summary>
public float brightness
{
get => _brightness;
set
{
_brightness = value;
SetVerticesDirty();
}
}
/// <summary>
/// Flatten the color behind to maintain color contrast on varying backgrounds
/// </summary>
public float flatten
{
get => _flatten;
set
{
_flatten = value;
SetVerticesDirty();
}
}
public override Material material
{
get => base.material;
set
{
base.material = value;
OnDirtyMaterial();
}
}
public override Material defaultMaterial
{
get
{
#if LETAI_PARAFORM
return DefaultResources.Instance.paraformMaterial;
#else
return DefaultResources.Instance.material;
#endif
}
}
[FormerlySerializedAs("source")]
[Tooltip("Source of the blurred background for this image")]
[SerializeField] TranslucentImageSource _source;
[FormerlySerializedAs("spriteBlending")]
[FormerlySerializedAs("m_spriteBlending")]
[Tooltip("How much Sprite and Color contribute to the Image. Use this instead of Color.alpha")]
[SerializeField] [Range(0, 1)] float _foregroundOpacity = .5f;
[FormerlySerializedAs("vibrancy")]
[Tooltip("(De)Saturate the image, 1 is normal, 0 is grey scale, below zero make the image negative")]
[SerializeField] [Range(-1, 2)] float _vibrancy = 1;
[FormerlySerializedAs("brightness")]
[Tooltip("In Normal Background Mode: Brighten/darken the background. In Colorful Background Mode: Set the background overall brightness.")]
[SerializeField] [Range(-1, 1)] float _brightness = 0;
[FormerlySerializedAs("flatten")]
[Tooltip("Flatten the color behind to maintain color contrast on varying backgrounds")]
[SerializeField] [Range(0, 1)] float _flatten = 0;
bool shouldRun;
bool isBirp;
TranslucentImageSource _sourcePrev;
protected override void Start()
{
isBirp = !GraphicsSettings.currentRenderPipeline;
AutoAcquireSource();
if (material && source)
{
material.SetTexture(ShaderID.BLUR_TEX, source.BlurredScreen);
}
m_OnDirtyMaterialCallback += OnDirtyMaterial;
if (canvas)
canvas.additionalShaderChannels |= AdditionalCanvasShaderChannels.TexCoord1
| AdditionalCanvasShaderChannels.TexCoord2
| AdditionalCanvasShaderChannels.TexCoord3;
}
protected override void OnEnable()
{
base.OnEnable();
SetVerticesDirty();
ConnectSource(source);
_sourcePrev = source;
paraformConfig.changed += ParaformConfigChanged;
Canvas.willRenderCanvases += OnWillRenderCanvases;
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
{
Start();
}
UnityEditor.Undo.undoRedoPerformed += OnUndoRedoPerformed;
#endif
}
void ParaformConfigChanged()
{
SetVerticesDirty();
UpdateTrueShadowHash();
}
protected override void OnDisable()
{
SetVerticesDirty();
base.OnDisable();
paraformConfig.changed -= ParaformConfigChanged;
Canvas.willRenderCanvases -= OnWillRenderCanvases;
DisconnectSource(source);
#if UNITY_EDITOR
UnityEditor.Undo.undoRedoPerformed -= OnUndoRedoPerformed;
#endif
}
void OnWillRenderCanvases()
{
SetParaformShaderGlobal();
}
// void Update()
// {
// #if DEBUG
// if (Application.isPlaying && !IsInPrefabMode())
// {
// if (!source)
// Debug.LogWarning("TranslucentImageSource is missing. " +
// "Please add the TranslucentImageSource component to your main camera, " +
// "then assign it to the Source field of the Translucent Image(s)");
// }
// #endif
// }
public bool HaveActiveRegion()
{
return (bool)this && IsActive() && canvas && canvas.enabled;
}
public void GetActiveRegion(VPMatrixCache vpMatrixCache, out ActiveRegion activeRegion)
{
VPMatrixCache.Index vpMatrixIndex;
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
vpMatrixIndex = VPMatrixCache.Index.INVALID;
}
else
{
var refCamera = canvas.worldCamera;
if (!refCamera)
{
if (Application.isPlaying)
{
Debug.LogError("Translucent Image need an Event Camera for World Space Canvas");
}
vpMatrixIndex = VPMatrixCache.Index.INVALID;
}
else
{
vpMatrixIndex = vpMatrixCache.IndexOf(refCamera);
if (!vpMatrixIndex.IsValid())
vpMatrixIndex = vpMatrixCache.Add(refCamera);
}
}
var rect = rectTransform.rect;
PadRectForRefraction(ref rect);
activeRegion = new ActiveRegion(rect,
rectTransform.localToWorldMatrix,
vpMatrixIndex);
}
/// <summary>
/// Copy material keywords state and properties, except stencil properties
/// </summary>
public static void CopyMaterialPropertiesTo(Material src, Material dst)
{
MaterialUtils.CopyKeyword(src, dst, ShaderID.KW_BACKGROUND_MODE_COLORFUL);
MaterialUtils.CopyKeyword(src, dst, ShaderID.KW_BACKGROUND_MODE_NORMAL);
MaterialUtils.CopyKeyword(src, dst, ShaderID.KW_BACKGROUND_MODE_OPAQUE);
CopyParaformMaterialPropertiesTo(src, dst);
}
void ConnectSource(TranslucentImageSource source)
{
if (!source) return;
source.RegisterActiveRegionProvider(this);
source.blurredScreenChanged += SetBlurTex;
source.blurRegionChanged += SetBlurRegion;
SetBlurTex();
SetBlurRegion();
}
void DisconnectSource(TranslucentImageSource source)
{
if (!source) return;
source.UnRegisterActiveRegionProvider(this);
source.blurredScreenChanged -= SetBlurTex;
source.blurRegionChanged -= SetBlurRegion;
}
void SetBlurTex()
{
if (!source)
return;
materialForRendering.SetTexture(ShaderID.BLUR_TEX, source.BlurredScreen);
}
void SetBlurRegion()
{
if (
!source
|| !canvas
|| !canvas.enabled
)
return;
if (isBirp || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
var minMaxVector = RectUtils.ToMinMaxVector(source.BlurRegionNormalizedScreenSpace);
materialForRendering.SetVector(ShaderID.CROP_REGION, minMaxVector);
}
else
{
materialForRendering.SetVector(ShaderID.CROP_REGION, RectUtils.ToMinMaxVector(source.BlurRegion));
}
}
void OnDirtyMaterial()
{
SetBlurTex();
SetBlurRegion();
CacheEta();
}
bool IsInPrefabMode()
{
#if !UNITY_EDITOR
return false;
#else
var stage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
return stage != null;
#endif
}
bool sourceAcquiredOnStart = false;
void AutoAcquireSource()
{
if (IsInPrefabMode()) return;
if (sourceAcquiredOnStart) return;
source = source ? source : Shims.FindObjectOfType<TranslucentImageSource>();
sourceAcquiredOnStart = true;
}
void OnUndoRedoPerformed()
{
source = _source;
OnDirtyMaterial();
UpdateTrueShadowHash();
}
void WriteVertexData(ref SpanWriter<float> writer)
{
writer.Write(Packing.FloatPacker.Uniform(8)
.Enqueue(foregroundOpacity, 1)
.Enqueue(flatten, 1)
);
writer.Write(Packing.FloatPacker.Uniform(10)
.Enqueue(vibrancy, -1, 2)
.Enqueue(brightness, -1, 1)
);
}
public virtual void ModifyMesh(VertexHelper vh)
{
ListPool<UIVertex>.Get(out var vertices);
vh.GetUIVertexStream(vertices);
Span<float> data = stackalloc float[4];
var writer = SpanUtils.WriterFor(data);
#if LETAI_PARAFORM
Span<float> dataParaform = stackalloc float[4 * 2];
var writerParaformCommon = SpanUtils.WriterFor(dataParaform[..^2]);
var writerParaformVertex = SpanUtils.WriterFor(dataParaform[^2..]);
var paraformEncoder = new Paraform.ParaformVertexDataEncoder(rectTransform, paraformConfig);
#endif
WriteVertexData(ref writer);
// writer.FillRest();
var uv1 = SpanUtils.ToVector4(data);
#if LETAI_PARAFORM
paraformEncoder.WriteCommon(ref writerParaformCommon);
var uv2 = SpanUtils.ToVector4(dataParaform[..4]);
#endif
for (var i = 0; i < vertices.Count; i++)
{
UIVertex vert = vertices[i];
vert.uv1 = uv1;
#if LETAI_PARAFORM
vert.uv2 = uv2;
paraformEncoder.WritePerVertex(ref writerParaformVertex, vert.position);
vert.uv3 = SpanUtils.ToVector4(dataParaform[4..8]);
writerParaformVertex.Reset();
#endif
vertices[i] = vert;
}
vh.Clear();
vh.AddUIVertexTriangleStream(vertices);
// ParaformVertexDataEncoder.ModifyMesh(this, paraformConfig, vh);
}
public virtual void ModifyMesh(Mesh mesh)
{
using var vh = new VertexHelper(mesh);
ModifyMesh(vh);
vh.FillMesh(mesh);
}
}
}