CinemachinePostProcessing.cs 17 KB

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