diff --git a/Packages/src/Editor/UIEffectEditor.cs b/Packages/src/Editor/UIEffectEditor.cs index 44a95c04..4608cb97 100644 --- a/Packages/src/Editor/UIEffectEditor.cs +++ b/Packages/src/Editor/UIEffectEditor.cs @@ -25,6 +25,7 @@ public class UIEffect2Editor : Editor private SerializedProperty _samplingFilter; private SerializedProperty _samplingIntensity; + private SerializedProperty _samplingScale; private SerializedProperty _transitionFilter; private SerializedProperty _transitionRate; @@ -71,6 +72,7 @@ private void OnEnable() _samplingFilter = serializedObject.FindProperty("m_SamplingFilter"); _samplingIntensity = serializedObject.FindProperty("m_SamplingIntensity"); + _samplingScale = serializedObject.FindProperty("m_SamplingScale"); _transitionFilter = serializedObject.FindProperty("m_TransitionFilter"); _transitionRate = serializedObject.FindProperty("m_TransitionRate"); @@ -145,6 +147,7 @@ public void DrawProperties() { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(_samplingIntensity); + EditorGUILayout.PropertyField(_samplingScale); EditorGUI.indentLevel--; } diff --git a/Packages/src/Editor/UIEffectReplicaEditor.cs b/Packages/src/Editor/UIEffectReplicaEditor.cs index 7c28cd3c..92d60295 100644 --- a/Packages/src/Editor/UIEffectReplicaEditor.cs +++ b/Packages/src/Editor/UIEffectReplicaEditor.cs @@ -11,12 +11,14 @@ public class UIEffectReplicaEditor : Editor { private SerializedProperty _target; private SerializedProperty _useTargetTransform; + private SerializedProperty _samplingScale; private Editor _uiEffectEditor; private void OnEnable() { _target = serializedObject.FindProperty("m_Target"); _useTargetTransform = serializedObject.FindProperty("m_UseTargetTransform"); + _samplingScale = serializedObject.FindProperty("m_SamplingScale"); } public override void OnInspectorGUI() @@ -24,6 +26,7 @@ public override void OnInspectorGUI() serializedObject.Update(); EditorGUILayout.PropertyField(_target); EditorGUILayout.PropertyField(_useTargetTransform); + EditorGUILayout.PropertyField(_samplingScale); if (_target.objectReferenceValue) { diff --git a/Packages/src/Runtime/Internal/Utilities/PowerRangeAttribute.cs b/Packages/src/Runtime/Internal/Utilities/PowerRangeAttribute.cs new file mode 100644 index 00000000..ca269da0 --- /dev/null +++ b/Packages/src/Runtime/Internal/Utilities/PowerRangeAttribute.cs @@ -0,0 +1,56 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace Coffee.UIEffectInternal +{ + [AttributeUsage(AttributeTargets.Field)] + public sealed class PowerRangeAttribute : PropertyAttribute + { + public readonly float min; + public readonly float max; + public readonly float power; + + public PowerRangeAttribute(float min, float max, float power) + { + this.min = min; + this.max = max; + this.power = power; + } + } + +#if UNITY_EDITOR + [CustomPropertyDrawer(typeof(PowerRangeAttribute))] + internal sealed class RangeDrawer : PropertyDrawer + { + public override void OnGUI(Rect r, SerializedProperty property, GUIContent label) + { + var labelWidth = EditorGUIUtility.labelWidth; + var attr = (PowerRangeAttribute)attribute; + label = EditorGUI.BeginProperty(r, label, property); + + EditorGUI.BeginChangeCheck(); + var rSlider = new Rect(r.x + labelWidth + 1, r.y, r.width - labelWidth - 57, r.height); + var powValue = Mathf.Log(property.floatValue, attr.power); + var powMin = Mathf.Log(attr.min, attr.power); + var powMax = Mathf.Log(attr.max, attr.power); + powValue = GUI.HorizontalSlider(rSlider, powValue, powMin, powMax); + if (EditorGUI.EndChangeCheck()) + { + property.floatValue = Mathf.Clamp(Mathf.Pow(attr.power, powValue), attr.min, attr.max); + } + + EditorGUI.BeginChangeCheck(); + EditorGUIUtility.labelWidth = r.width - 53; + var newValue = EditorGUI.FloatField(r, label, property.floatValue); + if (EditorGUI.EndChangeCheck()) + { + property.floatValue = Mathf.Clamp(newValue, attr.min, attr.max); + } + + EditorGUI.EndProperty(); + EditorGUIUtility.labelWidth = labelWidth; + } + } +#endif +} diff --git a/Packages/src/Runtime/Internal/Utilities/PowerRangeAttribute.cs.meta b/Packages/src/Runtime/Internal/Utilities/PowerRangeAttribute.cs.meta new file mode 100644 index 00000000..f0234a6b --- /dev/null +++ b/Packages/src/Runtime/Internal/Utilities/PowerRangeAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c353a915a3dd4175bed99560cfc1aad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Runtime/UIEffect.cs b/Packages/src/Runtime/UIEffect.cs index 0f35de8e..0ca5fbd3 100644 --- a/Packages/src/Runtime/UIEffect.cs +++ b/Packages/src/Runtime/UIEffect.cs @@ -37,6 +37,10 @@ public class UIEffect : UIEffectBase [SerializeField] protected float m_SamplingIntensity = 0.5f; + [PowerRange(0.01f, 100f, 10f)] + [SerializeField] + protected float m_SamplingScale = 1f; + [SerializeField] protected TransitionFilter m_TransitionFilter = TransitionFilter.None; @@ -205,6 +209,22 @@ public float samplingIntensity } } + public float samplingScale + { + get => m_SamplingScale; + set + { + value = Mathf.Clamp(value, 0.01f, 100); + if (Mathf.Approximately(m_SamplingScale, value)) return; + m_SamplingScale = value; + ApplyContextToMaterial(); + } + } + + public override float actualSamplingScale => samplingFilter != SamplingFilter.None + ? Mathf.Clamp(m_SamplingScale, 0.01f, 100) + : 1; + public TransitionFilter transitionFilter { get => m_TransitionFilter; @@ -651,6 +671,7 @@ public void LoadPreset(UIEffect preset) m_SamplingFilter = preset.m_SamplingFilter; m_SamplingIntensity = preset.m_SamplingIntensity; + m_SamplingScale = preset.m_SamplingScale; m_TransitionFilter = preset.m_TransitionFilter; m_TransitionRate = preset.m_TransitionRate; diff --git a/Packages/src/Runtime/UIEffectBase.cs b/Packages/src/Runtime/UIEffectBase.cs index 6d41ce50..3ad8298c 100644 --- a/Packages/src/Runtime/UIEffectBase.cs +++ b/Packages/src/Runtime/UIEffectBase.cs @@ -32,6 +32,7 @@ public abstract class UIEffectBase : UIBehaviour, IMeshModifier, IMaterialModifi public Graphic graphic => _graphic ? _graphic : _graphic = GetComponent(); public virtual uint effectId => (uint)GetInstanceID(); + public virtual float actualSamplingScale => 1; public virtual UIEffectContext context { @@ -102,7 +103,8 @@ public virtual Material GetModifiedMaterial(Material baseMaterial) } Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial"); - var hash = new Hash128((uint)baseMaterial.GetInstanceID(), effectId, 0, 0); + var samplingScaleId = (uint)(Mathf.InverseLerp(0.01f, 100, actualSamplingScale) * uint.MaxValue); + var hash = new Hash128((uint)baseMaterial.GetInstanceID(), effectId, samplingScaleId, 0); if (!MaterialRepository.Valid(hash, _material)) { Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial > Get or create material"); @@ -201,7 +203,7 @@ public virtual void ApplyContextToMaterial() { if (!isActiveAndEnabled || context == null) return; - context.ApplyToMaterial(_material); + context.ApplyToMaterial(_material, actualSamplingScale); #if UNITY_EDITOR UIEffectProjectSettings.shaderRegistry.RegisterVariant(_material, "UI > UIEffect"); diff --git a/Packages/src/Runtime/UIEffectContext.cs b/Packages/src/Runtime/UIEffectContext.cs index 7b987663..3b0f7764 100644 --- a/Packages/src/Runtime/UIEffectContext.cs +++ b/Packages/src/Runtime/UIEffectContext.cs @@ -23,6 +23,7 @@ public class UIEffectContext private static readonly int s_ColorValue = Shader.PropertyToID("_ColorValue"); private static readonly int s_ColorIntensity = Shader.PropertyToID("_ColorIntensity"); private static readonly int s_SamplingIntensity = Shader.PropertyToID("_SamplingIntensity"); + private static readonly int s_SamplingScale = Shader.PropertyToID("_SamplingScale"); private static readonly int s_TransitionRate = Shader.PropertyToID("_TransitionRate"); private static readonly int s_TransitionReverse = Shader.PropertyToID("_TransitionReverse"); private static readonly int s_TransitionTex = Shader.PropertyToID("_TransitionTex"); @@ -189,7 +190,7 @@ public void CopyFrom(UIEffectContext preset) shadowEffectOnOrigin = preset.shadowEffectOnOrigin; } - public void ApplyToMaterial(Material material) + public void ApplyToMaterial(Material material, float actualSamplingScale = 1f) { if (!material) return; @@ -205,6 +206,7 @@ public void ApplyToMaterial(Material material) material.SetFloat(s_ColorIntensity, Mathf.Clamp01(colorIntensity)); material.SetFloat(s_SamplingIntensity, Mathf.Clamp01(samplingIntensity)); + material.SetFloat(s_SamplingScale, actualSamplingScale); material.SetFloat(s_TransitionRate, Mathf.Clamp01(transitionRate)); material.SetInt(s_TransitionReverse, transitionReverse ? 1 : 0); diff --git a/Packages/src/Runtime/UIEffectReplica.cs b/Packages/src/Runtime/UIEffectReplica.cs index 6bba3700..3d54cfa8 100644 --- a/Packages/src/Runtime/UIEffectReplica.cs +++ b/Packages/src/Runtime/UIEffectReplica.cs @@ -8,6 +8,10 @@ public class UIEffectReplica : UIEffectBase [SerializeField] private UIEffect m_Target; [SerializeField] private bool m_UseTargetTransform = true; + [PowerRange(0.01f, 100, 10f)] + [SerializeField] + protected float m_SamplingScale = 1f; + private UIEffect _currentTarget; private Matrix4x4 _prevTransformHash; @@ -35,6 +39,22 @@ public bool useTargetTransform } } + public float samplingScale + { + get => m_SamplingScale; + set + { + value = Mathf.Clamp(value, 0.01f, 100); + if (Mathf.Approximately(m_SamplingScale, value)) return; + m_SamplingScale = value; + ApplyContextToMaterial(); + } + } + + public override float actualSamplingScale => target && target.samplingFilter != SamplingFilter.None + ? Mathf.Clamp(m_SamplingScale, 0.01f, 100) + : 1; + public override uint effectId => target ? target.effectId : 0; public override UIEffectContext context => target && target.isActiveAndEnabled ? target.context : null; diff --git a/Packages/src/Shaders/UIEffect.cginc b/Packages/src/Shaders/UIEffect.cginc index 8a413d9c..9a1f8c42 100644 --- a/Packages/src/Shaders/UIEffect.cginc +++ b/Packages/src/Shaders/UIEffect.cginc @@ -6,6 +6,7 @@ uniform float _ToneIntensity; uniform half4 _ColorValue; uniform float _ColorIntensity; uniform float _SamplingIntensity; +uniform float _SamplingScale; uniform sampler2D _TransitionTex; uniform float4 _TransitionTex_ST; uniform float _TransitionRate; @@ -39,6 +40,11 @@ uniform float _TargetSoftness; * step(uvMask.x, uv.x) * step(uv.x, uvMask.z) \ * step(uvMask.y, uv.y) * step(uv.y, uvMask.w) +float2 texel_size() +{ + return _MainTex_TexelSize.xy * _SamplingScale; +} + float3 rgb_to_hsv(float3 c) { const float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); @@ -241,7 +247,7 @@ half4 apply_sampling_filter(float2 uv, const float4 uvMask, const float2 uvLocal float4 o = 0; float sum = 0; float2 shift = 0; - const half2 blur = _MainTex_TexelSize.xy * _SamplingIntensity * 2; + const half2 blur = texel_size() * _SamplingIntensity * 2; for (int x = 0; x < KERNEL_SIZE; x++) { shift.x = blur.x * (float(x) - KERNEL_SIZE / 2); @@ -380,13 +386,13 @@ half4 uieffect(float2 uv, const float4 uvMask, const float2 uvLocal) // Sampling.Pixelation #if SAMPLING_PIXELATION { - const half2 pixelSize = max(2, (1 - lerp(0.5, 0.95, _SamplingIntensity)) * _MainTex_TexelSize.zw); + const half2 pixelSize = max(2, (1 - lerp(0.5, 0.95, _SamplingIntensity)) / texel_size()); uv = round(uv * pixelSize) / pixelSize; } // Sampling.RgbShift #elif SAMPLING_RGB_SHIFT { - const half2 offset = half2(_SamplingIntensity * _MainTex_TexelSize.x * 20, 0); + const half2 offset = half2(_SamplingIntensity * texel_size().x * 20, 0); const half2 r = uieffect_internal(uv + offset, uvMask, uvLocal).ra; const half2 g = uieffect_internal(uv, uvMask, uvLocal).ga; const half2 b = uieffect_internal(uv - offset, uvMask, uvLocal).ba; @@ -396,8 +402,8 @@ half4 uieffect(float2 uv, const float4 uvMask, const float2 uvLocal) #elif SAMPLING_EDGE_LUMINANCE || SAMPLING_EDGE_ALPHA { // Pixel size - const float dx = _MainTex_TexelSize.x; - const float dy = _MainTex_TexelSize.y; + const float dx = texel_size().x; + const float dy = texel_size().y; // Pixel values around the current pixel (3x3, 8 neighbors) const float2 uv00 = uv + half2(-dx, -dy);