ADD : 시네머신 뷰티파이 기능 추가

This commit is contained in:
KINDNICK 2025-09-27 01:02:00 +09:00
parent 6ac6bd357d
commit bc2da34e13
3 changed files with 402 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 00968bcdab263904792f434177f851c8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,392 @@
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Cinemachine;
namespace Streamingle.StreamingleControl.Extensions
{
/// <summary>
/// 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.
/// </summary>
[ExecuteAlways]
[AddComponentMenu("Cinemachine/Procedural/Extensions/Cinemachine Beautify Volume Settings")]
[SaveDuringPlay]
[DisallowMultipleComponent]
public class CinemachineBeautifyVolumeSettings : CinemachineExtension
{
/// <summary>
/// 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.
/// </summary>
public static float s_VolumePriority = 1001f;
/// <summary>
/// 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.
/// </summary>
[Tooltip("This weight will be applied to the Volume when this camera is active")]
public float Weight = 1f;
/// <summary>The reference object for focus tracking</summary>
public enum FocusTrackingMode
{
/// <summary>No focus tracking</summary>
None,
/// <summary>Focus offset is relative to the LookAt target</summary>
LookAtTarget,
/// <summary>Focus offset is relative to the Follow target</summary>
FollowTarget,
/// <summary>Focus offset is relative to the Custom target set here</summary>
CustomTarget,
/// <summary>Focus offset is relative to the camera</summary>
Camera
}
/// <summary>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</summary>
[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;
/// <summary>The target to use if Focus Tracks Target is set to Custom Target</summary>
[Tooltip("The target to use if Focus Tracks Target is set to Custom Target")]
public Transform FocusTarget;
/// <summary>Offset from target distance, to be used with Focus Tracks Target.
/// Offsets the sharpest point away from the focus target</summary>
[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;
/// <summary>
/// If Focus tracking is enabled, this will return the calculated focus distance
/// </summary>
public float CalculatedFocusDistance { get; private set; }
/// <summary>
/// This profile will be applied whenever this virtual camera is live.
/// If null, a default profile will be created automatically.
/// </summary>
[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<VolumeProfile>();
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;
/// <summary>True if the profile is enabled and nontrivial</summary>
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();
}
/// <summary>Called by the editor when the shared asset has been edited</summary>
public void InvalidateCachedProfile()
{
var extraStateCache = new System.Collections.Generic.List<VcamExtraState>();
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<VcamExtraState>(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;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 689a4eb435f05ac4493dc2ee57e61000