CinemachineVolumeSettings.cs 14 KB

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