Volume.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. using System.Collections.Generic;
  2. namespace UnityEngine.Rendering
  3. {
  4. /// <summary>
  5. /// A generic Volume component holding a <see cref="VolumeProfile"/>.
  6. /// </summary>
  7. [HelpURL(Documentation.baseURLHDRP + Documentation.version + Documentation.subURL + "Volumes" + Documentation.endURL)]
  8. [ExecuteAlways]
  9. [AddComponentMenu("Miscellaneous/Volume")]
  10. public class Volume : MonoBehaviour
  11. {
  12. /// <summary>
  13. /// Specifies whether to apply the Volume to the entire Scene or not.
  14. /// </summary>
  15. [Tooltip("When enabled, HDRP applies this Volume to the entire Scene.")]
  16. public bool isGlobal = true;
  17. /// <summary>
  18. /// The Volume priority in the stack. A higher value means higher priority. This supports negative values.
  19. /// </summary>
  20. [Tooltip("Sets the Volume priority in the stack. A higher value means higher priority. You can use negative values.")]
  21. public float priority = 0f;
  22. /// <summary>
  23. /// The outer distance to start blending from. A value of 0 means no blending and Unity applies
  24. /// the Volume overrides immediately upon entry.
  25. /// </summary>
  26. [Tooltip("Sets the outer distance to start blending from. A value of 0 means no blending and Unity applies the Volume overrides immediately upon entry.")]
  27. public float blendDistance = 0f;
  28. /// <summary>
  29. /// The total weight of this volume in the Scene. 0 means no effect and 1 means full effect.
  30. /// </summary>
  31. [Range(0f, 1f), Tooltip("Sets the total weight of this Volume in the Scene. 0 means no effect and 1 means full effect.")]
  32. public float weight = 1f;
  33. /// <summary>
  34. /// The shared Profile that this Volume uses.
  35. /// Modifying <c>sharedProfile</c> changes every Volumes that uses this Profile and also changes
  36. /// the Profile settings stored in the Project.
  37. /// </summary>
  38. /// <remarks>
  39. /// You should not modify Profiles that <c>sharedProfile</c> returns. If you want
  40. /// to modify the Profile of a Volume, use <see cref="profile"/> instead.
  41. /// </remarks>
  42. /// <seealso cref="profile"/>
  43. public VolumeProfile sharedProfile = null;
  44. /// <summary>
  45. /// Gets the first instantiated <see cref="VolumeProfile"/> assigned to the Volume.
  46. /// Modifying <c>profile</c> changes the Profile for this Volume only. If another Volume
  47. /// uses the same Profile, this clones the shared Profile and starts using it from now on.
  48. /// </summary>
  49. /// <remarks>
  50. /// This property automatically instantiates the Profile and make it unique to this Volume
  51. /// so you can safely edit it via scripting at runtime without changing the original Asset
  52. /// in the Project.
  53. /// Note that if you pass your own Profile, you must destroy it when you finish using it.
  54. /// </remarks>
  55. /// <seealso cref="sharedProfile"/>
  56. public VolumeProfile profile
  57. {
  58. get
  59. {
  60. if (m_InternalProfile == null)
  61. {
  62. m_InternalProfile = ScriptableObject.CreateInstance<VolumeProfile>();
  63. if (sharedProfile != null)
  64. {
  65. foreach (var item in sharedProfile.components)
  66. {
  67. var itemCopy = Instantiate(item);
  68. m_InternalProfile.components.Add(itemCopy);
  69. }
  70. }
  71. }
  72. return m_InternalProfile;
  73. }
  74. set => m_InternalProfile = value;
  75. }
  76. internal VolumeProfile profileRef => m_InternalProfile == null ? sharedProfile : m_InternalProfile;
  77. /// <summary>
  78. /// Checks if the Volume has an instantiated Profile or if it uses a shared Profile.
  79. /// </summary>
  80. /// <returns><c>true</c> if the profile has been instantiated.</returns>
  81. /// <seealso cref="profile"/>
  82. /// <seealso cref="sharedProfile"/>
  83. public bool HasInstantiatedProfile() => m_InternalProfile != null;
  84. // Needed for state tracking (see the comments in Update)
  85. int m_PreviousLayer;
  86. float m_PreviousPriority;
  87. VolumeProfile m_InternalProfile;
  88. void OnEnable()
  89. {
  90. m_PreviousLayer = gameObject.layer;
  91. VolumeManager.instance.Register(this, m_PreviousLayer);
  92. }
  93. void OnDisable()
  94. {
  95. VolumeManager.instance.Unregister(this, gameObject.layer);
  96. }
  97. void Update()
  98. {
  99. // Unfortunately we need to track the current layer to update the volume manager in
  100. // real-time as the user could change it at any time in the editor or at runtime.
  101. // Because no event is raised when the layer changes, we have to track it on every
  102. // frame :/
  103. UpdateLayer();
  104. // Same for priority. We could use a property instead, but it doesn't play nice with the
  105. // serialization system. Using a custom Attribute/PropertyDrawer for a property is
  106. // possible but it doesn't work with Undo/Redo in the editor, which makes it useless for
  107. // our case.
  108. if (priority != m_PreviousPriority)
  109. {
  110. VolumeManager.instance.SetLayerDirty(gameObject.layer);
  111. m_PreviousPriority = priority;
  112. }
  113. }
  114. internal void UpdateLayer()
  115. {
  116. int layer = gameObject.layer;
  117. if (layer != m_PreviousLayer)
  118. {
  119. VolumeManager.instance.UpdateVolumeLayer(this, m_PreviousLayer, layer);
  120. m_PreviousLayer = layer;
  121. }
  122. }
  123. #if UNITY_EDITOR
  124. // TODO: Look into a better volume previsualization system
  125. List<Collider> m_TempColliders;
  126. void OnDrawGizmos()
  127. {
  128. if (m_TempColliders == null)
  129. m_TempColliders = new List<Collider>();
  130. var colliders = m_TempColliders;
  131. GetComponents(colliders);
  132. if (isGlobal || colliders == null)
  133. return;
  134. var scale = transform.localScale;
  135. var invScale = new Vector3(1f / scale.x, 1f / scale.y, 1f / scale.z);
  136. Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, scale);
  137. Gizmos.color = CoreRenderPipelinePreferences.volumeGizmoColor;
  138. // Draw a separate gizmo for each collider
  139. foreach (var collider in colliders)
  140. {
  141. if (!collider.enabled)
  142. continue;
  143. // We'll just use scaling as an approximation for volume skin. It's far from being
  144. // correct (and is completely wrong in some cases). Ultimately we'd use a distance
  145. // field or at least a tesselate + push modifier on the collider's mesh to get a
  146. // better approximation, but the current Gizmo system is a bit limited and because
  147. // everything is dynamic in Unity and can be changed at anytime, it's hard to keep
  148. // track of changes in an elegant way (which we'd need to implement a nice cache
  149. // system for generated volume meshes).
  150. switch (collider)
  151. {
  152. case BoxCollider c:
  153. Gizmos.DrawCube(c.center, c.size);
  154. break;
  155. case SphereCollider c:
  156. // For sphere the only scale that is used is the transform.x
  157. Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one * scale.x);
  158. Gizmos.DrawSphere(c.center, c.radius);
  159. break;
  160. case MeshCollider c:
  161. // Only convex mesh m_Colliders are allowed
  162. if (!c.convex)
  163. c.convex = true;
  164. // Mesh pivot should be centered or this won't work
  165. Gizmos.DrawMesh(c.sharedMesh);
  166. break;
  167. default:
  168. // Nothing for capsule (DrawCapsule isn't exposed in Gizmo), terrain, wheel and
  169. // other m_Colliders...
  170. break;
  171. }
  172. }
  173. colliders.Clear();
  174. }
  175. #endif
  176. }
  177. }