From bc2da34e1343f23d3ff10cd22708dc7c0bee0eec Mon Sep 17 00:00:00 2001 From: KINDNICK <68893236+KINDNICK@users.noreply.github.com> Date: Sat, 27 Sep 2025 01:02:00 +0900 Subject: [PATCH] =?UTF-8?q?ADD=20:=20=EC=8B=9C=EB=84=A4=EB=A8=B8=EC=8B=A0?= =?UTF-8?q?=20=EB=B7=B0=ED=8B=B0=ED=8C=8C=EC=9D=B4=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StreamingleControl/Extensions.meta | 8 + .../CinemachineBeautifyVolumeSettings.cs | 392 ++++++++++++++++++ .../CinemachineBeautifyVolumeSettings.cs.meta | 2 + 3 files changed, 402 insertions(+) create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Extensions.meta create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Extensions/CinemachineBeautifyVolumeSettings.cs create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Extensions/CinemachineBeautifyVolumeSettings.cs.meta 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