1
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public class BlurConfig : ScriptableObject
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public enum BlurAlgorithmType
|
||||
{
|
||||
ScalableBlur
|
||||
}
|
||||
|
||||
public enum BackgroundFillMode
|
||||
{
|
||||
None,
|
||||
Color
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class BackgroundFill
|
||||
{
|
||||
public BackgroundFillMode mode = BackgroundFillMode.None;
|
||||
public Color color = Color.white;
|
||||
}
|
||||
|
||||
public interface IBlurAlgorithm
|
||||
{
|
||||
void Init(BlurConfig config, bool isBirp);
|
||||
|
||||
void Blur(
|
||||
CommandBuffer cmd,
|
||||
RenderTargetIdentifier src,
|
||||
Rect srcCropRegion,
|
||||
BackgroundFill backgroundFill,
|
||||
RenderTexture target
|
||||
);
|
||||
|
||||
int GetScratchesCount();
|
||||
void GetScratchDescriptor(int index, ref RenderTextureDescriptor descriptor);
|
||||
void SetScratch(int index, RenderTargetIdentifier value);
|
||||
}
|
||||
}
|
@@ -0,0 +1,132 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public class ScalableBlur : IBlurAlgorithm
|
||||
{
|
||||
const int BLUR_PASS = 0;
|
||||
const int CROP_BLUR_PASS = 1;
|
||||
|
||||
readonly RenderTargetIdentifier[] scratches = new RenderTargetIdentifier[14];
|
||||
|
||||
bool isBirp;
|
||||
Material material;
|
||||
ScalableBlurConfig config;
|
||||
|
||||
Material Material
|
||||
{
|
||||
get
|
||||
{
|
||||
if (material == null)
|
||||
Material = new Material(Shader.Find(isBirp
|
||||
? "Hidden/EfficientBlur"
|
||||
: "Hidden/EfficientBlur_UniversalRP"));
|
||||
|
||||
return material;
|
||||
}
|
||||
set => material = value;
|
||||
}
|
||||
|
||||
public void Init(BlurConfig config, bool isBirp)
|
||||
{
|
||||
this.isBirp = isBirp;
|
||||
this.config = (ScalableBlurConfig)config;
|
||||
}
|
||||
|
||||
public void Blur(
|
||||
CommandBuffer cmd,
|
||||
RenderTargetIdentifier src,
|
||||
Rect srcCropRegion,
|
||||
BackgroundFill backgroundFill,
|
||||
RenderTexture target
|
||||
)
|
||||
{
|
||||
float radius = ScaleWithResolution(config.Radius,
|
||||
target.width * srcCropRegion.width,
|
||||
target.height * srcCropRegion.height);
|
||||
ConfigMaterial(radius, srcCropRegion.ToMinMaxVector(), backgroundFill);
|
||||
|
||||
int stepCount = Mathf.Clamp(config.Iteration * 2 - 1, 1, scratches.Length * 2 - 1);
|
||||
|
||||
if(stepCount > 1)
|
||||
cmd.BlitCustom(src, scratches[0], Material, CROP_BLUR_PASS, isBirp);
|
||||
|
||||
var depth = Mathf.Min(config.Iteration - 1, scratches.Length - 1);
|
||||
for (var i = 1; i < stepCount; i++)
|
||||
{
|
||||
var fromIdx = SimplePingPong(i - 1, depth);
|
||||
var toIdx = SimplePingPong(i, depth);
|
||||
cmd.BlitCustom(scratches[fromIdx], scratches[toIdx], Material, 0, isBirp);
|
||||
}
|
||||
|
||||
cmd.BlitCustom(stepCount > 1 ? scratches[0] : src,
|
||||
target,
|
||||
Material,
|
||||
stepCount > 1 ? BLUR_PASS : CROP_BLUR_PASS,
|
||||
isBirp);
|
||||
}
|
||||
|
||||
public int GetScratchesCount()
|
||||
{
|
||||
return Mathf.Min(config.Iteration, scratches.Length);
|
||||
}
|
||||
|
||||
public void GetScratchDescriptor(int index, ref RenderTextureDescriptor descriptor)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
int firstDownsampleFactor = config.Iteration > 0 ? 1 : 0;
|
||||
descriptor.width >>= firstDownsampleFactor;
|
||||
descriptor.height >>= firstDownsampleFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptor.width >>= 1;
|
||||
descriptor.height >>= 1;
|
||||
}
|
||||
if (descriptor.width <= 0) descriptor.width = 1;
|
||||
if (descriptor.height <= 0) descriptor.height = 1;
|
||||
}
|
||||
|
||||
public void SetScratch(int index, RenderTargetIdentifier value)
|
||||
{
|
||||
scratches[index] = value;
|
||||
}
|
||||
|
||||
protected void ConfigMaterial(float radius, Vector4 cropRegion, BackgroundFill backgroundFill)
|
||||
{
|
||||
switch (backgroundFill.mode)
|
||||
{
|
||||
case BackgroundFillMode.None:
|
||||
Material.EnableKeyword("BACKGROUND_FILL_NONE");
|
||||
Material.DisableKeyword("BACKGROUND_FILL_COLOR");
|
||||
break;
|
||||
case BackgroundFillMode.Color:
|
||||
Material.EnableKeyword("BACKGROUND_FILL_COLOR");
|
||||
Material.DisableKeyword("BACKGROUND_FILL_NONE");
|
||||
Material.SetColor(ShaderId.BACKGROUND_COLOR, backgroundFill.color);
|
||||
break;
|
||||
}
|
||||
Material.SetFloat(ShaderId.RADIUS, radius);
|
||||
Material.SetVector(ShaderId.CROP_REGION, cropRegion);
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Relative blur size to maintain same look across multiple resolution
|
||||
/// </summary>
|
||||
float ScaleWithResolution(float baseRadius, float width, float height)
|
||||
{
|
||||
float scaleFactor = Mathf.Min(width, height) / 1080f;
|
||||
scaleFactor = Mathf.Clamp(scaleFactor, .5f, 2f); //too much variation cause artifact
|
||||
return baseRadius * scaleFactor;
|
||||
}
|
||||
|
||||
public static int SimplePingPong(int t, int max)
|
||||
{
|
||||
if (t > max)
|
||||
return 2 * max - t;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
[CreateAssetMenu(fileName = "New Scalable Blur Config",
|
||||
menuName = "Translucent Image/ Scalable Blur Config",
|
||||
order = 100)]
|
||||
public class ScalableBlurConfig : BlurConfig
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Blurriness. Does NOT affect performance")]
|
||||
float radius = 4;
|
||||
[SerializeField]
|
||||
[Tooltip("The number of times to run the algorithm to increase the smoothness of the effect. Can affect performance when increase")]
|
||||
[Range(0, 8)]
|
||||
int iteration = 4;
|
||||
[SerializeField]
|
||||
[Tooltip("How strong the blur is")]
|
||||
float strength;
|
||||
|
||||
/// <summary>
|
||||
/// Distance between the base texel and the texel to be sampled.
|
||||
/// </summary>
|
||||
public float Radius
|
||||
{
|
||||
get { return radius; }
|
||||
set { radius = Mathf.Max(0, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Half the number of time to process the image. It is half because the real number of iteration must alway be even. Using half also make calculation simpler
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Must be non-negative
|
||||
/// </value>
|
||||
public int Iteration
|
||||
{
|
||||
get { return iteration; }
|
||||
set { iteration = Mathf.Max(0, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User friendly property to control the amount of blur
|
||||
/// </summary>
|
||||
///<value>
|
||||
/// Must be non-negative
|
||||
/// </value>
|
||||
public float Strength
|
||||
{
|
||||
get { return strength = Radius * Mathf.Pow(2, Iteration); }
|
||||
set
|
||||
{
|
||||
strength = Mathf.Clamp(value, 0, (1 << 14) * (1 << 14));
|
||||
|
||||
// Bit fiddling would be faster, but need unsafe or .NET Core 3.0+
|
||||
// for BitOperations, and BitConverter that doesn't creates garbages :(
|
||||
radius = Mathf.Sqrt(strength);
|
||||
iteration = 0;
|
||||
while ((1 << iteration) < radius)
|
||||
iteration++;
|
||||
radius = strength / (1 << iteration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.Editor
|
||||
{
|
||||
public class EditorProperty
|
||||
{
|
||||
public readonly SerializedProperty serializedProperty;
|
||||
|
||||
readonly SerializedObject serializedObject;
|
||||
readonly MethodInfo propertySetter;
|
||||
readonly SerializedProperty dirtyFlag;
|
||||
|
||||
public EditorProperty(SerializedObject obj, string name)
|
||||
{
|
||||
var propertyName = char.ToLowerInvariant(name[0]) + name.Substring(1);
|
||||
serializedObject = obj;
|
||||
serializedProperty = serializedObject.FindProperty(propertyName);
|
||||
propertySetter = serializedObject.targetObject.GetType().GetProperty(name).SetMethod;
|
||||
dirtyFlag = serializedObject.FindProperty("modifiedFromInspector");
|
||||
}
|
||||
|
||||
public void Draw(params GUILayoutOption[] options)
|
||||
{
|
||||
using (var scope = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
EditorGUILayout.PropertyField(serializedProperty, options);
|
||||
|
||||
if (!scope.changed)
|
||||
return;
|
||||
|
||||
if (dirtyFlag != null)
|
||||
dirtyFlag.boolValue = true;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
foreach (var target in serializedObject.targetObjects)
|
||||
{
|
||||
switch (serializedProperty.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
propertySetter.Invoke(target, new object[] { serializedProperty.objectReferenceValue });
|
||||
break;
|
||||
case SerializedPropertyType.Float:
|
||||
propertySetter.Invoke(target, new object[] { serializedProperty.floatValue });
|
||||
break;
|
||||
case SerializedPropertyType.Integer:
|
||||
propertySetter.Invoke(target, new object[] { serializedProperty.intValue });
|
||||
break;
|
||||
case SerializedPropertyType.Rect:
|
||||
propertySetter.Invoke(target, new object[] { serializedProperty.rectValue });
|
||||
break;
|
||||
case SerializedPropertyType.Enum:
|
||||
propertySetter.Invoke(target, new object[] { serializedProperty.enumValueIndex });
|
||||
break;
|
||||
case SerializedPropertyType.Boolean:
|
||||
propertySetter.Invoke(target, new object[] { serializedProperty.boolValue });
|
||||
break;
|
||||
case SerializedPropertyType.Color:
|
||||
propertySetter.Invoke(target, new object[] { serializedProperty.colorValue });
|
||||
break;
|
||||
case SerializedPropertyType.Generic:
|
||||
// Not needed for now
|
||||
break;
|
||||
default: throw new NotImplementedException($"Type {serializedProperty.propertyType} is not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "LeTai.TranslucentImage.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"LeTai.TranslucentImage"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"LeTai_TranslucentImage"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.render-pipelines.universal",
|
||||
"expression": "12",
|
||||
"define": "URP12_OR_NEWER"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,175 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.Editor
|
||||
{
|
||||
public class MenuIntegration : MonoBehaviour
|
||||
{
|
||||
[MenuItem("GameObject/UI/Translucent Image", false, 3)]
|
||||
static void CreateTranslucentImage(MenuCommand menuCommand)
|
||||
{
|
||||
// Create a custom game object
|
||||
GameObject go = new GameObject("Translucent Image");
|
||||
go.AddComponent<TranslucentImage>();
|
||||
PlaceUIElementRoot(go, menuCommand);
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/Image/Convert to Translucent Image", false, 3)]
|
||||
static void ConvertToTranslucentImage(MenuCommand menuCommand)
|
||||
{
|
||||
var image = (Image) menuCommand.context;
|
||||
|
||||
var go = image.gameObject;
|
||||
Undo.DestroyObjectImmediate(image);
|
||||
|
||||
var ti = Undo.AddComponent<TranslucentImage>(go);
|
||||
ti.sprite = image.sprite;
|
||||
var color = image.color;
|
||||
ti.color = new Color(color.r, color.g, color.b,
|
||||
1); // sprite blending should be used instead of alpha most of the time
|
||||
ti.raycastTarget = image.raycastTarget;
|
||||
ti.type = image.type;
|
||||
ti.useSpriteMesh = image.useSpriteMesh;
|
||||
ti.preserveAspect = image.preserveAspect;
|
||||
ti.fillCenter = image.fillCenter;
|
||||
ti.fillMethod = image.fillMethod;
|
||||
ti.fillOrigin = image.fillOrigin;
|
||||
ti.fillAmount = image.fillAmount;
|
||||
ti.fillClockwise = image.fillClockwise;
|
||||
}
|
||||
|
||||
|
||||
#region PlaceUIElementRoot
|
||||
|
||||
static void PlaceUIElementRoot(GameObject element, MenuCommand menuCommand)
|
||||
{
|
||||
GameObject parent = menuCommand.context as GameObject;
|
||||
if (parent == null || parent.GetComponentInParent<Canvas>() == null)
|
||||
{
|
||||
parent = GetOrCreateCanvasGameObject();
|
||||
}
|
||||
|
||||
string uniqueName = GameObjectUtility.GetUniqueNameForSibling(parent.transform, element.name);
|
||||
element.name = uniqueName;
|
||||
Undo.RegisterCreatedObjectUndo(element, "Create " + element.name);
|
||||
Undo.SetTransformParent(element.transform, parent.transform, "Parent " + element.name);
|
||||
GameObjectUtility.SetParentAndAlign(element, parent);
|
||||
if (parent != menuCommand.context) // not a context click, so center in sceneview
|
||||
SetPositionVisibleinSceneView(
|
||||
parent.GetComponent<RectTransform>(),
|
||||
element.GetComponent<RectTransform>());
|
||||
|
||||
Selection.activeGameObject = element;
|
||||
}
|
||||
|
||||
// Helper function that returns a Canvas GameObject; preferably a parent of the selection, or other existing Canvas.
|
||||
public static GameObject GetOrCreateCanvasGameObject()
|
||||
{
|
||||
GameObject selectedGo = Selection.activeGameObject;
|
||||
|
||||
// Try to find a gameobject that is the selected GO or one if its parents.
|
||||
Canvas canvas = (selectedGo != null) ? selectedGo.GetComponentInParent<Canvas>() : null;
|
||||
if (canvas != null && canvas.gameObject.activeInHierarchy)
|
||||
return canvas.gameObject;
|
||||
|
||||
// No canvas in selection or its parents? Then use just any canvas..
|
||||
canvas = Shims.FindObjectOfType<Canvas>();
|
||||
if (canvas != null && canvas.gameObject.activeInHierarchy)
|
||||
return canvas.gameObject;
|
||||
|
||||
// No canvas in the scene at all? Then create a new one.
|
||||
return CreateNewUI();
|
||||
}
|
||||
|
||||
public static GameObject CreateNewUI()
|
||||
{
|
||||
// Root for the UI
|
||||
var root = new GameObject("Canvas") {layer = LayerMask.NameToLayer("UI")};
|
||||
Canvas canvas = root.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
root.AddComponent<CanvasScaler>();
|
||||
root.AddComponent<GraphicRaycaster>();
|
||||
Undo.RegisterCreatedObjectUndo(root, "Create " + root.name);
|
||||
|
||||
// if there is no event system add one...
|
||||
CreateEventSystem(false);
|
||||
return root;
|
||||
}
|
||||
|
||||
static void CreateEventSystem(bool select, GameObject parent = null)
|
||||
{
|
||||
var esys = Shims.FindObjectOfType<EventSystem>();
|
||||
if (esys == null)
|
||||
{
|
||||
var eventSystem = new GameObject("EventSystem");
|
||||
GameObjectUtility.SetParentAndAlign(eventSystem, parent);
|
||||
esys = eventSystem.AddComponent<EventSystem>();
|
||||
eventSystem.AddComponent<StandaloneInputModule>();
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(eventSystem, "Create " + eventSystem.name);
|
||||
}
|
||||
|
||||
if (select && esys != null)
|
||||
{
|
||||
Selection.activeGameObject = esys.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
static void SetPositionVisibleinSceneView(RectTransform canvasRTransform, RectTransform itemTransform)
|
||||
{
|
||||
// Find the best scene view
|
||||
SceneView sceneView = SceneView.lastActiveSceneView;
|
||||
if (sceneView == null && SceneView.sceneViews.Count > 0)
|
||||
sceneView = SceneView.sceneViews[0] as SceneView;
|
||||
|
||||
// Couldn't find a SceneView. Don't set position.
|
||||
if (sceneView == null || sceneView.camera == null)
|
||||
return;
|
||||
|
||||
// Create world space Plane from canvas position.
|
||||
Vector2 localPlanePosition;
|
||||
Camera camera = sceneView.camera;
|
||||
Vector3 position = Vector3.zero;
|
||||
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
canvasRTransform,
|
||||
new Vector2(camera.pixelWidth / 2, camera.pixelHeight / 2),
|
||||
camera,
|
||||
out localPlanePosition))
|
||||
{
|
||||
// Adjust for canvas pivot
|
||||
localPlanePosition.x = localPlanePosition.x + canvasRTransform.sizeDelta.x * canvasRTransform.pivot.x;
|
||||
localPlanePosition.y = localPlanePosition.y + canvasRTransform.sizeDelta.y * canvasRTransform.pivot.y;
|
||||
|
||||
localPlanePosition.x = Mathf.Clamp(localPlanePosition.x, 0, canvasRTransform.sizeDelta.x);
|
||||
localPlanePosition.y = Mathf.Clamp(localPlanePosition.y, 0, canvasRTransform.sizeDelta.y);
|
||||
|
||||
// Adjust for anchoring
|
||||
position.x = localPlanePosition.x - canvasRTransform.sizeDelta.x * itemTransform.anchorMin.x;
|
||||
position.y = localPlanePosition.y - canvasRTransform.sizeDelta.y * itemTransform.anchorMin.y;
|
||||
|
||||
Vector3 minLocalPosition;
|
||||
minLocalPosition.x = canvasRTransform.sizeDelta.x * (0 - canvasRTransform.pivot.x) +
|
||||
itemTransform.sizeDelta.x * itemTransform.pivot.x;
|
||||
minLocalPosition.y = canvasRTransform.sizeDelta.y * (0 - canvasRTransform.pivot.y) +
|
||||
itemTransform.sizeDelta.y * itemTransform.pivot.y;
|
||||
|
||||
Vector3 maxLocalPosition;
|
||||
maxLocalPosition.x = canvasRTransform.sizeDelta.x * (1 - canvasRTransform.pivot.x) -
|
||||
itemTransform.sizeDelta.x * itemTransform.pivot.x;
|
||||
maxLocalPosition.y = canvasRTransform.sizeDelta.y * (1 - canvasRTransform.pivot.y) -
|
||||
itemTransform.sizeDelta.y * itemTransform.pivot.y;
|
||||
|
||||
position.x = Mathf.Clamp(position.x, minLocalPosition.x, maxLocalPosition.x);
|
||||
position.y = Mathf.Clamp(position.y, minLocalPosition.y, maxLocalPosition.y);
|
||||
}
|
||||
|
||||
itemTransform.anchoredPosition = position;
|
||||
itemTransform.localRotation = Quaternion.identity;
|
||||
itemTransform.localScale = Vector3.one;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,111 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.AnimatedValues;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.Editor
|
||||
{
|
||||
[CustomEditor(typeof(ScalableBlurConfig))]
|
||||
[CanEditMultipleObjects]
|
||||
public class ScalableBlurConfigEditor : UnityEditor.Editor
|
||||
{
|
||||
readonly AnimBool useAdvancedControl = new AnimBool(false);
|
||||
|
||||
int tab, previousTab;
|
||||
|
||||
EditorProperty radius;
|
||||
EditorProperty iteration;
|
||||
EditorProperty strength;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
LoadTabSelection();
|
||||
useAdvancedControl.value = tab > 0;
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
radius = new EditorProperty(serializedObject, nameof(ScalableBlurConfig.Radius));
|
||||
iteration = new EditorProperty(serializedObject, nameof(ScalableBlurConfig.Iteration));
|
||||
strength = new EditorProperty(serializedObject, nameof(ScalableBlurConfig.Strength));
|
||||
|
||||
// Without this editor will not Repaint automatically when animating
|
||||
useAdvancedControl.valueChanged.AddListener(Repaint);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Draw();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
{
|
||||
DrawTabBar();
|
||||
|
||||
using (var changes = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
serializedObject.Update();
|
||||
DrawTabsContent();
|
||||
if (changes.changed) serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawTabBar()
|
||||
{
|
||||
using (var h = new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
tab = GUILayout.Toolbar(
|
||||
tab,
|
||||
new[] { "Simple", "Advanced" },
|
||||
GUILayout.MinWidth(0),
|
||||
GUILayout.MaxWidth(EditorGUIUtility.pixelsPerPoint * 192)
|
||||
);
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
|
||||
if (tab != previousTab)
|
||||
{
|
||||
GUI.FocusControl(""); // Defocus
|
||||
SaveTabSelection();
|
||||
previousTab = tab;
|
||||
}
|
||||
|
||||
useAdvancedControl.target = tab == 1;
|
||||
}
|
||||
|
||||
void DrawTabsContent()
|
||||
{
|
||||
if (EditorGUILayout.BeginFadeGroup(1 - useAdvancedControl.faded))
|
||||
{
|
||||
// EditorProperty dooesn't invoke getter. Not needed anywhere else.
|
||||
_ = ((ScalableBlurConfig)target).Strength;
|
||||
strength.Draw();
|
||||
}
|
||||
EditorGUILayout.EndFadeGroup();
|
||||
|
||||
if (EditorGUILayout.BeginFadeGroup(useAdvancedControl.faded))
|
||||
{
|
||||
radius.Draw();
|
||||
iteration.Draw();
|
||||
}
|
||||
EditorGUILayout.EndFadeGroup();
|
||||
}
|
||||
|
||||
//Persist selected tab between sessions and instances
|
||||
void SaveTabSelection()
|
||||
{
|
||||
EditorPrefs.SetInt("LETAI_TRANSLUCENTIMAGE_TIS_TAB", tab);
|
||||
}
|
||||
|
||||
void LoadTabSelection()
|
||||
{
|
||||
if (EditorPrefs.HasKey("LETAI_TRANSLUCENTIMAGE_TIS_TAB"))
|
||||
tab = EditorPrefs.GetInt("LETAI_TRANSLUCENTIMAGE_TIS_TAB");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.Editor
|
||||
{
|
||||
class ScenceGizmoAutoDisable : AssetPostprocessor
|
||||
{
|
||||
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
|
||||
{
|
||||
if (!importedAssets.Any(p => p.Contains("TranslucentImage")))
|
||||
return;
|
||||
|
||||
var structAnnotation = Type.GetType("UnityEditor.Annotation, UnityEditor");
|
||||
if (structAnnotation == null) return;
|
||||
|
||||
var fieldClassId = structAnnotation.GetField("classID");
|
||||
var fieldScriptClass = structAnnotation.GetField("scriptClass");
|
||||
var fieldFlags = structAnnotation.GetField("flags");
|
||||
var fieldIconEnabled = structAnnotation.GetField("iconEnabled");
|
||||
|
||||
Type classAnnotationUtility = Type.GetType("UnityEditor.AnnotationUtility, UnityEditor");
|
||||
if (classAnnotationUtility == null) return;
|
||||
|
||||
var methodGetAnnotations = classAnnotationUtility.GetMethod("GetAnnotations", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (methodGetAnnotations == null) return;
|
||||
var methodSetIconEnabled = classAnnotationUtility.GetMethod("SetIconEnabled", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (methodSetIconEnabled == null) return;
|
||||
|
||||
Array annotations = (Array)methodGetAnnotations.Invoke(null, null);
|
||||
foreach (var a in annotations)
|
||||
{
|
||||
string scriptClass = (string)fieldScriptClass.GetValue(a);
|
||||
|
||||
// built in types
|
||||
if (string.IsNullOrEmpty(scriptClass)) continue;
|
||||
|
||||
int classId = (int)fieldClassId.GetValue(a);
|
||||
int flags = (int)fieldFlags.GetValue(a);
|
||||
int iconEnabled = (int)fieldIconEnabled.GetValue(a);
|
||||
|
||||
const int maskHasIcon = 1;
|
||||
bool hasIconFlag = (flags & maskHasIcon) == maskHasIcon;
|
||||
|
||||
if (hasIconFlag
|
||||
&& iconEnabled != 0
|
||||
&& scriptClass.Contains("TranslucentImage"))
|
||||
{
|
||||
methodSetIconEnabled.Invoke(null, new object[] { classId, scriptClass, 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,151 @@
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UI;
|
||||
using UnityEngine;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.Editor
|
||||
{
|
||||
[CustomEditor(typeof(TranslucentImage))]
|
||||
[CanEditMultipleObjects]
|
||||
public class TranslucentImageEditor : ImageEditor
|
||||
{
|
||||
SerializedProperty spriteBlending;
|
||||
SerializedProperty source;
|
||||
SerializedProperty vibrancy;
|
||||
SerializedProperty brightness;
|
||||
SerializedProperty flatten;
|
||||
|
||||
bool materialUsedInDifferentSource;
|
||||
bool usingIncorrectShader;
|
||||
|
||||
Shader correctShader;
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
source = serializedObject.FindProperty("source");
|
||||
spriteBlending = serializedObject.FindProperty("m_spriteBlending");
|
||||
vibrancy = serializedObject.FindProperty("vibrancy");
|
||||
brightness = serializedObject.FindProperty("brightness");
|
||||
flatten = serializedObject.FindProperty("flatten");
|
||||
|
||||
correctShader = Shader.Find("UI/TranslucentImage");
|
||||
|
||||
var self = serializedObject.targetObject as TranslucentImage;
|
||||
if (self)
|
||||
{
|
||||
CheckMaterialUsedInDifferentSource(self);
|
||||
CheckCorrectShader(self);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
var ti = serializedObject.targetObject as TranslucentImage;
|
||||
Debug.Assert(ti != null, "Translucent Image Editor serializedObject target is null");
|
||||
var oldSource = ti.source;
|
||||
var oldMaterial = ti.material;
|
||||
|
||||
base.OnInspectorGUI();
|
||||
if (usingIncorrectShader)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Material should use shader UI/Translucent Image",
|
||||
MessageType.Error);
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(source);
|
||||
if (source.objectReferenceValue == null)
|
||||
{
|
||||
var existingSources = Shims.FindObjectsOfType<TranslucentImageSource>();
|
||||
if (existingSources.Length > 0)
|
||||
{
|
||||
using (new EditorGUI.IndentLevelScope())
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
EditorGUILayout.PrefixLabel("From current Scene");
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
foreach (var s in existingSources)
|
||||
{
|
||||
if (GUILayout.Button(s.gameObject.name))
|
||||
source.objectReferenceValue = s;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("No Translucent Image Source(s) found in current scene", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
if (materialUsedInDifferentSource)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Translucent Images with different Sources" +
|
||||
" should also use different Materials",
|
||||
MessageType.Error);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(spriteBlending);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Shared settings", EditorStyles.centeredGreyMiniLabel);
|
||||
EditorGUILayout.PropertyField(vibrancy);
|
||||
EditorGUILayout.PropertyField(brightness);
|
||||
EditorGUILayout.PropertyField(flatten);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (ti.source != oldSource)
|
||||
OnSourceChanged(ti);
|
||||
if (ti.material != oldMaterial)
|
||||
OnMaterialChanged(ti);
|
||||
}
|
||||
|
||||
void OnSourceChanged(TranslucentImage self)
|
||||
{
|
||||
CheckMaterialUsedInDifferentSource(self);
|
||||
}
|
||||
|
||||
void OnMaterialChanged(TranslucentImage self)
|
||||
{
|
||||
CheckMaterialUsedInDifferentSource(self);
|
||||
CheckCorrectShader(self);
|
||||
}
|
||||
|
||||
private void CheckCorrectShader(TranslucentImage self)
|
||||
{
|
||||
usingIncorrectShader = self.material.shader != correctShader;
|
||||
}
|
||||
|
||||
private void CheckMaterialUsedInDifferentSource(TranslucentImage self)
|
||||
{
|
||||
if (!self.source)
|
||||
{
|
||||
materialUsedInDifferentSource = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var diffSource = Shims.FindObjectsOfType<TranslucentImage>()
|
||||
.Where(ti => ti.source != self.source)
|
||||
.ToList();
|
||||
|
||||
if (!diffSource.Any())
|
||||
{
|
||||
materialUsedInDifferentSource = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var sameMat = diffSource.GroupBy(ti => ti.material).ToList();
|
||||
|
||||
materialUsedInDifferentSource = sameMat.All(group => group.Key == self.material);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.Editor
|
||||
{
|
||||
[CustomEditor(typeof(TranslucentImageSource))]
|
||||
[CanEditMultipleObjects]
|
||||
public class TranslucentImageSourceEditor : UnityEditor.Editor
|
||||
{
|
||||
UnityEditor.Editor configEditor;
|
||||
|
||||
EditorProperty blurConfig;
|
||||
EditorProperty downsample;
|
||||
EditorProperty blurRegion;
|
||||
EditorProperty maxUpdateRate;
|
||||
EditorProperty backgroundFill;
|
||||
EditorProperty preview;
|
||||
|
||||
ScalableBlurConfigEditor ConfigEditor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (configEditor == null)
|
||||
{
|
||||
var config = ((TranslucentImageSource)target).BlurConfig;
|
||||
if (config != null)
|
||||
configEditor = CreateEditor(config);
|
||||
}
|
||||
|
||||
return (ScalableBlurConfigEditor)configEditor;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
blurConfig = new EditorProperty(serializedObject, nameof(TranslucentImageSource.BlurConfig));
|
||||
downsample = new EditorProperty(serializedObject, nameof(TranslucentImageSource.Downsample));
|
||||
blurRegion = new EditorProperty(serializedObject, nameof(TranslucentImageSource.BlurRegion));
|
||||
maxUpdateRate = new EditorProperty(serializedObject, nameof(TranslucentImageSource.MaxUpdateRate));
|
||||
backgroundFill = new EditorProperty(serializedObject, nameof(TranslucentImageSource.BackgroundFill));
|
||||
preview = new EditorProperty(serializedObject, nameof(TranslucentImageSource.Preview));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
blurConfig.Draw();
|
||||
|
||||
var curConfig = (ScalableBlurConfig)blurConfig.serializedProperty.objectReferenceValue;
|
||||
if (!curConfig)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Missing Blur Config", MessageType.Warning);
|
||||
if (GUILayout.Button("New Blur Config File"))
|
||||
{
|
||||
ScalableBlurConfig newConfig = CreateInstance<ScalableBlurConfig>();
|
||||
|
||||
var path = AssetDatabase.GenerateUniqueAssetPath(
|
||||
$"Assets/{SceneManager.GetActiveScene().name} {serializedObject.targetObject.name} Blur Config.asset");
|
||||
AssetDatabase.CreateAsset(newConfig, path);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
EditorGUIUtility.PingObject(newConfig);
|
||||
blurConfig.serializedProperty.objectReferenceValue = newConfig;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigEditor.Draw();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
downsample.Draw();
|
||||
blurRegion.Draw();
|
||||
maxUpdateRate.Draw();
|
||||
backgroundFill.Draw();
|
||||
preview.Draw();
|
||||
|
||||
if (GUI.changed) serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
#if UNITY_2019
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.Editor
|
||||
{
|
||||
public class Unity2019ShaderCompilerBugWorkaround : ScriptableObject
|
||||
{
|
||||
[MenuItem("Tools/Translucent Image/Fix Shader Compile Errors")]
|
||||
static void ReImport()
|
||||
{
|
||||
var guids = AssetDatabase.FindAssets("l:TranslucentImageEditorResources lib");
|
||||
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
var text = File.ReadAllText(path);
|
||||
File.WriteAllText(path, text + "//DELETE ME: Workaround shader not compiling in Unity 2019");
|
||||
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
|
||||
File.WriteAllText(path, text);
|
||||
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
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")]
|
||||
public partial class TranslucentImage : Image, IMeshModifier
|
||||
{
|
||||
/// <summary>
|
||||
/// Source of blur for this image
|
||||
/// </summary>
|
||||
public TranslucentImageSource source;
|
||||
|
||||
/// <summary>
|
||||
/// (De)Saturate the image, 1 is normal, 0 is grey scale, below zero make the image negative
|
||||
/// </summary>
|
||||
[Tooltip("(De)Saturate the image, 1 is normal, 0 is black and white, below zero make the image negative")]
|
||||
[Range(-1, 3)]
|
||||
public float vibrancy = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Brighten/darken the image
|
||||
/// </summary>
|
||||
[Tooltip("Brighten/darken the image")] [Range(-1, 1)]
|
||||
public float brightness = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Flatten the color behind to help keep contrast on varying background
|
||||
/// </summary>
|
||||
[Tooltip("Flatten the color behind to help keep contrast on varying background")] [Range(0, 1)]
|
||||
public float flatten = .1f;
|
||||
|
||||
static readonly int _vibrancyPropId = Shader.PropertyToID("_Vibrancy");
|
||||
static readonly int _brightnessPropId = Shader.PropertyToID("_Brightness");
|
||||
static readonly int _flattenPropId = Shader.PropertyToID("_Flatten");
|
||||
static readonly int _blurTexPropId = Shader.PropertyToID("_BlurTex");
|
||||
static readonly int _cropRegionPropId = Shader.PropertyToID("_CropRegion");
|
||||
|
||||
Material materialForRenderingCached;
|
||||
bool shouldRun;
|
||||
bool isBirp;
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
isBirp = !GraphicsSettings.currentRenderPipeline;
|
||||
|
||||
oldVibrancy = vibrancy;
|
||||
oldBrightness = brightness;
|
||||
oldFlatten = flatten;
|
||||
|
||||
AutoAcquireSource();
|
||||
|
||||
if (material)
|
||||
{
|
||||
//Have to use string comparison as Addressable break object comparision :(
|
||||
if (Application.isPlaying && material.shader.name != "UI/TranslucentImage")
|
||||
{
|
||||
Debug.LogWarning("Translucent Image requires a material using the \"UI/TranslucentImage\" shader");
|
||||
}
|
||||
else if (source)
|
||||
{
|
||||
material.SetTexture(_blurTexPropId, source.BlurredScreen);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_5_6_OR_NEWER
|
||||
if (canvas)
|
||||
canvas.additionalShaderChannels |= AdditionalCanvasShaderChannels.TexCoord1;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsInPrefabMode()
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
return false;
|
||||
#else // UNITY_EDITOR
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
var stage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
|
||||
#else
|
||||
var stage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
|
||||
#endif
|
||||
return stage != null;
|
||||
#endif // !UNITY_EDITOR
|
||||
}
|
||||
|
||||
bool sourceAcquiredOnStart = false;
|
||||
|
||||
void AutoAcquireSource()
|
||||
{
|
||||
if (IsInPrefabMode()) return;
|
||||
if (sourceAcquiredOnStart) return;
|
||||
|
||||
source = source ? source : Shims.FindObjectOfType<TranslucentImageSource>();
|
||||
sourceAcquiredOnStart = true;
|
||||
}
|
||||
|
||||
bool Validate()
|
||||
{
|
||||
if (!IsActive() || !material)
|
||||
return false;
|
||||
|
||||
if (!source)
|
||||
{
|
||||
if (Application.isPlaying && !IsInPrefabMode())
|
||||
{
|
||||
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)");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!source.BlurredScreen)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (!shouldRun) return;
|
||||
|
||||
// After re-enabled
|
||||
if (!materialForRenderingCached)
|
||||
{
|
||||
materialForRenderingCached = materialForRendering;
|
||||
}
|
||||
|
||||
materialForRenderingCached.SetTexture(_blurTexPropId, source.BlurredScreen);
|
||||
|
||||
if (isBirp || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
materialForRenderingCached.SetVector(_cropRegionPropId, source.BlurRegionNormalizedScreenSpace.ToMinMaxVector());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialForRenderingCached.SetVector(_cropRegionPropId, source.BlurRegion.ToMinMaxVector());
|
||||
}
|
||||
|
||||
source.Request();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
shouldRun = Validate();
|
||||
if (!shouldRun) return;
|
||||
|
||||
if (_vibrancyPropId == 0 || _brightnessPropId == 0 || _flattenPropId == 0)
|
||||
return;
|
||||
|
||||
materialForRenderingCached = materialForRendering;
|
||||
|
||||
SyncMaterialProperty(_vibrancyPropId, ref vibrancy, ref oldVibrancy);
|
||||
SyncMaterialProperty(_brightnessPropId, ref brightness, ref oldBrightness);
|
||||
SyncMaterialProperty(_flattenPropId, ref flatten, ref oldFlatten);
|
||||
}
|
||||
|
||||
float oldVibrancy, oldBrightness, oldFlatten;
|
||||
|
||||
/// <summary>
|
||||
/// Sync material property with instance
|
||||
/// </summary>
|
||||
/// <param name="propId">material property id</param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="oldValue"></param>
|
||||
void SyncMaterialProperty(int propId, ref float value, ref float oldValue)
|
||||
{
|
||||
float matValue = materialForRendering.GetFloat(propId);
|
||||
if (Mathf.Abs(matValue - value) > 1e-4)
|
||||
{
|
||||
if (Mathf.Abs(value - oldValue) > 1e-4)
|
||||
{
|
||||
if (materialForRenderingCached)
|
||||
materialForRenderingCached.SetFloat(propId, value);
|
||||
|
||||
material.SetFloat(propId, value);
|
||||
SetMaterialDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
value = matValue;
|
||||
}
|
||||
}
|
||||
|
||||
oldValue = value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
[ExecuteAlways]
|
||||
[AddComponentMenu("UI/Translucent Image", 2)]
|
||||
public partial class TranslucentImage
|
||||
{
|
||||
protected override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
color = Color.white;
|
||||
|
||||
material = FindDefaultMaterial();
|
||||
vibrancy = material.GetFloat(_vibrancyPropId);
|
||||
brightness = material.GetFloat(_brightnessPropId);
|
||||
flatten = material.GetFloat(_flattenPropId);
|
||||
|
||||
source = source ? source : Shims.FindObjectOfType<TranslucentImageSource>();
|
||||
}
|
||||
|
||||
static Material FindDefaultMaterial()
|
||||
{
|
||||
var guid = AssetDatabase.FindAssets("Default-Translucent t:Material l:TranslucentImageResource");
|
||||
|
||||
if (guid.Length == 0)
|
||||
Debug.LogError("Can't find Default-Translucent Material");
|
||||
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid[0]);
|
||||
|
||||
return AssetDatabase.LoadAssetAtPath<Material>(path);
|
||||
}
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
|
||||
SetVerticesDirty();
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,80 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
|
||||
#endif
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public partial class TranslucentImage
|
||||
{
|
||||
[Tooltip("Blend between the sprite and background blur")]
|
||||
[Range(0, 1)]
|
||||
[FormerlySerializedAs("spriteBlending")]
|
||||
public float m_spriteBlending = .65f;
|
||||
|
||||
public float spriteBlending
|
||||
{
|
||||
get => m_spriteBlending;
|
||||
set
|
||||
{
|
||||
m_spriteBlending = value;
|
||||
SetVerticesDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ModifyMesh(VertexHelper vh)
|
||||
{
|
||||
List<UIVertex> vertices = new List<UIVertex>();
|
||||
vh.GetUIVertexStream(vertices);
|
||||
|
||||
for (var i = 0; i < vertices.Count; i++)
|
||||
{
|
||||
UIVertex moddedVertex = vertices[i];
|
||||
moddedVertex.uv1 = new Vector2(spriteBlending,
|
||||
0 //No use for this yet
|
||||
);
|
||||
vertices[i] = moddedVertex;
|
||||
}
|
||||
|
||||
vh.Clear();
|
||||
vh.AddUIVertexTriangleStream(vertices);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
SetVerticesDirty();
|
||||
#if UNITY_EDITOR
|
||||
if (!EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
SetVerticesDirty();
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
protected override void OnDidApplyAnimationProperties()
|
||||
{
|
||||
SetVerticesDirty();
|
||||
base.OnDidApplyAnimationProperties();
|
||||
}
|
||||
|
||||
public virtual void ModifyMesh(Mesh mesh)
|
||||
{
|
||||
using (var vh = new VertexHelper(mesh))
|
||||
{
|
||||
ModifyMesh(vh);
|
||||
vh.FillMesh(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,380 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
#if ENABLE_VR
|
||||
using UnityEngine.XR;
|
||||
#endif
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
/// <summary>
|
||||
/// Common source of blur for Translucent Images.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is an Image effect that blur the render target of the Camera it attached to, then save the result to a global read-only Render Texture
|
||||
/// </remarks>
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(Camera))]
|
||||
[AddComponentMenu("Image Effects/Tai Le Assets/Translucent Image Source")]
|
||||
[HelpURL("https://leloctai.com/asset/translucentimage/docs/articles/customize.html#translucent-image-source")]
|
||||
public partial class TranslucentImageSource : MonoBehaviour
|
||||
{
|
||||
#region Private Field
|
||||
[SerializeField]
|
||||
BlurConfig blurConfig;
|
||||
|
||||
[SerializeField] [Range(0, 3)]
|
||||
[Tooltip("Reduce the size of the screen before processing. Increase will improve performance but create more artifact")]
|
||||
int downsample;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Choose which part of the screen to blur. Smaller region is faster")]
|
||||
Rect blurRegion = new Rect(0, 0, 1, 1);
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("How many time to blur per second. Reduce to increase performance and save battery for slow moving background")]
|
||||
float maxUpdateRate = float.PositiveInfinity;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Preview the effect fullscreen. Not recommended for runtime use")]
|
||||
bool preview;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Fill the background where the frame buffer alpha is 0. Useful for VR Underlay and Passthrough, where these areas would otherwise be black")]
|
||||
BackgroundFill backgroundFill = new BackgroundFill();
|
||||
|
||||
int lastDownsample;
|
||||
Rect lastBlurRegion = new Rect(0, 0, 1, 1);
|
||||
Vector2Int lastCamPixelSize = Vector2Int.zero;
|
||||
float lastUpdate;
|
||||
|
||||
IBlurAlgorithm blurAlgorithm;
|
||||
|
||||
#pragma warning disable 0108
|
||||
Camera camera;
|
||||
#pragma warning restore 0108
|
||||
Material previewMaterial;
|
||||
RenderTexture blurredScreen;
|
||||
CommandBuffer cmd;
|
||||
bool isRequested;
|
||||
#pragma warning disable CS0169
|
||||
bool isForOverlayCanvas;
|
||||
#pragma warning restore CS0169
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
public BlurConfig BlurConfig
|
||||
{
|
||||
get { return blurConfig; }
|
||||
set
|
||||
{
|
||||
blurConfig = value;
|
||||
InitializeBlurAlgorithm();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rendered image will be shrinked by a factor of 2^{{Downsample}} before bluring to reduce processing time
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Must be non-negative. Default to 0
|
||||
/// </value>
|
||||
public int Downsample
|
||||
{
|
||||
get { return downsample; }
|
||||
set { downsample = Mathf.Max(0, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Define the rectangular area on screen that will be blurred.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Between 0 and 1
|
||||
/// </value>
|
||||
public Rect BlurRegion
|
||||
{
|
||||
get { return blurRegion; }
|
||||
set
|
||||
{
|
||||
Vector2 min = new Vector2(1 / (float)Cam.pixelWidth, 1 / (float)Cam.pixelHeight);
|
||||
blurRegion.x = Mathf.Clamp(value.x, 0, 1 - min.x);
|
||||
blurRegion.y = Mathf.Clamp(value.y, 0, 1 - min.y);
|
||||
blurRegion.width = Mathf.Clamp(value.width, min.x, 1 - blurRegion.x);
|
||||
blurRegion.height = Mathf.Clamp(value.height, min.y, 1 - blurRegion.y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of times to update the blurred image each second
|
||||
/// </summary>
|
||||
public float MaxUpdateRate
|
||||
{
|
||||
get => maxUpdateRate;
|
||||
set => maxUpdateRate = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill the background where the frame buffer alpha is 0. Useful for VR Underlay and Passthrough, where these areas would otherwise be black
|
||||
/// </summary>
|
||||
public BackgroundFill BackgroundFill
|
||||
{
|
||||
get => backgroundFill;
|
||||
set => backgroundFill = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render the blurred result to the render target
|
||||
/// </summary>
|
||||
public bool Preview
|
||||
{
|
||||
get => preview;
|
||||
set => preview = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of the image effect. Translucent Image use this as their content (read-only)
|
||||
/// </summary>
|
||||
public RenderTexture BlurredScreen
|
||||
{
|
||||
get { return blurredScreen; }
|
||||
set { blurredScreen = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set in SRP to provide Cam.rect for overlay cameras
|
||||
/// </summary>
|
||||
public Rect CamRectOverride { get; set; } = Rect.zero;
|
||||
|
||||
/// <summary>
|
||||
/// Blur Region rect is relative to Cam.rect . This is relative to the full screen
|
||||
/// </summary>
|
||||
public Rect BlurRegionNormalizedScreenSpace
|
||||
{
|
||||
get
|
||||
{
|
||||
var camRect = CamRectOverride.width == 0 ? Cam.rect : CamRectOverride;
|
||||
camRect.min = Vector2.Max(Vector2.zero, camRect.min);
|
||||
camRect.max = Vector2.Min(Vector2.one, camRect.max);
|
||||
|
||||
return new Rect(camRect.position + BlurRegion.position * camRect.size,
|
||||
camRect.size * BlurRegion.size);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
var camRect = CamRectOverride.width == 0 ? Cam.rect : CamRectOverride;
|
||||
camRect.min = Vector2.Max(Vector2.zero, camRect.min);
|
||||
camRect.max = Vector2.Min(Vector2.one, camRect.max);
|
||||
|
||||
BlurRegion = new Rect((value.position - camRect.position) / camRect.size,
|
||||
value.size / camRect.size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Camera attached to the same GameObject. Cached in field 'camera'
|
||||
/// </summary>
|
||||
internal Camera Cam
|
||||
{
|
||||
get { return camera ? camera : camera = GetComponent<Camera>(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimum time in second to wait before refresh the blurred image.
|
||||
/// If maxUpdateRate non-positive then just stop updating
|
||||
/// </summary>
|
||||
float MinUpdateCycle
|
||||
{
|
||||
get { return (MaxUpdateRate > 0) ? (1f / MaxUpdateRate) : float.PositiveInfinity; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnGUI()
|
||||
{
|
||||
if (!Preview) return;
|
||||
if (UnityEditor.Selection.activeGameObject != gameObject) return;
|
||||
|
||||
var curBlurRegionNSS = BlurRegionNormalizedScreenSpace;
|
||||
var newBlurRegionNSS = ResizableScreenRect.Draw(curBlurRegionNSS);
|
||||
|
||||
if (newBlurRegionNSS != curBlurRegionNSS)
|
||||
{
|
||||
UnityEditor.Undo.RecordObject(this, "Change Blur Region");
|
||||
BlurRegionNormalizedScreenSpace = newBlurRegionNSS;
|
||||
}
|
||||
|
||||
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
UnityEditor.EditorApplication.QueuePlayerLoopUpdate();
|
||||
}
|
||||
#endif
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
previewMaterial = new Material(Shader.Find("Hidden/FillCrop"));
|
||||
|
||||
InitializeBlurAlgorithm();
|
||||
CreateNewBlurredScreen(Vector2Int.RoundToInt(Cam.pixelRect.size));
|
||||
|
||||
lastDownsample = Downsample;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (BlurredScreen)
|
||||
BlurredScreen.Release();
|
||||
}
|
||||
|
||||
void InitializeBlurAlgorithm()
|
||||
{
|
||||
switch (blurConfig)
|
||||
{
|
||||
case ScalableBlurConfig _:
|
||||
blurAlgorithm = new ScalableBlur();
|
||||
break;
|
||||
default:
|
||||
blurAlgorithm = new ScalableBlur();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void CreateNewBlurredScreen(Vector2Int camPixelSize)
|
||||
{
|
||||
if (BlurredScreen)
|
||||
BlurredScreen.Release();
|
||||
|
||||
#if ENABLE_VR
|
||||
if (XRSettings.enabled)
|
||||
{
|
||||
BlurredScreen = new RenderTexture(XRSettings.eyeTextureDesc);
|
||||
BlurredScreen.width = Mathf.RoundToInt(BlurredScreen.width * BlurRegion.width) >> Downsample;
|
||||
BlurredScreen.height = Mathf.RoundToInt(BlurredScreen.height * BlurRegion.height) >> Downsample;
|
||||
BlurredScreen.depth = 0;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
BlurredScreen = new RenderTexture(Mathf.RoundToInt(camPixelSize.x * BlurRegion.width) >> Downsample,
|
||||
Mathf.RoundToInt(camPixelSize.y * BlurRegion.height) >> Downsample, 0);
|
||||
}
|
||||
|
||||
BlurredScreen.antiAliasing = 1;
|
||||
BlurredScreen.useMipMap = false;
|
||||
|
||||
BlurredScreen.name = $"{gameObject.name} Translucent Image Source";
|
||||
BlurredScreen.filterMode = FilterMode.Bilinear;
|
||||
|
||||
BlurredScreen.Create();
|
||||
}
|
||||
|
||||
TextureDimension lastEyeTexDim;
|
||||
|
||||
public void OnBeforeBlur(Vector2Int camPixelSize)
|
||||
{
|
||||
if (
|
||||
BlurredScreen == null
|
||||
|| !BlurredScreen.IsCreated()
|
||||
|| Downsample != lastDownsample
|
||||
|| !BlurRegion.Approximately(lastBlurRegion)
|
||||
|| camPixelSize != lastCamPixelSize
|
||||
#if ENABLE_VR
|
||||
|| XRSettings.deviceEyeTextureDimension != lastEyeTexDim
|
||||
#endif
|
||||
)
|
||||
{
|
||||
CreateNewBlurredScreen(camPixelSize);
|
||||
lastDownsample = Downsample;
|
||||
lastBlurRegion = BlurRegion;
|
||||
lastCamPixelSize = camPixelSize;
|
||||
#if ENABLE_VR
|
||||
lastEyeTexDim = XRSettings.deviceEyeTextureDimension;
|
||||
#endif
|
||||
}
|
||||
|
||||
lastUpdate = GetTrueCurrentTime();
|
||||
}
|
||||
|
||||
|
||||
private void OnRenderImage(RenderTexture source, RenderTexture destination)
|
||||
{
|
||||
if (cmd == null)
|
||||
{
|
||||
cmd = new CommandBuffer();
|
||||
cmd.name = "Translucent Image Source";
|
||||
}
|
||||
|
||||
if (blurAlgorithm != null && BlurConfig != null)
|
||||
{
|
||||
if (ShouldUpdateBlur())
|
||||
{
|
||||
cmd.Clear();
|
||||
|
||||
OnBeforeBlur(Vector2Int.RoundToInt(Cam.pixelRect.size));
|
||||
blurAlgorithm.Init(BlurConfig, true);
|
||||
var blurExecData = new BlurExecutor.BlurExecutionData(source,
|
||||
this,
|
||||
blurAlgorithm);
|
||||
BlurExecutor.ExecuteBlurWithTempTextures(cmd, ref blurExecData);
|
||||
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
|
||||
if (Preview)
|
||||
{
|
||||
previewMaterial.SetVector(ShaderId.CROP_REGION, BlurRegion.ToMinMaxVector());
|
||||
Graphics.Blit(BlurredScreen, destination, previewMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.Blit(source, destination);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.Blit(source, destination);
|
||||
}
|
||||
|
||||
|
||||
isRequested = false;
|
||||
}
|
||||
|
||||
public void Request()
|
||||
{
|
||||
isRequested = true;
|
||||
}
|
||||
|
||||
public bool ShouldUpdateBlur()
|
||||
{
|
||||
if (!enabled)
|
||||
return false;
|
||||
|
||||
if (!Preview && !isRequested)
|
||||
return false;
|
||||
|
||||
float now = GetTrueCurrentTime();
|
||||
bool should = now - lastUpdate >= MinUpdateCycle;
|
||||
|
||||
return should;
|
||||
}
|
||||
|
||||
private static float GetTrueCurrentTime()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
return (float)UnityEditor.EditorApplication.timeSinceStartup;
|
||||
#else
|
||||
return Time.unscaledTime;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
#if LETAI_TRUESHADOW
|
||||
|
||||
using LeTai.TrueShadow.PluginInterfaces;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public partial class TranslucentImage : ITrueShadowCustomHashProvider { }
|
||||
}
|
||||
|
||||
#endif
|
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "LeTai.TranslucentImage.UniversalRP.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:e646286fd75dc0f44bb2f84ffb34408d",
|
||||
"GUID:15fc0a57446b3144c949da3e2b9737a9"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"LeTai_TranslucentImage"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.UniversalRP.Editor
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class RendererFeatureChecker : EditorWindow
|
||||
{
|
||||
static RendererFeatureChecker()
|
||||
{
|
||||
EditorApplication.update += DoCheck;
|
||||
}
|
||||
|
||||
static void DoCheck()
|
||||
{
|
||||
EditorApplication.update -= DoCheck;
|
||||
|
||||
var pipelineAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
|
||||
if (pipelineAsset == null)
|
||||
return;
|
||||
|
||||
var rendererData = (ScriptableRendererData)
|
||||
typeof(UniversalRenderPipelineAsset)
|
||||
.GetProperty("scriptableRendererData", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?.GetValue(pipelineAsset);
|
||||
|
||||
if (rendererData == null)
|
||||
return;
|
||||
|
||||
var haveFeature = rendererData.rendererFeatures.OfType<TranslucentImageBlurSource>().Any();
|
||||
|
||||
if (haveFeature)
|
||||
return;
|
||||
|
||||
const float width = 400f;
|
||||
const float height = 200f;
|
||||
var window = GetWindowWithRect<RendererFeatureChecker>(
|
||||
new Rect(
|
||||
(Screen.width - width) / 2f,
|
||||
(Screen.height - height) / 2f,
|
||||
width, height
|
||||
),
|
||||
true,
|
||||
"Translucent Image",
|
||||
true
|
||||
);
|
||||
|
||||
window.rendererData = rendererData;
|
||||
}
|
||||
|
||||
ScriptableRendererData rendererData;
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
GUILayout.Label("Missing Renderer Feature", EditorStyles.largeLabel);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
GUILayout.Label(
|
||||
"Translucent Image needs a renderer feature added to the active Renderer Asset. Do you want to add it now?",
|
||||
EditorStyles.wordWrappedLabel
|
||||
);
|
||||
|
||||
if (GUILayout.Button("More info", EditorStyles.linkLabel))
|
||||
Application.OpenURL("https://leloctai.com/asset/translucentimage/docs/articles/universalrp.html");
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button("Select Current Renderer Asset"))
|
||||
{
|
||||
EditorGUIUtility.PingObject(rendererData);
|
||||
Selection.activeObject = rendererData;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.UniversalRP.Editor
|
||||
{
|
||||
[CustomEditor(typeof(TranslucentImageBlurSource))]
|
||||
public class TranslucentImageBlurSourceEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
var feature = (TranslucentImageBlurSource)target;
|
||||
if (feature.rendererType == RendererType.Renderer2D)
|
||||
{
|
||||
var ver = Version.Parse(Regex.Replace(Application.unityVersion, @"[^\d.]", "."));
|
||||
if (
|
||||
ver >= new Version(2023, 1, 7) ||
|
||||
(ver.Major == 2022 && ver >= new Version(2022, 3, 7))
|
||||
)
|
||||
EditorGUILayout.HelpBox("The 2D Renderer is sometimes buggy with custom renderer features at the moment.\n\n" +
|
||||
"If you're encountering problems, check if the built-in \"Full Screen Pass Renderer Feature\" is functioning correctly in the same setup. If it does not, Translucent Image is unlikely to be able to work either.\n\n" +
|
||||
"Also, make sure you're on the latest Unity patch release for your minor version", MessageType.Warning, true);
|
||||
else
|
||||
EditorGUILayout.HelpBox("This version of the 2D Renderer includes a bug that prevents Translucent Image from working correctly in many cases. (UUM-14400)", MessageType.Error, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "LeTai.TranslucentImage.UniversalRP",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
|
||||
"GUID:15fc0a57446b3144c949da3e2b9737a9",
|
||||
"GUID:ff218ee40fe2b8648ab3234d56415557"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"LeTai_TranslucentImage"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.render-pipelines.universal",
|
||||
"expression": "12",
|
||||
"define": "URP12_OR_NEWER"
|
||||
},
|
||||
{
|
||||
"name": "Unity",
|
||||
"expression": "[2021.3,2021.3.13)",
|
||||
"define": "UNITY_BUGGED_HAS_PASSES_AFTER_POSTPROCESS"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,220 @@
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
#define HAS_DOUBLEBUFFER_BOTH
|
||||
#endif
|
||||
|
||||
#if UNITY_2023_3_OR_NEWER
|
||||
#define HAS_RENDERGRAPH
|
||||
#endif
|
||||
|
||||
#if URP12_OR_NEWER
|
||||
#define HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
#define HAS_RENDERORDER
|
||||
#else
|
||||
#define STATIC_AFTERPOSTTEX
|
||||
#endif
|
||||
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.UniversalRP
|
||||
{
|
||||
enum RendererType
|
||||
{
|
||||
Universal,
|
||||
Renderer2D
|
||||
}
|
||||
|
||||
[MovedFrom("LeTai.Asset.TranslucentImage.LWRP")]
|
||||
public partial class TranslucentImageBlurRenderPass : ScriptableRenderPass
|
||||
{
|
||||
internal struct PassData
|
||||
{
|
||||
public TranslucentImageSource blurSource;
|
||||
public IBlurAlgorithm blurAlgorithm;
|
||||
public Vector2Int camPixelSize;
|
||||
public bool shouldUpdateBlur;
|
||||
public bool isPreviewing;
|
||||
}
|
||||
|
||||
internal struct SRPassData
|
||||
{
|
||||
#if !HAS_DOUBLEBUFFER_BOTH
|
||||
public RendererType rendererType;
|
||||
public RenderTargetIdentifier cameraColorTarget;
|
||||
#if HAS_RENDERORDER
|
||||
public TranslucentImageBlurSource.RenderOrder renderOrder;
|
||||
#endif
|
||||
#endif
|
||||
public bool canvasDisappearWorkaround;
|
||||
}
|
||||
|
||||
public readonly struct PreviewExecutionData
|
||||
{
|
||||
public readonly TranslucentImageSource blurSource;
|
||||
public readonly RenderTargetIdentifier previewTarget;
|
||||
public readonly Material previewMaterial;
|
||||
|
||||
public PreviewExecutionData(
|
||||
TranslucentImageSource blurSource,
|
||||
RenderTargetIdentifier previewTarget,
|
||||
Material previewMaterial
|
||||
)
|
||||
{
|
||||
this.blurSource = blurSource;
|
||||
this.previewTarget = previewTarget;
|
||||
this.previewMaterial = previewMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
private const string PROFILER_TAG = "Translucent Image Source";
|
||||
|
||||
#if STATIC_AFTERPOSTTEX
|
||||
readonly RenderTargetIdentifier afterPostprocessTexture;
|
||||
#endif
|
||||
|
||||
#if HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
readonly URPRendererInternal urpRendererInternal;
|
||||
#endif
|
||||
|
||||
PassData currentPassData;
|
||||
SRPassData currentSRPassData;
|
||||
Material previewMaterial;
|
||||
|
||||
public Material PreviewMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!previewMaterial)
|
||||
previewMaterial = CoreUtils.CreateEngineMaterial("Hidden/FillCrop_UniversalRP");
|
||||
|
||||
return previewMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
internal TranslucentImageBlurRenderPass(
|
||||
|
||||
|
||||
#if HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
URPRendererInternal urpRendererInternal
|
||||
#endif
|
||||
)
|
||||
{
|
||||
#if STATIC_AFTERPOSTTEX
|
||||
afterPostprocessTexture = new RenderTargetIdentifier(Shader.PropertyToID("_AfterPostProcessTexture"), 0, CubemapFace.Unknown, -1);
|
||||
#endif
|
||||
|
||||
#if HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
this.urpRendererInternal = urpRendererInternal;
|
||||
#endif
|
||||
|
||||
RenderGraphInit();
|
||||
}
|
||||
|
||||
#if !HAS_DOUBLEBUFFER_BOTH
|
||||
RenderTargetIdentifier GetAfterPostColor()
|
||||
{
|
||||
#if STATIC_AFTERPOSTTEX
|
||||
return afterPostprocessTexture;
|
||||
#else
|
||||
return urpRendererInternal.GetAfterPostColor();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
~TranslucentImageBlurRenderPass()
|
||||
{
|
||||
CoreUtils.Destroy(previewMaterial);
|
||||
RenderGraphDispose();
|
||||
}
|
||||
|
||||
internal void SetupSRP(SRPassData srPassData)
|
||||
{
|
||||
currentSRPassData = srPassData;
|
||||
}
|
||||
|
||||
|
||||
internal void Setup(PassData passData)
|
||||
{
|
||||
currentPassData = passData;
|
||||
}
|
||||
|
||||
#if HAS_RENDERGRAPH
|
||||
[Obsolete("This rendering path is for compatibility mode only (when Render Graph is disabled). Use Render Graph API instead.", false)]
|
||||
#endif
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
var cmd = CommandBufferPool.Get(PROFILER_TAG);
|
||||
RenderTargetIdentifier sourceTex;
|
||||
|
||||
#if !HAS_DOUBLEBUFFER_BOTH
|
||||
var isPostProcessEnabled = renderingData.cameraData.postProcessEnabled;
|
||||
void SetSource2DRenderer()
|
||||
{
|
||||
bool useAfterPostTex = isPostProcessEnabled;
|
||||
#if HAS_RENDERORDER
|
||||
useAfterPostTex &= currentSRPassData.renderOrder == TranslucentImageBlurSource.RenderOrder.AfterPostProcessing;
|
||||
#endif
|
||||
sourceTex = useAfterPostTex
|
||||
? GetAfterPostColor()
|
||||
: currentSRPassData.cameraColorTarget;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_DOUBLEBUFFER_BOTH
|
||||
sourceTex = urpRendererInternal.GetBackBuffer();
|
||||
#elif HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
if (currentSRPassData.rendererType == RendererType.Universal)
|
||||
{
|
||||
sourceTex = urpRendererInternal.GetBackBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSource2DRenderer();
|
||||
}
|
||||
#else
|
||||
SetSource2DRenderer();
|
||||
#endif
|
||||
|
||||
var blurSource = currentPassData.blurSource;
|
||||
bool shouldResetTarget = currentSRPassData.canvasDisappearWorkaround && renderingData.cameraData.resolveFinalTarget;
|
||||
|
||||
if (currentPassData.shouldUpdateBlur)
|
||||
{
|
||||
blurSource.OnBeforeBlur(currentPassData.camPixelSize);
|
||||
var blurExecData = new BlurExecutor.BlurExecutionData(sourceTex,
|
||||
blurSource,
|
||||
currentPassData.blurAlgorithm);
|
||||
BlurExecutor.ExecuteBlurWithTempTextures(cmd, ref blurExecData);
|
||||
|
||||
if (shouldResetTarget)
|
||||
CoreUtils.SetRenderTarget(cmd, BuiltinRenderTextureType.CameraTarget);
|
||||
}
|
||||
|
||||
|
||||
if (currentPassData.isPreviewing)
|
||||
{
|
||||
var previewTarget = shouldResetTarget ? BuiltinRenderTextureType.CameraTarget : sourceTex;
|
||||
var previewExecData = new PreviewExecutionData(blurSource,
|
||||
previewTarget,
|
||||
PreviewMaterial);
|
||||
ExecutePreview(cmd, ref previewExecData);
|
||||
}
|
||||
|
||||
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
CommandBufferPool.Release(cmd);
|
||||
}
|
||||
|
||||
public static void ExecutePreview(CommandBuffer cmd, ref PreviewExecutionData data)
|
||||
{
|
||||
var blurSource = data.blurSource;
|
||||
|
||||
data.previewMaterial.SetVector(ShaderId.CROP_REGION, blurSource.BlurRegion.ToMinMaxVector());
|
||||
cmd.BlitCustom(blurSource.BlurredScreen, data.previewTarget, data.previewMaterial, 0);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
#if UNITY_2023_3_OR_NEWER
|
||||
#define HAS_RENDERGRAPH
|
||||
#endif
|
||||
|
||||
#if HAS_RENDERGRAPH
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.RenderGraphModule;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
#endif
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.UniversalRP
|
||||
{
|
||||
public partial class TranslucentImageBlurRenderPass
|
||||
{
|
||||
#if HAS_RENDERGRAPH
|
||||
class BlurRGPassData
|
||||
{
|
||||
public TextureHandle sourceTex;
|
||||
public TextureHandle[] scratches;
|
||||
public TranslucentImageSource blurSource;
|
||||
public IBlurAlgorithm blurAlgorithm;
|
||||
}
|
||||
|
||||
class PreviewRGPassData
|
||||
{
|
||||
public TranslucentImageSource blurSource;
|
||||
public TextureHandle previewTarget;
|
||||
public Material previewMaterial;
|
||||
}
|
||||
|
||||
string[] scratchNames;
|
||||
readonly Dictionary<RenderTexture, RTHandle> blurredScreenHdlDict = new();
|
||||
|
||||
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
||||
{
|
||||
var blurSource = currentPassData.blurSource;
|
||||
|
||||
if (currentPassData.shouldUpdateBlur)
|
||||
{
|
||||
blurSource.OnBeforeBlur(currentPassData.camPixelSize);
|
||||
|
||||
var blurredScreen = blurSource.BlurredScreen;
|
||||
blurredScreenHdlDict.TryGetValue(blurredScreen, out var blurredScreenHdl);
|
||||
if (blurredScreenHdl == null || blurredScreenHdl.rt != blurredScreen)
|
||||
{
|
||||
blurredScreenHdl?.Release();
|
||||
blurredScreenHdl = RTHandles.Alloc(blurredScreen);
|
||||
|
||||
blurredScreenHdlDict[blurredScreen] = blurredScreenHdl;
|
||||
}
|
||||
|
||||
// ReSharper disable once ConvertToUsingDeclaration
|
||||
using (var builder = renderGraph.AddUnsafePass<BlurRGPassData>(PROFILER_TAG, out var data))
|
||||
{
|
||||
var blurAlgorithm = currentPassData.blurAlgorithm;
|
||||
var scratchesCount = blurAlgorithm.GetScratchesCount();
|
||||
|
||||
var resourceData = frameData.Get<UniversalResourceData>();
|
||||
data.sourceTex = resourceData.activeColorTexture;
|
||||
data.scratches = new TextureHandle[scratchesCount];
|
||||
data.blurSource = blurSource;
|
||||
data.blurAlgorithm = blurAlgorithm;
|
||||
|
||||
builder.UseTexture(data.sourceTex, AccessFlags.Read);
|
||||
|
||||
var desc = blurSource.BlurredScreen.descriptor;
|
||||
for (int i = 0; i < scratchesCount; i++)
|
||||
{
|
||||
blurAlgorithm.GetScratchDescriptor(i, ref desc);
|
||||
data.scratches[i] = UniversalRenderer.CreateRenderGraphTexture(renderGraph,
|
||||
desc,
|
||||
scratchNames[i],
|
||||
false,
|
||||
FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp);
|
||||
builder.UseTexture(data.scratches[i], AccessFlags.ReadWrite);
|
||||
}
|
||||
|
||||
var blurredScreenTHdl = renderGraph.ImportTexture(blurredScreenHdl);
|
||||
builder.UseTexture(blurredScreenTHdl, AccessFlags.Write);
|
||||
|
||||
builder.SetRenderFunc(static (BlurRGPassData data, UnsafeGraphContext context) =>
|
||||
{
|
||||
var scratchesCount = data.blurAlgorithm.GetScratchesCount();
|
||||
for (int i = 0; i < scratchesCount; i++)
|
||||
data.blurAlgorithm.SetScratch(i, data.scratches[i]);
|
||||
|
||||
var blurExecData = new BlurExecutor.BlurExecutionData(
|
||||
data.sourceTex,
|
||||
data.blurSource,
|
||||
data.blurAlgorithm
|
||||
);
|
||||
BlurExecutor.ExecuteBlur(CommandBufferHelpers.GetNativeCommandBuffer(context.cmd), ref blurExecData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPassData.isPreviewing)
|
||||
{
|
||||
// ReSharper disable once ConvertToUsingDeclaration
|
||||
using (var builder = renderGraph.AddUnsafePass<PreviewRGPassData>(PROFILER_TAG, out var data))
|
||||
{
|
||||
var resourceData = frameData.Get<UniversalResourceData>();
|
||||
data.blurSource = blurSource;
|
||||
data.previewMaterial = PreviewMaterial;
|
||||
data.previewTarget = resourceData.activeColorTexture;
|
||||
|
||||
builder.UseTexture(data.previewTarget, AccessFlags.Write);
|
||||
|
||||
builder.SetRenderFunc(static (PreviewRGPassData data, UnsafeGraphContext context) =>
|
||||
{
|
||||
var previewExecData = new PreviewExecutionData(
|
||||
data.blurSource,
|
||||
data.previewTarget,
|
||||
data.previewMaterial
|
||||
);
|
||||
ExecutePreview(CommandBufferHelpers.GetNativeCommandBuffer(context.cmd), ref previewExecData);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderGraphInit()
|
||||
{
|
||||
scratchNames = new string[14];
|
||||
for (var i = 0; i < scratchNames.Length; i++)
|
||||
{
|
||||
scratchNames[i] = $"TI_intermediate_rt_{i}";
|
||||
}
|
||||
}
|
||||
|
||||
void RenderGraphDispose()
|
||||
{
|
||||
foreach (var (_, hdl) in blurredScreenHdlDict)
|
||||
{
|
||||
hdl?.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
void RenderGraphInit() { }
|
||||
void RenderGraphDispose() { }
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,331 @@
|
||||
#if !UNITY_2023_3_OR_NEWER
|
||||
#define BUGGY_OVERLAY_CAM_PIXEL_RECT
|
||||
#endif
|
||||
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
#define HAS_DOUBLEBUFFER_BOTH
|
||||
#endif
|
||||
|
||||
#if UNITY_2022_1_OR_NEWER
|
||||
#define HAS_RTHANDLE
|
||||
#define HAS_SETUP_OVERRIDE
|
||||
#endif
|
||||
|
||||
#if URP12_OR_NEWER
|
||||
#define HAS_RENDERORDER
|
||||
#define HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
#endif
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
|
||||
#if HAS_RTHANDLE
|
||||
using RTH = UnityEngine.Rendering.RTHandle;
|
||||
#else
|
||||
using RTH = UnityEngine.Rendering.Universal.RenderTargetHandle;
|
||||
#endif
|
||||
|
||||
|
||||
[assembly: InternalsVisibleTo("LeTai.TranslucentImage.UniversalRP.Editor")]
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage.UniversalRP
|
||||
{
|
||||
#if HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
class URPRendererInternal
|
||||
{
|
||||
ScriptableRenderer renderer;
|
||||
Func<RTH> getBackBufferDelegate;
|
||||
Func<RTH> getAfterPostColorDelegate;
|
||||
|
||||
|
||||
public void CacheRenderer(ScriptableRenderer renderer)
|
||||
{
|
||||
if (this.renderer == renderer) return;
|
||||
|
||||
this.renderer = renderer;
|
||||
|
||||
void CacheBackBufferGetter(object rd)
|
||||
{
|
||||
#if UNITY_2022_1_OR_NEWER
|
||||
const string backBufferMethodName = "PeekBackBuffer";
|
||||
#else
|
||||
const string backBufferMethodName = "GetBackBuffer";
|
||||
#endif
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
var cbs = rd.GetType()
|
||||
.GetField("m_ColorBufferSystem", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(rd);
|
||||
var gbb = cbs.GetType()
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.First(m => m.Name == backBufferMethodName && m.GetParameters().Length == 0);
|
||||
|
||||
getBackBufferDelegate = (Func<RTH>)gbb.CreateDelegate(typeof(Func<RTH>), cbs);
|
||||
}
|
||||
|
||||
#if HAS_DOUBLEBUFFER_BOTH
|
||||
CacheBackBufferGetter(renderer);
|
||||
#else
|
||||
if (renderer is UniversalRenderer ur)
|
||||
{
|
||||
CacheBackBufferGetter(ur);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
getAfterPostColorDelegate =
|
||||
(Func<RTH>)renderer.GetType()
|
||||
.GetProperty("afterPostProcessColorHandle", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetGetMethod(true)
|
||||
.CreateDelegate(typeof(Func<RTH>), renderer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public RenderTargetIdentifier GetBackBuffer()
|
||||
{
|
||||
Debug.Assert(getBackBufferDelegate != null);
|
||||
|
||||
var r = getBackBufferDelegate.Invoke();
|
||||
#if HAS_RTHANDLE
|
||||
return r.nameID;
|
||||
#else
|
||||
return r.Identifier();
|
||||
#endif
|
||||
}
|
||||
|
||||
public RenderTargetIdentifier GetAfterPostColor()
|
||||
{
|
||||
Debug.Assert(getAfterPostColorDelegate != null);
|
||||
|
||||
var r = getAfterPostColorDelegate.Invoke();
|
||||
#if HAS_RTHANDLE
|
||||
return r.nameID;
|
||||
#else
|
||||
return r.Identifier();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[MovedFrom("LeTai.Asset.TranslucentImage.LWRP")]
|
||||
public class TranslucentImageBlurSource : ScriptableRendererFeature
|
||||
{
|
||||
#if HAS_RENDERORDER
|
||||
public enum RenderOrder
|
||||
{
|
||||
AfterPostProcessing,
|
||||
BeforePostProcessing,
|
||||
}
|
||||
|
||||
public RenderOrder renderOrder = RenderOrder.AfterPostProcessing;
|
||||
#endif
|
||||
public bool canvasDisappearWorkaround = false;
|
||||
|
||||
internal RendererType rendererType;
|
||||
|
||||
readonly Dictionary<Camera, TranslucentImageSource> blurSourceCache = new Dictionary<Camera, TranslucentImageSource>();
|
||||
readonly Dictionary<Camera, Camera> baseCameraCache = new Dictionary<Camera, Camera>();
|
||||
|
||||
#if HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
URPRendererInternal urpRendererInternal;
|
||||
#endif
|
||||
TranslucentImageBlurRenderPass pass;
|
||||
IBlurAlgorithm blurAlgorithm;
|
||||
|
||||
/// <summary>
|
||||
/// When adding new Translucent Image Source to existing Camera at run time, the new Source must be registered here
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
public void RegisterSource(TranslucentImageSource source)
|
||||
{
|
||||
blurSourceCache[source.GetComponent<Camera>()] = source;
|
||||
}
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
blurAlgorithm = new ScalableBlur();
|
||||
|
||||
#if HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
urpRendererInternal = new URPRendererInternal();
|
||||
#endif
|
||||
|
||||
// ReSharper disable once JoinDeclarationAndInitializer
|
||||
RenderPassEvent renderPassEvent;
|
||||
#if HAS_RENDERORDER
|
||||
renderPassEvent = renderOrder == RenderOrder.BeforePostProcessing
|
||||
? RenderPassEvent.BeforeRenderingPostProcessing
|
||||
: RenderPassEvent.AfterRenderingPostProcessing;
|
||||
#else
|
||||
renderPassEvent = RenderPassEvent.AfterRendering;
|
||||
#endif
|
||||
pass = new TranslucentImageBlurRenderPass(
|
||||
#if HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
urpRendererInternal
|
||||
#endif
|
||||
) {
|
||||
renderPassEvent = renderPassEvent
|
||||
};
|
||||
|
||||
blurSourceCache.Clear();
|
||||
}
|
||||
|
||||
void SetupSRP(ScriptableRenderer renderer)
|
||||
{
|
||||
#if HAS_DOUBLEBUFFER_UNIVERSAL_RENDERER
|
||||
urpRendererInternal.CacheRenderer(renderer);
|
||||
#endif
|
||||
|
||||
#if UNITY_2021_3_OR_NEWER
|
||||
if (renderer is UniversalRenderer)
|
||||
#else
|
||||
if (renderer is ForwardRenderer)
|
||||
#endif
|
||||
{
|
||||
rendererType = RendererType.Universal;
|
||||
}
|
||||
else
|
||||
{
|
||||
rendererType = RendererType.Renderer2D;
|
||||
}
|
||||
|
||||
pass.SetupSRP(new TranslucentImageBlurRenderPass.SRPassData {
|
||||
#if !HAS_DOUBLEBUFFER_BOTH
|
||||
rendererType = rendererType,
|
||||
#if HAS_RTHANDLE
|
||||
cameraColorTarget = renderer.cameraColorTargetHandle,
|
||||
#else
|
||||
cameraColorTarget = renderer.cameraColorTarget,
|
||||
#endif
|
||||
#if HAS_RENDERORDER
|
||||
renderOrder = renderOrder,
|
||||
#endif
|
||||
#endif // !HAS_DOUBLEBUFFER_BOTH
|
||||
canvasDisappearWorkaround = canvasDisappearWorkaround,
|
||||
});
|
||||
}
|
||||
|
||||
#if HAS_SETUP_OVERRIDE
|
||||
public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
|
||||
{
|
||||
var cameraData = renderingData.cameraData;
|
||||
var blurSource = GetBlurSource(cameraData.camera);
|
||||
|
||||
if (blurSource == null)
|
||||
return;
|
||||
|
||||
SetupSRP(renderer);
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void AddRenderPasses(
|
||||
ScriptableRenderer renderer,
|
||||
ref RenderingData renderingData
|
||||
)
|
||||
{
|
||||
var cameraData = renderingData.cameraData;
|
||||
var camera = renderingData.cameraData.camera;
|
||||
var blurSource = GetBlurSource(camera);
|
||||
|
||||
if (blurSource == null)
|
||||
return;
|
||||
if (blurSource.BlurConfig == null)
|
||||
return;
|
||||
|
||||
blurSource.CamRectOverride = Rect.zero;
|
||||
#if BUGGY_OVERLAY_CAM_PIXEL_RECT
|
||||
if (cameraData.renderType == CameraRenderType.Overlay)
|
||||
{
|
||||
var baseCam = GetBaseCamera(camera);
|
||||
if (baseCam)
|
||||
blurSource.CamRectOverride = baseCam.rect;
|
||||
}
|
||||
#endif
|
||||
|
||||
blurAlgorithm.Init(blurSource.BlurConfig, false);
|
||||
|
||||
|
||||
#if UNITY_BUGGED_HAS_PASSES_AFTER_POSTPROCESS
|
||||
bool applyFinalPostProcessing = renderingData.postProcessingEnabled
|
||||
&& cameraData.resolveFinalTarget
|
||||
&& cameraData.antialiasing == AntialiasingMode.FastApproximateAntialiasing;
|
||||
pass.renderPassEvent = applyFinalPostProcessing ? RenderPassEvent.AfterRenderingPostProcessing : RenderPassEvent.AfterRendering;
|
||||
#endif
|
||||
|
||||
pass.Setup(new TranslucentImageBlurRenderPass.PassData {
|
||||
blurAlgorithm = blurAlgorithm,
|
||||
blurSource = blurSource,
|
||||
camPixelSize = Vector2Int.RoundToInt(GetPixelSize(cameraData).size),
|
||||
shouldUpdateBlur = blurSource.ShouldUpdateBlur(),
|
||||
isPreviewing = blurSource.Preview,
|
||||
});
|
||||
|
||||
#if !HAS_SETUP_OVERRIDE
|
||||
SetupSRP(renderer);
|
||||
#endif
|
||||
|
||||
renderer.EnqueuePass(pass);
|
||||
}
|
||||
|
||||
TranslucentImageSource GetBlurSource(Camera camera)
|
||||
{
|
||||
if (!blurSourceCache.ContainsKey(camera))
|
||||
{
|
||||
blurSourceCache.Add(camera, camera.GetComponent<TranslucentImageSource>());
|
||||
}
|
||||
|
||||
return blurSourceCache[camera];
|
||||
}
|
||||
|
||||
#if BUGGY_OVERLAY_CAM_PIXEL_RECT
|
||||
Camera GetBaseCamera(Camera camera)
|
||||
{
|
||||
if (!baseCameraCache.ContainsKey(camera))
|
||||
{
|
||||
Camera baseCamera = null;
|
||||
|
||||
foreach (var uacd in Shims.FindObjectsOfType<UniversalAdditionalCameraData>())
|
||||
{
|
||||
if (uacd.renderType != CameraRenderType.Base) continue;
|
||||
if (uacd.cameraStack == null) continue;
|
||||
if (!uacd.cameraStack.Contains(camera)) continue;
|
||||
|
||||
baseCamera = uacd.GetComponent<Camera>();
|
||||
}
|
||||
|
||||
baseCameraCache.Add(camera, baseCamera);
|
||||
}
|
||||
|
||||
return baseCameraCache[camera];
|
||||
}
|
||||
|
||||
readonly FieldInfo cameraDataPixelRectField = typeof(CameraData).GetField("pixelRect", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
#endif
|
||||
|
||||
public Rect GetPixelSize(CameraData cameraData)
|
||||
{
|
||||
#if !BUGGY_OVERLAY_CAM_PIXEL_RECT
|
||||
return cameraData.camera.pixelRect;
|
||||
#else
|
||||
if (cameraData.renderType == CameraRenderType.Base)
|
||||
return cameraData.camera.pixelRect;
|
||||
|
||||
if (cameraDataPixelRectField == null)
|
||||
Debug.LogError("CameraData.pixelRect does not exists in this version of URP. Please report a bug.");
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
return (Rect)cameraDataPixelRectField.GetValue(cameraData);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public static class BlurExecutor
|
||||
{
|
||||
static readonly int[] TEMP_RT = new int[14];
|
||||
|
||||
static BlurExecutor()
|
||||
{
|
||||
for (var i = 0; i < TEMP_RT.Length; i++)
|
||||
{
|
||||
TEMP_RT[i] = Shader.PropertyToID($"TI_intermediate_rt_{i}");
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct BlurExecutionData
|
||||
{
|
||||
public readonly RenderTargetIdentifier sourceTex;
|
||||
public readonly TranslucentImageSource blurSource;
|
||||
public readonly IBlurAlgorithm blurAlgorithm;
|
||||
|
||||
public BlurExecutionData(
|
||||
RenderTargetIdentifier sourceTex,
|
||||
TranslucentImageSource blurSource,
|
||||
IBlurAlgorithm blurAlgorithm
|
||||
)
|
||||
{
|
||||
this.sourceTex = sourceTex;
|
||||
this.blurSource = blurSource;
|
||||
this.blurAlgorithm = blurAlgorithm;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExecuteBlurWithTempTextures(CommandBuffer cmd, ref BlurExecutionData data)
|
||||
{
|
||||
var scratchesCount = data.blurAlgorithm.GetScratchesCount();
|
||||
|
||||
var desc = data.blurSource.BlurredScreen.descriptor;
|
||||
desc.msaaSamples = 1;
|
||||
desc.useMipMap = false;
|
||||
desc.depthBufferBits = 0;
|
||||
|
||||
for (int i = 0; i < scratchesCount; i++)
|
||||
{
|
||||
data.blurAlgorithm.GetScratchDescriptor(i, ref desc);
|
||||
cmd.GetTemporaryRT(TEMP_RT[i], desc, FilterMode.Bilinear);
|
||||
data.blurAlgorithm.SetScratch(i, TEMP_RT[i]);
|
||||
}
|
||||
|
||||
{
|
||||
ExecuteBlur(cmd, ref data);
|
||||
}
|
||||
|
||||
for (int i = 0; i < scratchesCount; i++)
|
||||
cmd.ReleaseTemporaryRT(TEMP_RT[i]);
|
||||
}
|
||||
|
||||
public static void ExecuteBlur(CommandBuffer cmd, ref BlurExecutionData data)
|
||||
{
|
||||
var blurSource = data.blurSource;
|
||||
var blurredScreen = blurSource.BlurredScreen;
|
||||
var blurRegion = blurSource.BlurRegion;
|
||||
|
||||
data.blurAlgorithm.Blur(cmd,
|
||||
data.sourceTex,
|
||||
blurRegion,
|
||||
blurSource.BackgroundFill,
|
||||
blurredScreen);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
static Mesh fullscreenTriangle;
|
||||
|
||||
/// <summary>
|
||||
/// A fullscreen triangle mesh.
|
||||
/// </summary>
|
||||
static Mesh FullscreenTriangle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fullscreenTriangle != null)
|
||||
return fullscreenTriangle;
|
||||
|
||||
fullscreenTriangle = new Mesh { name = "Fullscreen Triangle" };
|
||||
fullscreenTriangle.SetVertices(
|
||||
new List<Vector3> {
|
||||
new Vector3(-1f, -1f, 0f),
|
||||
new Vector3(-1f, 3f, 0f),
|
||||
new Vector3(3f, -1f, 0f)
|
||||
}
|
||||
);
|
||||
fullscreenTriangle.SetIndices(new[] { 0, 1, 2 }, MeshTopology.Triangles, 0, false);
|
||||
fullscreenTriangle.UploadMeshData(false);
|
||||
|
||||
return fullscreenTriangle;
|
||||
}
|
||||
}
|
||||
|
||||
public static void BlitCustom(
|
||||
this CommandBuffer cmd,
|
||||
RenderTargetIdentifier source,
|
||||
RenderTargetIdentifier destination,
|
||||
Material material,
|
||||
int passIndex,
|
||||
bool useBuiltin = false
|
||||
)
|
||||
{
|
||||
if (useBuiltin)
|
||||
cmd.Blit(source, destination, material, passIndex);
|
||||
else if (
|
||||
SystemInfo.graphicsShaderLevel >= 30
|
||||
#if !UNITY_2023_1_OR_NEWER
|
||||
&& SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2
|
||||
#endif
|
||||
)
|
||||
cmd.BlitProcedural(source, destination, material, passIndex);
|
||||
else
|
||||
cmd.BlitFullscreenTriangle(source, destination, material, passIndex);
|
||||
}
|
||||
|
||||
public static void BlitFullscreenTriangle(
|
||||
this CommandBuffer cmd,
|
||||
RenderTargetIdentifier source,
|
||||
RenderTargetIdentifier destination,
|
||||
Material material,
|
||||
int pass
|
||||
)
|
||||
{
|
||||
cmd.SetGlobalTexture("_MainTex", source);
|
||||
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
cmd.SetRenderTarget(destination, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
||||
#else
|
||||
cmd.SetRenderTarget(destination);
|
||||
#endif
|
||||
|
||||
cmd.DrawMesh(FullscreenTriangle, Matrix4x4.identity, material, 0, pass);
|
||||
}
|
||||
|
||||
public static void BlitProcedural(
|
||||
this CommandBuffer cmd,
|
||||
RenderTargetIdentifier source,
|
||||
RenderTargetIdentifier destination,
|
||||
Material material,
|
||||
int passIndex
|
||||
)
|
||||
{
|
||||
cmd.SetGlobalTexture(ShaderId.MAIN_TEX, source);
|
||||
cmd.SetRenderTarget(new RenderTargetIdentifier(destination, 0, CubemapFace.Unknown, -1),
|
||||
RenderBufferLoadAction.DontCare,
|
||||
RenderBufferStoreAction.Store,
|
||||
RenderBufferLoadAction.DontCare,
|
||||
RenderBufferStoreAction.DontCare);
|
||||
cmd.DrawProcedural(Matrix4x4.identity, material, passIndex, MeshTopology.Quads, 4, 1, null);
|
||||
}
|
||||
|
||||
/// For normalized screen size
|
||||
internal static bool Approximately(this Rect self, Rect other)
|
||||
{
|
||||
return QuickApproximate(self.x, other.x)
|
||||
&& QuickApproximate(self.y, other.y)
|
||||
&& QuickApproximate(self.width, other.width)
|
||||
&& QuickApproximate(self.height, other.height);
|
||||
}
|
||||
|
||||
const float EPSILON01 = 5.9604644e-8f; // different between 1 and largest float < 1
|
||||
|
||||
private static bool QuickApproximate(float a, float b)
|
||||
{
|
||||
return Mathf.Abs(b - a) < EPSILON01;
|
||||
}
|
||||
|
||||
public static Vector4 ToMinMaxVector(this Rect self)
|
||||
{
|
||||
return new Vector4(
|
||||
self.xMin,
|
||||
self.yMin,
|
||||
self.xMax,
|
||||
self.yMax
|
||||
);
|
||||
}
|
||||
|
||||
public static Vector4 ToVector4(this Rect self)
|
||||
{
|
||||
return new Vector4(
|
||||
self.xMin,
|
||||
self.yMin,
|
||||
self.width,
|
||||
self.height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,167 @@
|
||||
// Due to the use of asmdef in the ../Editor folder, this class cannot be put there,
|
||||
// as then it cannot be referenced outside of the assembly
|
||||
// The Blur region GUI requires the use of OnGUI for interactivity,
|
||||
// so it cannot be completely done within the custom Editor either
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public static class ResizableScreenRect
|
||||
{
|
||||
const float BORDER_THICKNESS = 2;
|
||||
const float DRAG_EXTEND = 16;
|
||||
|
||||
delegate void DragHandlerDelegate(Vector2 delta, ref Rect rect);
|
||||
|
||||
static DragHandlerDelegate currentDragHandler;
|
||||
|
||||
static void DrawSquareFromCenter(Vector2 postion, float extent)
|
||||
{
|
||||
var v = Vector2.one * extent;
|
||||
GUI.DrawTexture(new Rect(postion - v, v * 2), Texture2D.whiteTexture);
|
||||
}
|
||||
|
||||
public static Rect Draw(Rect normalizedScreenRect)
|
||||
{
|
||||
var guiRect = normalizedScreenRect;
|
||||
guiRect.y = 1 - guiRect.y - guiRect.height;
|
||||
guiRect.x *= Screen.width;
|
||||
guiRect.width *= Screen.width;
|
||||
guiRect.y *= Screen.height;
|
||||
guiRect.height *= Screen.height;
|
||||
|
||||
var borderThickness = BORDER_THICKNESS * EditorGUIUtility.pixelsPerPoint;
|
||||
GUI.DrawTexture(new Rect(guiRect.x - borderThickness,
|
||||
guiRect.y - borderThickness,
|
||||
borderThickness,
|
||||
guiRect.height + borderThickness * 2),
|
||||
Texture2D.whiteTexture);
|
||||
GUI.DrawTexture(new Rect(guiRect.x,
|
||||
guiRect.y - borderThickness,
|
||||
guiRect.width + 1,
|
||||
borderThickness),
|
||||
Texture2D.whiteTexture);
|
||||
GUI.DrawTexture(new Rect(guiRect.xMax,
|
||||
guiRect.y - borderThickness,
|
||||
borderThickness,
|
||||
guiRect.height + borderThickness * 2),
|
||||
Texture2D.whiteTexture);
|
||||
GUI.DrawTexture(new Rect(guiRect.x,
|
||||
guiRect.yMax,
|
||||
guiRect.width + 1,
|
||||
borderThickness),
|
||||
Texture2D.whiteTexture);
|
||||
var boxExtend = borderThickness * 2;
|
||||
DrawSquareFromCenter(guiRect.min, boxExtend);
|
||||
DrawSquareFromCenter(guiRect.max, boxExtend);
|
||||
DrawSquareFromCenter(new Vector2(guiRect.xMax, guiRect.yMin), boxExtend);
|
||||
DrawSquareFromCenter(new Vector2(guiRect.xMin, guiRect.yMax), boxExtend);
|
||||
|
||||
return HandleEvent(guiRect);
|
||||
}
|
||||
|
||||
static Rect HandleEvent(Rect guiRect)
|
||||
{
|
||||
if (Event.current.type == EventType.MouseDown)
|
||||
{
|
||||
currentDragHandler = ChooseDragHandler(guiRect, Event.current.mousePosition);
|
||||
}
|
||||
else if (Event.current.type == EventType.MouseUp)
|
||||
{
|
||||
currentDragHandler = null;
|
||||
}
|
||||
else if (Event.current.type == EventType.MouseDrag)
|
||||
{
|
||||
currentDragHandler?.Invoke(Event.current.delta, ref guiRect);
|
||||
}
|
||||
|
||||
var result = guiRect;
|
||||
|
||||
result.x /= Screen.width;
|
||||
result.y /= Screen.height;
|
||||
result.width /= Screen.width;
|
||||
result.height /= Screen.height;
|
||||
result.y = 1 - result.y - result.height;
|
||||
|
||||
result.xMin = Mathf.Max(0, result.xMin);
|
||||
result.yMin = Mathf.Max(0, result.yMin);
|
||||
result.width = Mathf.Min(1, result.width);
|
||||
result.height = Mathf.Min(1, result.height);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static DragHandlerDelegate ChooseDragHandler(Rect rect, Vector2 pointer)
|
||||
{
|
||||
float extend = DRAG_EXTEND * EditorGUIUtility.pixelsPerPoint;
|
||||
|
||||
bool PointerXNear(float point) => Mathf.Abs(point - pointer.x) <= extend;
|
||||
bool PointerYNear(float point) => Mathf.Abs(point - pointer.y) <= extend;
|
||||
|
||||
if (PointerXNear(rect.xMin))
|
||||
{
|
||||
if (PointerYNear(rect.yMin)) return DRAG_HANDLER_TOP_LEFT;
|
||||
if (PointerYNear(rect.yMax)) return DRAG_HANDLER_BOTTOM_LEFT;
|
||||
return DRAG_HANDLER_LEFT;
|
||||
}
|
||||
|
||||
if (PointerXNear(rect.xMax))
|
||||
{
|
||||
if (PointerYNear(rect.yMin)) return DRAG_HANDLER_TOP_RIGHT;
|
||||
if (PointerYNear(rect.yMax)) return DRAG_HANDLER_BOTTOM_RIGHT;
|
||||
return DRAG_HANDLER_RIGHT;
|
||||
}
|
||||
|
||||
if (PointerYNear(rect.yMin)) return DRAG_HANDLER_TOP;
|
||||
if (PointerYNear(rect.yMax)) return DRAG_HANDLER_BOTTOM;
|
||||
return DRAG_HANDLER_CENTER;
|
||||
}
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_CENTER =
|
||||
(Vector2 delta, ref Rect rect) => { rect.position += delta; };
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_LEFT =
|
||||
(Vector2 delta, ref Rect rect) => { rect.xMin += delta.x; };
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_TOP =
|
||||
(Vector2 delta, ref Rect rect) => { rect.yMin += delta.y; };
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_BOTTOM =
|
||||
(Vector2 delta, ref Rect rect) => { rect.yMax += delta.y; };
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_RIGHT =
|
||||
(Vector2 delta, ref Rect rect) => { rect.xMax += delta.x; };
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_TOP_LEFT =
|
||||
(Vector2 delta, ref Rect rect) =>
|
||||
{
|
||||
rect.xMin += delta.x;
|
||||
rect.yMin += delta.y;
|
||||
};
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_TOP_RIGHT =
|
||||
(Vector2 delta, ref Rect rect) =>
|
||||
{
|
||||
rect.xMax += delta.x;
|
||||
rect.yMin += delta.y;
|
||||
};
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_BOTTOM_RIGHT =
|
||||
(Vector2 delta, ref Rect rect) =>
|
||||
{
|
||||
rect.xMax += delta.x;
|
||||
rect.yMax += delta.y;
|
||||
};
|
||||
|
||||
static readonly DragHandlerDelegate DRAG_HANDLER_BOTTOM_LEFT =
|
||||
(Vector2 delta, ref Rect rect) =>
|
||||
{
|
||||
rect.xMin += delta.x;
|
||||
rect.yMax += delta.y;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public static class ShaderId
|
||||
{
|
||||
public static readonly int MAIN_TEX = Shader.PropertyToID("_MainTex");
|
||||
public static readonly int RADIUS = Shader.PropertyToID("_Radius");
|
||||
public static readonly int COLOR = Shader.PropertyToID("_Color");
|
||||
// public static readonly int ENV_TEX = Shader.PropertyToID("_EnvTex");
|
||||
public static readonly int BACKGROUND_COLOR = Shader.PropertyToID("_BackgroundColor");
|
||||
public static readonly int CROP_REGION = Shader.PropertyToID("_CropRegion");
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeTai.Asset.TranslucentImage
|
||||
{
|
||||
public static class Shims
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T FindObjectOfType<T>(bool includeInactive = false, bool sorted = true) where T : Object
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
if (sorted)
|
||||
return Object.FindFirstObjectByType<T>(includeInactive ? FindObjectsInactive.Include : FindObjectsInactive.Exclude);
|
||||
else
|
||||
return Object.FindAnyObjectByType<T>(includeInactive ? FindObjectsInactive.Include : FindObjectsInactive.Exclude);
|
||||
#elif UNITY_2020_1_OR_NEWER
|
||||
return Object.FindObjectOfType<T>(includeInactive);
|
||||
#else
|
||||
return Object.FindObjectOfType<T>();
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T[] FindObjectsOfType<T>(bool includeInactive = false) where T : Object
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
return Object.FindObjectsByType<T>(includeInactive ? FindObjectsInactive.Include : FindObjectsInactive.Exclude,
|
||||
FindObjectsSortMode.None);
|
||||
#elif UNITY_2020_1_OR_NEWER
|
||||
return Object.FindObjectsOfType<T>(includeInactive);
|
||||
#else
|
||||
return Object.FindObjectsOfType<T>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user