CinemachinePostProcessing.cs 16 KB

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