PostProcessVolume.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. using System.Collections.Generic;
  2. namespace UnityEngine.Rendering.PostProcessing
  3. {
  4. //
  5. // Here's a quick look at the architecture of this framework and how it's integrated into Unity
  6. // (written between versions 5.6 and 2017.1):
  7. //
  8. // Users have to be able to plug in their own effects without having to modify the codebase and
  9. // these custom effects should work out-of-the-box with all the other features we provide
  10. // (volume blending etc). This relies on heavy use of polymorphism, but the only way to get
  11. // the serialization system to work well with polymorphism in Unity is to use ScriptableObjects.
  12. //
  13. // Users can push their custom effects at different (hardcoded) injection points.
  14. //
  15. // Each effect consists of at least two classes (+ shaders): a POD "Settings" class which only
  16. // stores parameters, and a "Renderer" class that holds the rendering logic. Settings are linked
  17. // to renderers using a PostProcessAttribute. These are automatically collected at init time
  18. // using reflection. Settings in this case are ScriptableObjects, we only need to serialize
  19. // these.
  20. //
  21. // We could store these settings object straight into each volume and call it a day, but
  22. // unfortunately there's one feature of Unity that doesn't work well with scene-stored assets:
  23. // prefabs. So we need to store all of these settings in a disk-asset and treat them as
  24. // sub-assets.
  25. //
  26. // Note: We have to use ScriptableObject for everything but these don't work with the Animator
  27. // tool. It's unfortunate but it's the only way to make it easily extensible. On the other
  28. // hand, users can animate post-processing effects using Volumes or straight up scripting.
  29. //
  30. // Volume blending leverages the physics system for distance checks to the nearest point on
  31. // volume colliders. Each volume can have several colliders or any type (cube, mesh...), making
  32. // it quite a powerful feature to use.
  33. //
  34. // Volumes & blending are handled by a singleton manager (see PostProcessManager).
  35. //
  36. // Rendering is handled by a PostProcessLayer component living on the camera, which mean you
  37. // can easily toggle post-processing on & off or change the anti-aliasing type per-camera,
  38. // which is very useful when doing multi-layered camera rendering or any other technique that
  39. // involves multiple-camera setups. This PostProcessLayer component can also filters volumes
  40. // by layers (as in Unity layers) so you can easily choose which volumes should affect the
  41. // camera.
  42. //
  43. // All post-processing shaders MUST use the custom Standard Shader Library bundled with the
  44. // framework. The reason for that is because the codebase is meant to work without any
  45. // modification on the Classic Render Pipelines (Forward, Deferred...) and the upcoming
  46. // Scriptable Render Pipelines (HDPipe, LDPipe...). But these don't have compatible shader
  47. // libraries so instead of writing two code paths we chose to provide a minimalist, generic
  48. // Standard Library geared toward post-processing use. An added bonus to that if users create
  49. // their own post-processing effects using this framework, then they'll work without any
  50. // modification on both Classic and Scriptable Render Pipelines.
  51. //
  52. /// <summary>
  53. /// A post-process volume component holding a post-process profile.
  54. /// </summary>
  55. /// <seealso cref="RuntimeUtilities.DestroyVolume"/>
  56. #if UNITY_2018_3_OR_NEWER
  57. [ExecuteAlways]
  58. #else
  59. [ExecuteInEditMode]
  60. #endif
  61. [AddComponentMenu("Rendering/Post-process Volume", 1001)]
  62. public sealed class PostProcessVolume : MonoBehaviour
  63. {
  64. /// <summary>
  65. /// The shared profile of this volume.
  66. /// Modifying <c>sharedProfile</c> will change all volumes using this profile, and change
  67. /// profile settings that are stored in the project too.
  68. /// </summary>
  69. /// <remarks>
  70. /// It is not recommended to modify profiles returned by <c>sharedProfile</c>. If you want
  71. /// to modify the profile of a volume use <see cref="profile"/> instead.
  72. /// </remarks>
  73. /// <seealso cref="profile"/>
  74. public PostProcessProfile sharedProfile;
  75. /// <summary>
  76. /// Should this volume be applied to the whole scene?
  77. /// </summary>
  78. [Tooltip("Check this box to mark this volume as global. This volume's Profile will be applied to the whole Scene.")]
  79. public bool isGlobal = false;
  80. /// <summary>
  81. /// The outer distance to start blending from. A value of 0 means no blending and the volume
  82. /// overrides will be applied immediatly upon entry.
  83. /// </summary>
  84. [Min(0f), Tooltip("The distance (from the attached Collider) to start blending from. A value of 0 means there will be no blending and the Volume overrides will be applied immediatly upon entry to the attached Collider.")]
  85. public float blendDistance = 0f;
  86. /// <summary>
  87. /// The total weight of this volume in the scene. 0 means it won't do anything, 1 means full
  88. /// effect.
  89. /// </summary>
  90. [Range(0f, 1f), Tooltip("The total weight of this Volume in the Scene. A value of 0 signifies that it will have no effect, 1 signifies full effect.")]
  91. public float weight = 1f;
  92. /// <summary>
  93. /// The volume priority in the stack. Higher number means higher priority. Negative values
  94. /// are supported.
  95. /// </summary>
  96. [Tooltip("The volume priority in the stack. A higher value means higher priority. Negative values are supported.")]
  97. public float priority = 0f;
  98. /// <summary>
  99. /// Returns the first instantiated <see cref="PostProcessProfile"/> assigned to the volume.
  100. /// Modifying <paramref name="profile"/> will change the profile for this volume only. If
  101. /// the profile is used by any other volume, this will clone the shared profile and start
  102. /// using it from now on.
  103. /// </summary>
  104. /// <remarks>
  105. /// This property automatically instantiates the profile and make it unique to this volume
  106. /// so you can safely edit it via scripting at runtime without changing the original asset
  107. /// in the project.
  108. /// Note that if you pass in your own profile, it is your responsibility to destroy it once
  109. /// it's not in use anymore.
  110. /// </remarks>
  111. /// <seealso cref="sharedProfile"/>
  112. /// <seealso cref="RuntimeUtilities.DestroyProfile"/>
  113. public PostProcessProfile profile
  114. {
  115. get
  116. {
  117. if (m_InternalProfile == null)
  118. {
  119. m_InternalProfile = ScriptableObject.CreateInstance<PostProcessProfile>();
  120. if (sharedProfile != null)
  121. {
  122. foreach (var item in sharedProfile.settings)
  123. {
  124. var itemCopy = Instantiate(item);
  125. m_InternalProfile.settings.Add(itemCopy);
  126. }
  127. }
  128. }
  129. return m_InternalProfile;
  130. }
  131. set
  132. {
  133. m_InternalProfile = value;
  134. }
  135. }
  136. internal PostProcessProfile profileRef
  137. {
  138. get
  139. {
  140. return m_InternalProfile == null
  141. ? sharedProfile
  142. : m_InternalProfile;
  143. }
  144. }
  145. /// <summary>
  146. /// Checks if the volume has an intantiated profile or is using a shared profile.
  147. /// </summary>
  148. /// <returns><c>true</c> if the profile has been intantiated</returns>
  149. /// <seealso cref="profile"/>
  150. /// <seealso cref="sharedProfile"/>
  151. public bool HasInstantiatedProfile()
  152. {
  153. return m_InternalProfile != null;
  154. }
  155. int m_PreviousLayer;
  156. float m_PreviousPriority;
  157. List<Collider> m_TempColliders;
  158. PostProcessProfile m_InternalProfile;
  159. void OnEnable()
  160. {
  161. PostProcessManager.instance.Register(this);
  162. m_PreviousLayer = gameObject.layer;
  163. m_TempColliders = new List<Collider>();
  164. }
  165. void OnDisable()
  166. {
  167. PostProcessManager.instance.Unregister(this);
  168. }
  169. void Update()
  170. {
  171. // Unfortunately we need to track the current layer to update the volume manager in
  172. // real-time as the user could change it at any time in the editor or at runtime.
  173. // Because no event is raised when the layer changes, we have to track it on every
  174. // frame :/
  175. int layer = gameObject.layer;
  176. if (layer != m_PreviousLayer)
  177. {
  178. PostProcessManager.instance.UpdateVolumeLayer(this, m_PreviousLayer, layer);
  179. m_PreviousLayer = layer;
  180. }
  181. // Same for `priority`. We could use a property instead, but it doesn't play nice with
  182. // the serialization system. Using a custom Attribute/PropertyDrawer for a property is
  183. // possible but it doesn't work with Undo/Redo in the editor, which makes it useless.
  184. if (priority != m_PreviousPriority)
  185. {
  186. PostProcessManager.instance.SetLayerDirty(layer);
  187. m_PreviousPriority = priority;
  188. }
  189. }
  190. // TODO: Look into a better volume previsualization system
  191. void OnDrawGizmos()
  192. {
  193. var colliders = m_TempColliders;
  194. GetComponents(colliders);
  195. if (isGlobal || colliders == null)
  196. return;
  197. #if UNITY_EDITOR
  198. // Can't access the UnityEditor.Rendering.PostProcessing namespace from here, so
  199. // we'll get the preferred color manually
  200. unchecked
  201. {
  202. int value = UnityEditor.EditorPrefs.GetInt("PostProcessing.Volume.GizmoColor", (int)0x8033cc1a);
  203. Gizmos.color = ColorUtilities.ToRGBA((uint)value);
  204. }
  205. #endif
  206. var scale = transform.lossyScale;
  207. var invScale = new Vector3(1f / scale.x, 1f / scale.y, 1f / scale.z);
  208. Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, scale);
  209. // Draw a separate gizmo for each collider
  210. foreach (var collider in colliders)
  211. {
  212. if (!collider.enabled)
  213. continue;
  214. // We'll just use scaling as an approximation for volume skin. It's far from being
  215. // correct (and is completely wrong in some cases). Ultimately we'd use a distance
  216. // field or at least a tesselate + push modifier on the collider's mesh to get a
  217. // better approximation, but the current Gizmo system is a bit limited and because
  218. // everything is dynamic in Unity and can be changed at anytime, it's hard to keep
  219. // track of changes in an elegant way (which we'd need to implement a nice cache
  220. // system for generated volume meshes).
  221. var type = collider.GetType();
  222. if (type == typeof(BoxCollider))
  223. {
  224. var c = (BoxCollider)collider;
  225. Gizmos.DrawCube(c.center, c.size);
  226. Gizmos.DrawWireCube(c.center, c.size + invScale * blendDistance * 4f);
  227. }
  228. else if (type == typeof(SphereCollider))
  229. {
  230. var c = (SphereCollider)collider;
  231. Gizmos.DrawSphere(c.center, c.radius);
  232. Gizmos.DrawWireSphere(c.center, c.radius + invScale.x * blendDistance * 2f);
  233. }
  234. else if (type == typeof(MeshCollider))
  235. {
  236. var c = (MeshCollider)collider;
  237. // Only convex mesh colliders are allowed
  238. if (!c.convex)
  239. c.convex = true;
  240. // Mesh pivot should be centered or this won't work
  241. Gizmos.DrawMesh(c.sharedMesh);
  242. Gizmos.DrawWireMesh(c.sharedMesh, Vector3.zero, Quaternion.identity, Vector3.one + invScale * blendDistance * 4f);
  243. }
  244. // Nothing for capsule (DrawCapsule isn't exposed in Gizmo), terrain, wheel and
  245. // other colliders...
  246. }
  247. colliders.Clear();
  248. }
  249. }
  250. }