OutlineEffect.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. // Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
  2. // See the LICENSE.md file in the project root for more information.
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using UnityEngine.Rendering;
  7. namespace UnityFx.Outline
  8. {
  9. /// <summary>
  10. /// Renders outlines at specific camera. Should be attached to camera to function.
  11. /// </summary>
  12. /// <seealso cref="OutlineLayer"/>
  13. /// <seealso cref="OutlineBehaviour"/>
  14. /// <seealso cref="OutlineSettings"/>
  15. /// <seealso href="https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/"/>
  16. [ExecuteInEditMode]
  17. [RequireComponent(typeof(Camera))]
  18. public sealed partial class OutlineEffect : MonoBehaviour
  19. {
  20. #region data
  21. [SerializeField, Tooltip(OutlineResources.OutlineResourcesTooltip)]
  22. private OutlineResources _outlineResources;
  23. [SerializeField, Tooltip(OutlineResources.OutlineLayerCollectionTooltip)]
  24. private OutlineLayerCollection _outlineLayers;
  25. [SerializeField, HideInInspector]
  26. private CameraEvent _cameraEvent = OutlineRenderer.RenderEvent;
  27. private Camera _camera;
  28. private CommandBuffer _commandBuffer;
  29. private List<OutlineRenderObject> _renderObjects = new List<OutlineRenderObject>(16);
  30. #endregion
  31. #region interface
  32. /// <summary>
  33. /// Gets or sets resources used by the effect implementation.
  34. /// </summary>
  35. /// <exception cref="ArgumentNullException">Thrown if setter argument is <see langword="null"/>.</exception>
  36. public OutlineResources OutlineResources
  37. {
  38. get
  39. {
  40. return _outlineResources;
  41. }
  42. set
  43. {
  44. if (value is null)
  45. {
  46. throw new ArgumentNullException(nameof(OutlineResources));
  47. }
  48. _outlineResources = value;
  49. }
  50. }
  51. /// <summary>
  52. /// Gets collection of outline layers.
  53. /// </summary>
  54. public OutlineLayerCollection OutlineLayers
  55. {
  56. get
  57. {
  58. return _outlineLayers;
  59. }
  60. set
  61. {
  62. _outlineLayers = value;
  63. }
  64. }
  65. /// <summary>
  66. /// Gets outline layers (for internal use only).
  67. /// </summary>
  68. internal OutlineLayerCollection OutlineLayersInternal => _outlineLayers;
  69. /// <summary>
  70. /// Gets or sets <see cref="CameraEvent"/> used to render the outlines.
  71. /// </summary>
  72. public CameraEvent RenderEvent
  73. {
  74. get
  75. {
  76. return _cameraEvent;
  77. }
  78. set
  79. {
  80. if (_cameraEvent != value)
  81. {
  82. if (_commandBuffer != null)
  83. {
  84. var camera = GetComponent<Camera>();
  85. if (camera)
  86. {
  87. camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer);
  88. camera.AddCommandBuffer(value, _commandBuffer);
  89. }
  90. }
  91. _cameraEvent = value;
  92. }
  93. }
  94. }
  95. /// <summary>
  96. /// Adds the <see cref="GameObject"/> passed to the first outline layer. Creates the layer if needed.
  97. /// </summary>
  98. /// <param name="go">The <see cref="GameObject"/> to add and render outline for.</param>
  99. /// <seealso cref="AddGameObject(GameObject, int)"/>
  100. public void AddGameObject(GameObject go)
  101. {
  102. AddGameObject(go, 0);
  103. }
  104. /// <summary>
  105. /// Adds the <see cref="GameObject"/> passed to the specified outline layer. Creates the layer if needed.
  106. /// </summary>
  107. /// <param name="go">The <see cref="GameObject"/> to add and render outline for.</param>
  108. /// <seealso cref="AddGameObject(GameObject)"/>
  109. public void AddGameObject(GameObject go, int layerIndex)
  110. {
  111. if (layerIndex < 0)
  112. {
  113. throw new ArgumentOutOfRangeException("layerIndex");
  114. }
  115. CreateLayersIfNeeded();
  116. while (_outlineLayers.Count <= layerIndex)
  117. {
  118. _outlineLayers.Add(new OutlineLayer());
  119. }
  120. _outlineLayers[layerIndex].Add(go);
  121. }
  122. /// <summary>
  123. /// Removes the specified <see cref="GameObject"/> from <see cref="OutlineLayers"/>.
  124. /// </summary>
  125. /// <param name="go">A <see cref="GameObject"/> to remove.</param>
  126. public void RemoveGameObject(GameObject go)
  127. {
  128. if (_outlineLayers)
  129. {
  130. _outlineLayers.Remove(go);
  131. }
  132. }
  133. #endregion
  134. #region MonoBehaviour
  135. private void Awake()
  136. {
  137. OutlineResources.LogSrpNotSupported(this);
  138. OutlineResources.LogPpNotSupported(this);
  139. }
  140. private void OnEnable()
  141. {
  142. InitCameraAndCommandBuffer();
  143. }
  144. private void OnDisable()
  145. {
  146. ReleaseCameraAndCommandBuffer();
  147. }
  148. private void OnPreRender()
  149. {
  150. FillCommandBuffer();
  151. }
  152. private void OnDestroy()
  153. {
  154. // TODO: Find a way to do this once per OutlineLayerCollection instance.
  155. if (_outlineLayers)
  156. {
  157. _outlineLayers.Reset();
  158. }
  159. }
  160. #if UNITY_EDITOR
  161. //private void OnValidate()
  162. //{
  163. // InitCameraAndCommandBuffer();
  164. // FillCommandBuffer();
  165. //}
  166. private void Reset()
  167. {
  168. _outlineLayers = null;
  169. }
  170. #endif
  171. #endregion
  172. #region implementation
  173. private void InitCameraAndCommandBuffer()
  174. {
  175. _camera = GetComponent<Camera>();
  176. if (_camera && _commandBuffer is null)
  177. {
  178. _commandBuffer = new CommandBuffer
  179. {
  180. name = string.Format("{0} - {1}", GetType().Name, name)
  181. };
  182. _camera.depthTextureMode |= DepthTextureMode.Depth;
  183. _camera.AddCommandBuffer(_cameraEvent, _commandBuffer);
  184. }
  185. }
  186. private void ReleaseCameraAndCommandBuffer()
  187. {
  188. if (_commandBuffer != null)
  189. {
  190. if (_camera)
  191. {
  192. _camera.RemoveCommandBuffer(_cameraEvent, _commandBuffer);
  193. }
  194. _commandBuffer.Dispose();
  195. _commandBuffer = null;
  196. }
  197. _camera = null;
  198. }
  199. private void FillCommandBuffer()
  200. {
  201. if (_camera && _outlineLayers && _commandBuffer != null)
  202. {
  203. _commandBuffer.Clear();
  204. if (_outlineResources && _outlineResources.IsValid)
  205. {
  206. using (var renderer = new OutlineRenderer(_commandBuffer, _outlineResources, _camera.actualRenderingPath))
  207. {
  208. _renderObjects.Clear();
  209. _outlineLayers.GetRenderObjects(_renderObjects);
  210. renderer.Render(_renderObjects);
  211. }
  212. }
  213. }
  214. }
  215. private void CreateLayersIfNeeded()
  216. {
  217. if (_outlineLayers is null)
  218. {
  219. _outlineLayers = ScriptableObject.CreateInstance<OutlineLayerCollection>();
  220. _outlineLayers.name = "OutlineLayers";
  221. }
  222. }
  223. #endregion
  224. }
  225. }