diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Extensions.meta b/Assets/Scripts/Streamingle/StreamingleControl/Extensions.meta
new file mode 100644
index 00000000..d0732316
--- /dev/null
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Extensions.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 00968bcdab263904792f434177f851c8
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Extensions/CinemachineBeautifyVolumeSettings.cs b/Assets/Scripts/Streamingle/StreamingleControl/Extensions/CinemachineBeautifyVolumeSettings.cs
new file mode 100644
index 00000000..a1a64b32
--- /dev/null
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Extensions/CinemachineBeautifyVolumeSettings.cs
@@ -0,0 +1,392 @@
+using UnityEngine;
+using UnityEngine.Rendering;
+
+using Unity.Cinemachine;
+
+namespace Streamingle.StreamingleControl.Extensions
+{
+ ///
+ /// Cinemachine Extension for controlling Beautify post-processing effects.
+ /// This extension allows each Virtual Camera to have its own Beautify settings
+ /// that will be applied when the camera becomes active, similar to CinemachineVolumeSettings.
+ ///
+ [ExecuteAlways]
+ [AddComponentMenu("Cinemachine/Procedural/Extensions/Cinemachine Beautify Volume Settings")]
+ [SaveDuringPlay]
+ [DisallowMultipleComponent]
+ public class CinemachineBeautifyVolumeSettings : CinemachineExtension
+ {
+ ///
+ /// This is the priority for the Beautify volume. It's set to a high number
+ /// to ensure that it overrides other volumes for the active vcam.
+ ///
+ public static float s_VolumePriority = 1001f;
+
+ ///
+ /// The weight that the Beautify profile will have when the camera is fully active.
+ /// It will blend to and from 0 along with the camera.
+ ///
+ [Tooltip("This weight will be applied to the Volume when this camera is active")]
+ public float Weight = 1f;
+
+ /// The reference object for focus tracking
+ public enum FocusTrackingMode
+ {
+ /// No focus tracking
+ None,
+ /// Focus offset is relative to the LookAt target
+ LookAtTarget,
+ /// Focus offset is relative to the Follow target
+ FollowTarget,
+ /// Focus offset is relative to the Custom target set here
+ CustomTarget,
+ /// Focus offset is relative to the camera
+ Camera
+ }
+
+ /// If DOF is enabled, will set the focus distance to be the distance
+ /// from the selected target to the camera. The Focus Offset field will then modify that distance
+ [Tooltip("If DOF is enabled, will set the focus distance to be the distance from the selected target to the camera. The Focus Offset field will then modify that distance.")]
+ public FocusTrackingMode FocusTracking = FocusTrackingMode.None;
+
+ /// The target to use if Focus Tracks Target is set to Custom Target
+ [Tooltip("The target to use if Focus Tracks Target is set to Custom Target")]
+ public Transform FocusTarget;
+
+ /// Offset from target distance, to be used with Focus Tracks Target.
+ /// Offsets the sharpest point away from the focus target
+ [Tooltip("Offset from target distance, to be used with Focus Tracks Target. Offsets the sharpest point away from the focus target.")]
+ public float FocusOffset = 0f;
+
+ ///
+ /// If Focus tracking is enabled, this will return the calculated focus distance
+ ///
+ public float CalculatedFocusDistance { get; private set; }
+
+ ///
+ /// This profile will be applied whenever this virtual camera is live.
+ /// If null, a default profile will be created automatically.
+ ///
+ [Tooltip("This Beautify profile will be applied whenever this virtual camera is live")]
+ public VolumeProfile Profile;
+
+ [Header("DOF Settings")]
+ [Tooltip("Enable or disable DOF effect")]
+ public bool EnableDOF = false;
+
+ [Tooltip("DOF distance value")]
+ public float DOFDistance = 1f;
+
+ [Tooltip("DOF focal length")]
+ public float FocalLength = 0.3f;
+
+ [Tooltip("DOF aperture value")]
+ public float Aperture = 5.6f;
+
+ [Tooltip("Enable bokeh effect")]
+ public bool EnableBokeh = true;
+
+ [Tooltip("Bokeh threshold")]
+ public float BokehThreshold = 1f;
+
+ [Tooltip("Bokeh intensity")]
+ public float BokehIntensity = 2f;
+
+ [Tooltip("Enable foreground blur")]
+ public bool EnableForegroundBlur = true;
+
+ [Tooltip("Foreground blur distance")]
+ public float ForegroundDistance = 0.5f;
+
+ // Private fields for profile management
+ class VcamExtraState : VcamExtraStateBase
+ {
+ public VolumeProfile ProfileCopy;
+
+ public void CreateProfileCopy(VolumeProfile source)
+ {
+ DestroyProfileCopy();
+ if (source == null) return;
+
+ VolumeProfile profile = ScriptableObject.CreateInstance();
+ profile.name = source.name + " (Copy)";
+
+ try
+ {
+ for (int i = 0; i < source.components.Count; ++i)
+ {
+ if (source.components[i] != null)
+ {
+ var itemCopy = UnityEngine.Object.Instantiate(source.components[i]);
+ if (itemCopy != null)
+ {
+ itemCopy.hideFlags = HideFlags.DontSave;
+ profile.components.Add(itemCopy);
+ }
+ }
+ }
+ profile.isDirty = true;
+ ProfileCopy = profile;
+ }
+ catch (System.Exception ex)
+ {
+ UnityEngine.Debug.LogWarning($"[CinemachineBeautifyVolumeSettings] Failed to create profile copy: {ex.Message}");
+ if (profile != null)
+ {
+ if (Application.isPlaying)
+ UnityEngine.Object.Destroy(profile);
+ else
+ UnityEngine.Object.DestroyImmediate(profile);
+ }
+ }
+ }
+
+ public void DestroyProfileCopy()
+ {
+ if (ProfileCopy != null)
+ {
+ try
+ {
+ // First destroy all components to avoid reference issues
+ for (int i = ProfileCopy.components.Count - 1; i >= 0; i--)
+ {
+ if (ProfileCopy.components[i] != null)
+ {
+ if (Application.isPlaying)
+ UnityEngine.Object.Destroy(ProfileCopy.components[i]);
+ else
+ UnityEngine.Object.DestroyImmediate(ProfileCopy.components[i]);
+ }
+ }
+ ProfileCopy.components.Clear();
+
+ // Then destroy the profile itself
+ if (Application.isPlaying)
+ UnityEngine.Object.Destroy(ProfileCopy);
+ else
+ UnityEngine.Object.DestroyImmediate(ProfileCopy);
+ }
+ catch (System.Exception ex)
+ {
+ UnityEngine.Debug.LogWarning($"[CinemachineBeautifyVolumeSettings] Error destroying profile copy: {ex.Message}");
+ }
+ finally
+ {
+ ProfileCopy = null;
+ }
+ }
+ }
+ }
+
+ private System.Type m_BeautifyType;
+ private object m_BeautifyOverride;
+
+ /// True if the profile is enabled and nontrivial
+ public bool IsValid => Profile != null && Profile.components.Count > 0;
+
+ protected override void Awake()
+ {
+ base.Awake();
+ InitializeBeautify();
+ }
+
+ protected override void OnEnable()
+ {
+ InvalidateCachedProfile();
+ }
+
+ protected override void OnDestroy()
+ {
+ InvalidateCachedProfile();
+ base.OnDestroy();
+ }
+
+ /// Called by the editor when the shared asset has been edited
+ public void InvalidateCachedProfile()
+ {
+ var extraStateCache = new System.Collections.Generic.List();
+ GetAllExtraStates(extraStateCache);
+ for (int i = 0; i < extraStateCache.Count; ++i)
+ extraStateCache[i].DestroyProfileCopy();
+ }
+
+ protected override void PostPipelineStageCallback(
+ CinemachineVirtualCameraBase vcam,
+ CinemachineCore.Stage stage,
+ ref CameraState state,
+ float deltaTime)
+ {
+ // Set the focus after the camera has been fully positioned.
+ if (stage == CinemachineCore.Stage.Finalize)
+ {
+ var extra = GetExtraState(vcam);
+ if (!IsValid)
+ extra.DestroyProfileCopy();
+ else
+ {
+ var profile = Profile;
+
+ // Handle Focus Tracking
+ if (FocusTracking == FocusTrackingMode.None)
+ extra.DestroyProfileCopy();
+ else
+ {
+ if (extra.ProfileCopy == null)
+ extra.CreateProfileCopy(Profile);
+ profile = extra.ProfileCopy;
+ }
+
+ // Calculate and apply focus distance for DOF (always if Focus Tracking is not None)
+ if (FocusTracking != FocusTrackingMode.None && profile != null)
+ {
+ var beautifyOverride = FindBeautifyOverride(profile);
+ if (beautifyOverride != null)
+ {
+ float focusDistance = FocusOffset;
+ if (FocusTracking == FocusTrackingMode.LookAtTarget)
+ focusDistance += (state.GetFinalPosition() - state.ReferenceLookAt).magnitude;
+ else
+ {
+ Transform focusTarget = null;
+ switch (FocusTracking)
+ {
+ default: break;
+ case FocusTrackingMode.FollowTarget: focusTarget = vcam.Follow; break;
+ case FocusTrackingMode.CustomTarget: focusTarget = FocusTarget; break;
+ }
+ if (focusTarget != null)
+ focusDistance += (state.GetFinalPosition() - focusTarget.position).magnitude;
+ }
+ CalculatedFocusDistance = focusDistance = Mathf.Max(0, focusDistance);
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldDistance", focusDistance);
+ state.Lens.PhysicalProperties.FocusDistance = focusDistance;
+ profile.isDirty = true;
+ }
+ }
+
+ // Update all Beautify parameters in the profile
+ UpdateBeautifyParameters(profile);
+
+ // Apply the post-processing
+ state.AddCustomBlendable(new CameraState.CustomBlendableItems.Item { Custom = profile, Weight = Weight });
+ }
+ }
+ }
+
+ private void InitializeBeautify()
+ {
+ // Find Beautify type
+ m_BeautifyType = System.Type.GetType("Beautify.Universal.Beautify, Unity.RenderPipelines.Universal.Runtime");
+
+ if (m_BeautifyType == null)
+ {
+ UnityEngine.Debug.LogError("[CinemachineBeautifyVolumeSettings] Beautify 타입을 찾을 수 없습니다. Beautify 에셋이 설치되어 있는지 확인하세요.");
+ return;
+ }
+
+ UnityEngine.Debug.Log("[CinemachineBeautifyVolumeSettings] Beautify type found successfully");
+ }
+
+ private void UpdateBeautifyParameters(VolumeProfile profile)
+ {
+ if (profile == null || m_BeautifyType == null) return;
+
+ // Find or create Beautify override in the profile
+ object beautifyOverride = FindBeautifyOverride(profile);
+
+ if (beautifyOverride != null)
+ {
+ // Update all Beautify parameters
+ SetBeautifyParameter(beautifyOverride, "depthOfField", EnableDOF);
+
+ // Use calculated focus distance if Focus Tracking is active, otherwise use DOFDistance
+ float finalDOFDistance = (FocusTracking != FocusTrackingMode.None) ? CalculatedFocusDistance : DOFDistance;
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldDistance", finalDOFDistance);
+
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldFocalLength", FocalLength);
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldAperture", Aperture);
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldBokeh", EnableBokeh);
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldBokehThreshold", BokehThreshold);
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldBokehIntensity", BokehIntensity);
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldForegroundBlur", EnableForegroundBlur);
+ SetBeautifyParameter(beautifyOverride, "depthOfFieldForegroundDistance", ForegroundDistance);
+
+ profile.isDirty = true;
+ }
+ }
+
+ private object FindBeautifyOverride(VolumeProfile profile)
+ {
+ if (profile == null || m_BeautifyType == null) return null;
+
+ // Try to find existing Beautify override in the profile
+ for (int i = 0; i < profile.components.Count; i++)
+ {
+ if (profile.components[i] != null && profile.components[i].GetType() == m_BeautifyType)
+ {
+ return profile.components[i];
+ }
+ }
+
+ return null;
+ }
+
+
+
+
+
+ private void SetBeautifyParameter(object beautifyOverride, string fieldName, object value)
+ {
+ if (beautifyOverride == null || m_BeautifyType == null) return;
+
+ var field = m_BeautifyType.GetField(fieldName);
+ if (field != null)
+ {
+ var fieldValue = field.GetValue(beautifyOverride);
+ if (fieldValue != null)
+ {
+ var valueProperty = fieldValue.GetType().GetProperty("value");
+ var overrideProperty = fieldValue.GetType().GetProperty("overrideState");
+
+ if (valueProperty != null && overrideProperty != null)
+ {
+ // Enable override state
+ overrideProperty.SetValue(fieldValue, true);
+
+ // Set value with type checking
+ if (valueProperty.PropertyType == value.GetType() ||
+ (valueProperty.PropertyType == typeof(bool) && value is bool) ||
+ (valueProperty.PropertyType == typeof(float) && value is float))
+ {
+ valueProperty.SetValue(fieldValue, value);
+ }
+ }
+ }
+ }
+ }
+
+
+ void OnValidate()
+ {
+ Weight = Mathf.Max(0, Weight);
+ }
+
+ void Reset()
+ {
+ Weight = 1;
+ FocusTracking = FocusTrackingMode.None;
+ FocusTarget = null;
+ FocusOffset = 0;
+ Profile = null;
+ EnableDOF = false;
+ DOFDistance = 1f;
+ FocalLength = 0.3f;
+ Aperture = 5.6f;
+ EnableBokeh = true;
+ BokehThreshold = 1f;
+ BokehIntensity = 2f;
+ EnableForegroundBlur = true;
+ ForegroundDistance = 0.5f;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Extensions/CinemachineBeautifyVolumeSettings.cs.meta b/Assets/Scripts/Streamingle/StreamingleControl/Extensions/CinemachineBeautifyVolumeSettings.cs.meta
new file mode 100644
index 00000000..8ae81aaf
--- /dev/null
+++ b/Assets/Scripts/Streamingle/StreamingleControl/Extensions/CinemachineBeautifyVolumeSettings.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 689a4eb435f05ac4493dc2ee57e61000
\ No newline at end of file