ADD : 시네머신 뷰티파이 기능 추가
This commit is contained in:
parent
6ac6bd357d
commit
bc2da34e13
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00968bcdab263904792f434177f851c8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 689a4eb435f05ac4493dc2ee57e61000
|
||||
Loading…
x
Reference in New Issue
Block a user