OutlineBehaviour.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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;
  5. using System.Collections.Generic;
  6. using UnityEngine;
  7. using UnityEngine.Rendering;
  8. namespace UnityFx.Outline
  9. {
  10. /// <summary>
  11. /// Attach this script to a <see cref="GameObject"/> to add outline effect. It can be configured in edit-time or in runtime via scripts.
  12. /// </summary>
  13. /// <seealso cref="OutlineEffect"/>
  14. [ExecuteInEditMode]
  15. [DisallowMultipleComponent]
  16. public sealed class OutlineBehaviour : MonoBehaviour, IOutlineSettings
  17. {
  18. #region data
  19. #pragma warning disable 0649
  20. [SerializeField, Tooltip(OutlineResources.OutlineResourcesTooltip)]
  21. private OutlineResources _outlineResources;
  22. [SerializeField, HideInInspector]
  23. private OutlineSettingsInstance _outlineSettings;
  24. [SerializeField, HideInInspector]
  25. private int _ignoreLayerMask;
  26. [SerializeField, HideInInspector]
  27. private CameraEvent _cameraEvent = OutlineRenderer.RenderEvent;
  28. [SerializeField, HideInInspector]
  29. private Camera _targetCamera;
  30. [SerializeField, Tooltip("If set, list of object renderers is updated on each frame. Enable if the object has child renderers which are enabled/disabled frequently.")]
  31. private bool _updateRenderers;
  32. #pragma warning restore 0649
  33. private Dictionary<Camera, CommandBuffer> _cameraMap = new Dictionary<Camera, CommandBuffer>();
  34. private List<Camera> _camerasToRemove = new List<Camera>();
  35. private OutlineRendererCollection _renderers;
  36. #endregion
  37. #region interface
  38. /// <summary>
  39. /// Gets or sets resources used by the effect implementation.
  40. /// </summary>
  41. /// <exception cref="ArgumentNullException">Thrown if setter argument is <see langword="null"/>.</exception>
  42. /// <seealso cref="OutlineSettings"/>
  43. public OutlineResources OutlineResources
  44. {
  45. get
  46. {
  47. return _outlineResources;
  48. }
  49. set
  50. {
  51. if (value is null)
  52. {
  53. throw new ArgumentNullException(nameof(OutlineResources));
  54. }
  55. _outlineResources = value;
  56. }
  57. }
  58. /// <summary>
  59. /// Gets or sets outline settings. Set this to non-<see langword="null"/> value to share settings with other components.
  60. /// </summary>
  61. /// <seealso cref="OutlineResources"/>
  62. public OutlineSettings OutlineSettings
  63. {
  64. get
  65. {
  66. if (_outlineSettings == null)
  67. {
  68. _outlineSettings = new OutlineSettingsInstance();
  69. }
  70. return _outlineSettings.OutlineSettings;
  71. }
  72. set
  73. {
  74. if (_outlineSettings == null)
  75. {
  76. _outlineSettings = new OutlineSettingsInstance();
  77. }
  78. _outlineSettings.OutlineSettings = value;
  79. }
  80. }
  81. /// <summary>
  82. /// Gets or sets layer mask to use for ignored <see cref="Renderer"/> components in this game object.
  83. /// </summary>
  84. public int IgnoreLayerMask
  85. {
  86. get
  87. {
  88. return _ignoreLayerMask;
  89. }
  90. set
  91. {
  92. if (_ignoreLayerMask != value)
  93. {
  94. _ignoreLayerMask = value;
  95. _renderers?.Reset(false, value);
  96. }
  97. }
  98. }
  99. /// <summary>
  100. /// Gets or sets <see cref="CameraEvent"/> used to render the outlines.
  101. /// </summary>
  102. public CameraEvent RenderEvent
  103. {
  104. get
  105. {
  106. return _cameraEvent;
  107. }
  108. set
  109. {
  110. if (_cameraEvent != value)
  111. {
  112. foreach (var kvp in _cameraMap)
  113. {
  114. if (kvp.Key)
  115. {
  116. kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value);
  117. kvp.Key.AddCommandBuffer(value, kvp.Value);
  118. }
  119. }
  120. _cameraEvent = value;
  121. }
  122. }
  123. }
  124. /// <summary>
  125. /// Gets outline renderers. By default all child <see cref="Renderer"/> components are used for outlining.
  126. /// </summary>
  127. /// <seealso cref="UpdateRenderers"/>
  128. public ICollection<Renderer> OutlineRenderers
  129. {
  130. get
  131. {
  132. CreateRenderersIfNeeded();
  133. return _renderers;
  134. }
  135. }
  136. /// <summary>
  137. /// Gets or sets camera to render outlines to. If not set, outlines are rendered to all active cameras.
  138. /// </summary>
  139. /// <seealso cref="Cameras"/>
  140. public Camera Camera
  141. {
  142. get
  143. {
  144. return _targetCamera;
  145. }
  146. set
  147. {
  148. if (_targetCamera != value)
  149. {
  150. if (value)
  151. {
  152. _camerasToRemove.Clear();
  153. foreach (var kvp in _cameraMap)
  154. {
  155. if (kvp.Key && kvp.Key != value)
  156. {
  157. kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value);
  158. kvp.Value.Dispose();
  159. _camerasToRemove.Add(kvp.Key);
  160. }
  161. }
  162. foreach (var camera in _camerasToRemove)
  163. {
  164. _cameraMap.Remove(camera);
  165. }
  166. }
  167. _targetCamera = value;
  168. }
  169. }
  170. }
  171. /// <summary>
  172. /// Gets all cameras outline data is rendered to.
  173. /// </summary>
  174. /// <seealso cref="Camera"/>
  175. public ICollection<Camera> Cameras => _cameraMap.Keys;
  176. /// <summary>
  177. /// Updates renderer list.
  178. /// </summary>
  179. /// <seealso cref="OutlineRenderers"/>
  180. public void UpdateRenderers()
  181. {
  182. _renderers?.Reset(false, _ignoreLayerMask);
  183. }
  184. #endregion
  185. #region MonoBehaviour
  186. private void Awake()
  187. {
  188. OutlineResources.LogSrpNotSupported(this);
  189. OutlineResources.LogPpNotSupported(this);
  190. CreateRenderersIfNeeded();
  191. CreateSettingsIfNeeded();
  192. }
  193. private void OnEnable()
  194. {
  195. Camera.onPreRender += OnCameraPreRender;
  196. }
  197. private void OnDisable()
  198. {
  199. Camera.onPreRender -= OnCameraPreRender;
  200. foreach (var kvp in _cameraMap)
  201. {
  202. if (kvp.Key)
  203. {
  204. kvp.Key.RemoveCommandBuffer(_cameraEvent, kvp.Value);
  205. }
  206. kvp.Value.Dispose();
  207. }
  208. _cameraMap.Clear();
  209. }
  210. private void Update()
  211. {
  212. if (_outlineResources != null && _renderers != null)
  213. {
  214. _camerasToRemove.Clear();
  215. if (_updateRenderers)
  216. {
  217. _renderers.Reset(false, _ignoreLayerMask);
  218. }
  219. foreach (var kvp in _cameraMap)
  220. {
  221. var camera = kvp.Key;
  222. var cmdBuffer = kvp.Value;
  223. if (camera)
  224. {
  225. cmdBuffer.Clear();
  226. FillCommandBuffer(camera, cmdBuffer);
  227. }
  228. else
  229. {
  230. cmdBuffer.Dispose();
  231. _camerasToRemove.Add(camera);
  232. }
  233. }
  234. foreach (var camera in _camerasToRemove)
  235. {
  236. _cameraMap.Remove(camera);
  237. }
  238. }
  239. }
  240. #if UNITY_EDITOR
  241. private void OnValidate()
  242. {
  243. CreateRenderersIfNeeded();
  244. CreateSettingsIfNeeded();
  245. }
  246. private void Reset()
  247. {
  248. if (_renderers != null)
  249. {
  250. _renderers.Reset(false, _ignoreLayerMask);
  251. }
  252. }
  253. #endif
  254. #endregion
  255. #region IOutlineSettings
  256. /// <inheritdoc/>
  257. public Color OutlineColor
  258. {
  259. get
  260. {
  261. CreateSettingsIfNeeded();
  262. return _outlineSettings.OutlineColor;
  263. }
  264. set
  265. {
  266. CreateSettingsIfNeeded();
  267. _outlineSettings.OutlineColor = value;
  268. }
  269. }
  270. /// <inheritdoc/>
  271. public int OutlineWidth
  272. {
  273. get
  274. {
  275. CreateSettingsIfNeeded();
  276. return _outlineSettings.OutlineWidth;
  277. }
  278. set
  279. {
  280. CreateSettingsIfNeeded();
  281. _outlineSettings.OutlineWidth = value;
  282. }
  283. }
  284. /// <inheritdoc/>
  285. public float OutlineIntensity
  286. {
  287. get
  288. {
  289. CreateSettingsIfNeeded();
  290. return _outlineSettings.OutlineIntensity;
  291. }
  292. set
  293. {
  294. CreateSettingsIfNeeded();
  295. _outlineSettings.OutlineIntensity = value;
  296. }
  297. }
  298. /// <inheritdoc/>
  299. public float OutlineAlphaCutoff
  300. {
  301. get
  302. {
  303. CreateSettingsIfNeeded();
  304. return _outlineSettings.OutlineAlphaCutoff;
  305. }
  306. set
  307. {
  308. CreateSettingsIfNeeded();
  309. _outlineSettings.OutlineAlphaCutoff = value;
  310. }
  311. }
  312. /// <inheritdoc/>
  313. public OutlineRenderFlags OutlineRenderMode
  314. {
  315. get
  316. {
  317. CreateSettingsIfNeeded();
  318. return _outlineSettings.OutlineRenderMode;
  319. }
  320. set
  321. {
  322. CreateSettingsIfNeeded();
  323. _outlineSettings.OutlineRenderMode = value;
  324. }
  325. }
  326. #endregion
  327. #region IEquatable
  328. /// <inheritdoc/>
  329. public bool Equals(IOutlineSettings other)
  330. {
  331. return OutlineSettings.Equals(_outlineSettings, other);
  332. }
  333. #endregion
  334. #region implementation
  335. private void OnCameraPreRender(Camera camera)
  336. {
  337. if (camera && (!_targetCamera || _targetCamera == camera))
  338. {
  339. if (_outlineSettings.RequiresCameraDepth)
  340. {
  341. camera.depthTextureMode |= DepthTextureMode.Depth;
  342. }
  343. if (!_cameraMap.ContainsKey(camera))
  344. {
  345. var cmdBuf = new CommandBuffer();
  346. cmdBuf.name = string.Format("{0} - {1}", GetType().Name, name);
  347. camera.AddCommandBuffer(_cameraEvent, cmdBuf);
  348. _cameraMap.Add(camera, cmdBuf);
  349. #if UNITY_EDITOR
  350. FillCommandBuffer(camera, cmdBuf);
  351. #endif
  352. }
  353. }
  354. }
  355. private void FillCommandBuffer(Camera camera, CommandBuffer cmdBuffer)
  356. {
  357. if (_renderers.Count > 0)
  358. {
  359. using (var renderer = new OutlineRenderer(cmdBuffer, _outlineResources, camera.actualRenderingPath))
  360. {
  361. renderer.Render(_renderers.GetList(), _outlineSettings, name);
  362. }
  363. }
  364. }
  365. private void CreateSettingsIfNeeded()
  366. {
  367. if (_outlineSettings == null)
  368. {
  369. _outlineSettings = new OutlineSettingsInstance();
  370. }
  371. }
  372. private void CreateRenderersIfNeeded()
  373. {
  374. if (_renderers == null)
  375. {
  376. _renderers = new OutlineRendererCollection(gameObject);
  377. _renderers.Reset(false, _ignoreLayerMask);
  378. }
  379. }
  380. #endregion
  381. }
  382. }