CinemachineVolumeSettings.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. using UnityEngine;
  2. using UnityEngine.Serialization;
  3. #if CINEMACHINE_HDRP
  4. using System.Collections.Generic;
  5. using UnityEngine.Rendering;
  6. #if CINEMACHINE_HDRP_7_3_1
  7. using UnityEngine.Rendering.HighDefinition;
  8. #else
  9. using UnityEngine.Experimental.Rendering.HDPipeline;
  10. #endif
  11. #elif CINEMACHINE_URP
  12. using System.Collections.Generic;
  13. using UnityEngine.Rendering;
  14. using UnityEngine.Rendering.Universal;
  15. #endif
  16. namespace Cinemachine.PostFX
  17. {
  18. #if !(CINEMACHINE_HDRP || CINEMACHINE_URP)
  19. // Workaround for Unity scripting bug
  20. /// <summary>
  21. /// This behaviour is a liaison between Cinemachine with the Post-Processing v3 module.
  22. ///
  23. /// As a component on the Virtual Camera, it holds
  24. /// a Post-Processing Profile asset that will be applied to the Unity camera whenever
  25. /// the Virtual camera is live. It also has the optional functionality of animating
  26. /// the Focus Distance and DepthOfField properties of the Camera State, and
  27. /// applying them to the current Post-Processing profile, provided that profile has a
  28. /// DepthOfField effect that is enabled.
  29. /// </summary>
  30. [AddComponentMenu("")] // Hide in menu
  31. public class CinemachineVolumeSettings : MonoBehaviour {}
  32. #else
  33. /// <summary>
  34. /// This behaviour is a liaison between Cinemachine with the Post-Processing v3 module.
  35. ///
  36. /// As a component on the Virtual Camera, it holds
  37. /// a Post-Processing Profile asset that will be applied to the Unity camera whenever
  38. /// the Virtual camera is live. It also has the optional functionality of animating
  39. /// the Focus Distance and DepthOfField properties of the Camera State, and
  40. /// applying them to the current Post-Processing profile, provided that profile has a
  41. /// DepthOfField effect that is enabled.
  42. /// </summary>
  43. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  44. [ExecuteAlways]
  45. [AddComponentMenu("")] // Hide in menu
  46. [SaveDuringPlay]
  47. [DisallowMultipleComponent]
  48. [HelpURL(Documentation.BaseURL + "manual/CinemachineVolumeSettings.html")]
  49. public class CinemachineVolumeSettings : CinemachineExtension
  50. {
  51. /// <summary>
  52. /// This is the priority for the vcam's PostProcessing volumes. It's set to a high
  53. /// number in order to ensure that it overrides other volumes for the active vcam.
  54. /// You can change this value if necessary to work with other systems.
  55. /// </summary>
  56. static public float s_VolumePriority = 1000f;
  57. /// <summary>This is obsolete, please use m_FocusTracking</summary>
  58. [HideInInspector]
  59. public bool m_FocusTracksTarget;
  60. /// <summary>The reference object for focus tracking</summary>
  61. public enum FocusTrackingMode
  62. {
  63. /// <summary>No focus tracking</summary>
  64. None,
  65. /// <summary>Focus offset is relative to the LookAt target</summary>
  66. LookAtTarget,
  67. /// <summary>Focus offset is relative to the Follow target</summary>
  68. FollowTarget,
  69. /// <summary>Focus offset is relative to the Custom target set here</summary>
  70. CustomTarget,
  71. /// <summary>Focus offset is relative to the camera</summary>
  72. Camera
  73. };
  74. /// <summary>If the profile has the appropriate overrides, will set the base focus
  75. /// distance to be the distance from the selected target to the camera.
  76. /// The Focus Offset field will then modify that distance</summary>
  77. [Tooltip("If the profile has the appropriate overrides, will set the base focus "
  78. + "distance to be the distance from the selected target to the camera."
  79. + "The Focus Offset field will then modify that distance.")]
  80. public FocusTrackingMode m_FocusTracking;
  81. /// <summary>The target to use if Focus Tracks Target is set to Custom Target</summary>
  82. [Tooltip("The target to use if Focus Tracks Target is set to Custom Target")]
  83. public Transform m_FocusTarget;
  84. /// <summary>Offset from target distance, to be used with Focus Tracks Target.
  85. /// Offsets the sharpest point away from the focus target</summary>
  86. [Tooltip("Offset from target distance, to be used with Focus Tracks Target. "
  87. + "Offsets the sharpest point away from the focus target.")]
  88. public float m_FocusOffset;
  89. /// <summary>
  90. /// This profile will be applied whenever this virtual camera is live
  91. /// </summary>
  92. [Tooltip("This profile will be applied whenever this virtual camera is live")]
  93. public VolumeProfile m_Profile;
  94. class VcamExtraState
  95. {
  96. public VolumeProfile mProfileCopy;
  97. public void CreateProfileCopy(VolumeProfile source)
  98. {
  99. DestroyProfileCopy();
  100. VolumeProfile profile = ScriptableObject.CreateInstance<VolumeProfile>();
  101. if (source != null)
  102. {
  103. foreach (var item in source.components)
  104. {
  105. var itemCopy = Instantiate(item);
  106. profile.components.Add(itemCopy);
  107. profile.isDirty = true;
  108. }
  109. }
  110. mProfileCopy = profile;
  111. }
  112. public void DestroyProfileCopy()
  113. {
  114. if (mProfileCopy != null)
  115. RuntimeUtility.DestroyObject(mProfileCopy);
  116. mProfileCopy = null;
  117. }
  118. }
  119. /// <summary>True if the profile is enabled and nontrivial</summary>
  120. public bool IsValid { get { return m_Profile != null && m_Profile.components.Count > 0; } }
  121. /// <summary>Called by the editor when the shared asset has been edited</summary>
  122. public void InvalidateCachedProfile()
  123. {
  124. var list = GetAllExtraStates<VcamExtraState>();
  125. for (int i = 0; i < list.Count; ++i)
  126. list[i].DestroyProfileCopy();
  127. }
  128. protected override void OnEnable()
  129. {
  130. base.OnEnable();
  131. // Map legacy m_FocusTracksTarget to focus mode
  132. if (m_FocusTracksTarget)
  133. {
  134. m_FocusTracking = VirtualCamera.LookAt != null
  135. ? FocusTrackingMode.LookAtTarget : FocusTrackingMode.Camera;
  136. }
  137. m_FocusTracksTarget = false;
  138. }
  139. protected override void OnDestroy()
  140. {
  141. InvalidateCachedProfile();
  142. base.OnDestroy();
  143. }
  144. /// <summary>Apply PostProcessing effects</summary>
  145. /// <param name="vcam">The virtual camera being processed</param>
  146. /// <param name="stage">The current pipeline stage</param>
  147. /// <param name="state">The current virtual camera state</param>
  148. /// <param name="deltaTime">The current applicable deltaTime</param>
  149. protected override void PostPipelineStageCallback(
  150. CinemachineVirtualCameraBase vcam,
  151. CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
  152. {
  153. // Set the focus after the camera has been fully positioned.
  154. if (stage == CinemachineCore.Stage.Finalize)
  155. {
  156. var extra = GetExtraState<VcamExtraState>(vcam);
  157. if (!IsValid)
  158. extra.DestroyProfileCopy();
  159. else
  160. {
  161. var profile = m_Profile;
  162. // Handle Follow Focus
  163. if (m_FocusTracking == FocusTrackingMode.None)
  164. extra.DestroyProfileCopy();
  165. else
  166. {
  167. if (extra.mProfileCopy == null)
  168. extra.CreateProfileCopy(m_Profile);
  169. profile = extra.mProfileCopy;
  170. if (profile.TryGet(out DepthOfField dof))
  171. {
  172. float focusDistance = m_FocusOffset;
  173. if (m_FocusTracking == FocusTrackingMode.LookAtTarget)
  174. focusDistance += (state.FinalPosition - state.ReferenceLookAt).magnitude;
  175. else
  176. {
  177. Transform focusTarget = null;
  178. switch (m_FocusTracking)
  179. {
  180. default: break;
  181. case FocusTrackingMode.FollowTarget: focusTarget = VirtualCamera.Follow; break;
  182. case FocusTrackingMode.CustomTarget: focusTarget = m_FocusTarget; break;
  183. }
  184. if (focusTarget != null)
  185. focusDistance += (state.FinalPosition - focusTarget.position).magnitude;
  186. }
  187. #if UNITY_2022_2_OR_NEWER
  188. state.Lens.FocusDistance =
  189. #endif
  190. dof.focusDistance.value = Mathf.Max(0, focusDistance);
  191. profile.isDirty = true;
  192. }
  193. }
  194. // Apply the post-processing
  195. state.AddCustomBlendable(new CameraState.CustomBlendable(profile, 1));
  196. }
  197. }
  198. }
  199. static void OnCameraCut(CinemachineBrain brain)
  200. {
  201. //Debug.Log($"Camera cut to {brain.ActiveVirtualCamera.Name}");
  202. #if CINEMACHINE_HDRP_7_3_1
  203. // Reset temporal effects
  204. var cam = brain.OutputCamera;
  205. if (cam != null)
  206. {
  207. HDCamera hdCam = HDCamera.GetOrCreate(cam);
  208. hdCam.volumetricHistoryIsValid = false;
  209. hdCam.colorPyramidHistoryIsValid = false;
  210. hdCam.Reset();
  211. }
  212. #elif CINEMACHINE_URP_14
  213. // Reset temporal effects
  214. var cam = brain.OutputCamera;
  215. if (cam != null && cam.TryGetComponent<UniversalAdditionalCameraData>(out var data))
  216. data.resetHistory = true;
  217. #endif
  218. }
  219. static void ApplyPostFX(CinemachineBrain brain)
  220. {
  221. CameraState state = brain.CurrentCameraState;
  222. int numBlendables = state.NumCustomBlendables;
  223. var volumes = GetDynamicBrainVolumes(brain, numBlendables);
  224. for (int i = 0; i < volumes.Count; ++i)
  225. {
  226. volumes[i].weight = 0;
  227. volumes[i].sharedProfile = null;
  228. volumes[i].profile = null;
  229. }
  230. Volume firstVolume = null;
  231. int numPPblendables = 0;
  232. for (int i = 0; i < numBlendables; ++i)
  233. {
  234. var b = state.GetCustomBlendable(i);
  235. var profile = b.m_Custom as VolumeProfile;
  236. if (!(profile == null)) // in case it was deleted
  237. {
  238. var v = volumes[i];
  239. if (firstVolume == null)
  240. firstVolume = v;
  241. v.sharedProfile = profile;
  242. v.isGlobal = true;
  243. v.priority = s_VolumePriority - (numBlendables - i) - 1;
  244. v.weight = b.m_Weight;
  245. ++numPPblendables;
  246. }
  247. #if false // set this to true to force first weight to 1
  248. // If more than one volume, then set the frst one's weight to 1
  249. if (numPPblendables > 1)
  250. firstVolume.weight = 1;
  251. #endif
  252. }
  253. // if (firstVolume != null)
  254. // Debug.Log($"Applied post FX for {numPPblendables} PP blendables in {brain.ActiveVirtualCamera.Name}");
  255. }
  256. static string sVolumeOwnerName = "__CMVolumes";
  257. static List<Volume> sVolumes = new List<Volume>();
  258. static List<Volume> GetDynamicBrainVolumes(CinemachineBrain brain, int minVolumes)
  259. {
  260. // Locate the camera's child object that holds our dynamic volumes
  261. GameObject volumeOwner = null;
  262. Transform t = brain.transform;
  263. int numChildren = t.childCount;
  264. sVolumes.Clear();
  265. for (int i = 0; volumeOwner == null && i < numChildren; ++i)
  266. {
  267. GameObject child = t.GetChild(i).gameObject;
  268. if (child.hideFlags == HideFlags.HideAndDontSave)
  269. {
  270. child.GetComponents(sVolumes);
  271. if (sVolumes.Count > 0)
  272. volumeOwner = child;
  273. }
  274. }
  275. if (minVolumes > 0)
  276. {
  277. if (volumeOwner == null)
  278. {
  279. volumeOwner = new GameObject(sVolumeOwnerName);
  280. volumeOwner.hideFlags = HideFlags.HideAndDontSave;
  281. volumeOwner.transform.parent = t;
  282. }
  283. // Update the volume's layer so it will be seen
  284. #if CINEMACHINE_HDRP
  285. var data = brain.gameObject.GetComponent<HDAdditionalCameraData>();
  286. #elif CINEMACHINE_URP
  287. var data = brain.gameObject.GetComponent<UniversalAdditionalCameraData>();
  288. #endif
  289. if (data != null)
  290. {
  291. int mask = data.volumeLayerMask;
  292. for (int i = 0; i < 32; ++i)
  293. {
  294. if ((mask & (1 << i)) != 0)
  295. {
  296. volumeOwner.layer = i;
  297. break;
  298. }
  299. }
  300. }
  301. while (sVolumes.Count < minVolumes)
  302. sVolumes.Add(volumeOwner.gameObject.AddComponent<Volume>());
  303. }
  304. return sVolumes;
  305. }
  306. #if UNITY_EDITOR
  307. [UnityEditor.InitializeOnLoad]
  308. class EditorInitialize { static EditorInitialize() { InitializeModule(); } }
  309. #endif
  310. [RuntimeInitializeOnLoadMethod]
  311. static void InitializeModule()
  312. {
  313. // Afetr the brain pushes the state to the camera, hook in to the PostFX
  314. CinemachineCore.CameraUpdatedEvent.RemoveListener(ApplyPostFX);
  315. CinemachineCore.CameraUpdatedEvent.AddListener(ApplyPostFX);
  316. CinemachineCore.CameraCutEvent.RemoveListener(OnCameraCut);
  317. CinemachineCore.CameraCutEvent.AddListener(OnCameraCut);
  318. }
  319. }
  320. #endif
  321. }