396 lines
16 KiB
C#
396 lines
16 KiB
C#
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);
|
|
|
|
// Only apply volume settings if this camera is live (active)
|
|
bool isLive = CinemachineCore.IsLive(vcam);
|
|
|
|
if (!IsValid || !isLive)
|
|
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;
|
|
}
|
|
}
|
|
} |