using UnityEngine; using System.Collections.Generic; using UnityEngine.Rendering; #if CINEMACHINE_HDRP using UnityEngine.Rendering.HighDefinition; #endif namespace Unity.Cinemachine { /// /// This behaviour will drive the Camera focusDistance property. It can be used to hold focus onto /// a specific object, or (in HDRP) to auto-detect what is in front of the camera and focus on that. /// /// Camera.focusDistance is only available in physical mode, and appropriate processing /// must be installed for it to have any visible effect. /// /// This component's ScreenCenter mode is only available in HDRP projects, and in this mode /// the component cannot be dynamically added at runtime; it must be added in the editor. /// [ExecuteAlways] [AddComponentMenu("Cinemachine/Procedural/Extensions/Cinemachine Auto Focus")] [SaveDuringPlay] [DisallowMultipleComponent] [HelpURL(Documentation.BaseURL + "manual/CinemachineAutoFocus.html")] public class CinemachineAutoFocus : CinemachineExtension { /// 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, /// HDRP only: Focus will be on whatever is located in the depth buffer. /// at the center of the screen ScreenCenter }; /// The camera's focus distance will be set to the distance from the camera to /// the selected target. The Focus Offset field will then modify that distance. [Tooltip("The camera's focus distance will be set to the distance from the camera to " + "the selected target. The Focus Offset field will then modify that distance.")] public FocusTrackingMode FocusTarget; /// The target to use if Focus Target is set to Custom Target [Tooltip("The target to use if Focus Target is set to Custom Target")] public Transform CustomTarget; /// Offsets the sharpest point away in depth from the focus target location [Tooltip("Offsets the sharpest point away in depth from the focus target location.")] public float FocusDepthOffset; /// /// Set this to make the focus adjust gradually to the desired setting. The /// value corresponds approximately to the time the focus will take to adjust to the new value. /// [Tooltip("The value corresponds approximately to the time the focus will take to adjust to the new value.")] public float Damping; #if CINEMACHINE_HDRP /// /// Radius of the AutoFocus sensor in the center of the screen. A value of 1 would fill the screen. /// It's recommended to keep this quite small. Default value is 0.02. /// [Tooltip("Radius of the AutoFocus sensor in the center of the screen. A value of 1 would fill the screen. " + "It's recommended to keep this quite small. Default value is 0.02")] [Range(0, 0.1f)] public float AutoDetectionRadius; CustomPassVolume m_CustomPassVolume; /// Serialized so that the compute shader is included in the build. [SerializeField] ComputeShader m_ComputeShader; void OnDisable() { ReleaseFocusVolume(); } #endif class VcamExtraState : VcamExtraStateBase { public float CurrentFocusDistance; } void Reset() { Damping = 0.2f; FocusTarget = FocusTrackingMode.None; CustomTarget = null; FocusDepthOffset = 0; #if CINEMACHINE_HDRP AutoDetectionRadius = 0.02f; #endif } void OnValidate() { Damping = Mathf.Max(0, Damping); } /// Apply PostProcessing effects /// The virtual camera being processed /// The current pipeline stage /// The current virtual camera state /// The current applicable deltaTime protected override void PostPipelineStageCallback( CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime) { #if CINEMACHINE_HDRP if (FocusTarget != FocusTrackingMode.ScreenCenter || !CinemachineCore.IsLive(vcam)) ReleaseFocusVolume(); #endif // Set the focus after the camera has been fully positioned if (stage == CinemachineCore.Stage.Finalize && FocusTarget != FocusTrackingMode.None) { var extra = GetExtraState(vcam); float focusDistance = 0; Transform focusTarget = null; switch (FocusTarget) { default: break; case FocusTrackingMode.LookAtTarget: if (state.HasLookAt()) focusDistance = (state.GetFinalPosition() - state.ReferenceLookAt).magnitude; else focusTarget = vcam.LookAt; // probably null, but doesn't hurt break; case FocusTrackingMode.FollowTarget: focusTarget = vcam.Follow; break; case FocusTrackingMode.CustomTarget: focusTarget = CustomTarget; break; #if CINEMACHINE_HDRP case FocusTrackingMode.ScreenCenter: focusDistance = FetchAutoFocusDistance(vcam, extra); if (focusDistance < 0) return; // not available, abort break; #endif } if (focusTarget != null) focusDistance += (state.GetFinalPosition() - focusTarget.position).magnitude; focusDistance = Mathf.Max(0.1f, focusDistance + FocusDepthOffset); // Apply damping if (deltaTime >= 0 && vcam.PreviousStateIsValid) focusDistance = extra.CurrentFocusDistance + Damper.Damp( focusDistance - extra.CurrentFocusDistance, Damping, deltaTime); extra.CurrentFocusDistance = focusDistance; state.Lens.PhysicalProperties.FocusDistance = focusDistance; } } #if CINEMACHINE_HDRP float FetchAutoFocusDistance(CinemachineVirtualCameraBase vcam, VcamExtraState extra) { var volume = GetFocusVolume(vcam); if (volume != null && volume.customPasses.Count > 0) { if (volume.customPasses[0] is FocusDistance fd) { fd.KernelRadius = AutoDetectionRadius; fd.CurrentFocusDistance = vcam.PreviousStateIsValid ? extra.CurrentFocusDistance : 0; return fd.ComputedFocusDistance; } } return -1; // unavailable } static Dictionary s_VolumeRefCounts; static List s_ScratchList; [RuntimeInitializeOnLoadMethod] static void InitializeModule() { s_VolumeRefCounts = null; s_ScratchList = null; } CustomPassVolume GetFocusVolume(CinemachineVirtualCameraBase vcam) { if (s_VolumeRefCounts == null || s_VolumeRefCounts.Count == 0) { s_VolumeRefCounts = new Dictionary(); s_ScratchList = new List(); m_CustomPassVolume = null; // re-fetch after domain reload } if (m_CustomPassVolume == null) { var brain = CinemachineCore.FindPotentialTargetBrain(vcam); var cam = brain == null ? null : brain.OutputCamera; if (cam != null) { // Find an existing custom pass volume with our custom shader pass s_ScratchList.Clear(); cam.GetComponents(s_ScratchList); for (int i = 0; i < s_ScratchList.Count; ++i) { var v = s_ScratchList[i]; if (v.injectionPoint == CustomPassInjectionPoint.AfterOpaqueDepthAndNormal && v.customPasses.Count == 1 && v.customPasses[0] is FocusDistance) { m_CustomPassVolume = v; if (!s_VolumeRefCounts.ContainsKey(cam)) s_VolumeRefCounts[cam] = 0; break; } } if (m_CustomPassVolume == null) { m_CustomPassVolume = cam.gameObject.AddComponent(); m_CustomPassVolume.hideFlags = HideFlags.HideAndDontSave; m_CustomPassVolume.isGlobal = true; m_CustomPassVolume.injectionPoint = CustomPassInjectionPoint.AfterOpaqueDepthAndNormal; m_CustomPassVolume.targetCamera = cam; #if UNITY_EDITOR m_CustomPassVolume.runInEditMode = true; #endif var pass = m_CustomPassVolume.AddPassOfType() as FocusDistance; pass.ComputeShader = m_ComputeShader; pass.PushToCamera = false; pass.CurrentFocusDistance = cam.focusDistance; pass.Camera = cam; pass.targetColorBuffer = CustomPass.TargetBuffer.None; pass.targetDepthBuffer = CustomPass.TargetBuffer.Camera; pass.clearFlags = ClearFlag.None; pass.KernelRadius = AutoDetectionRadius; pass.name = GetType().Name; s_VolumeRefCounts[cam] = 0; } var refs = s_VolumeRefCounts[cam]; ++refs; s_VolumeRefCounts[cam] = refs; m_CustomPassVolume.enabled = refs > 0; } } return m_CustomPassVolume; } void ReleaseFocusVolume() { if (m_CustomPassVolume != null && s_VolumeRefCounts != null) { if (m_CustomPassVolume.TryGetComponent(out Camera cam)) { var refs = s_VolumeRefCounts[cam]; --refs; s_VolumeRefCounts[cam] = refs; m_CustomPassVolume.enabled = refs > 0; } } m_CustomPassVolume = null; } #endif } }