OutlineResources.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  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 System.Diagnostics;
  6. using UnityEditor;
  7. using UnityEngine;
  8. using UnityEngine.Rendering;
  9. namespace UnityFx.Outline
  10. {
  11. /// <summary>
  12. /// This asset is used to store references to shaders and other resources needed at runtime without having to use a Resources folder.
  13. /// </summary>
  14. /// <seealso cref="OutlineRenderer"/>
  15. [CreateAssetMenu(fileName = "OutlineResources", menuName = "UnityFx/Outline/Outline Resources")]
  16. public sealed class OutlineResources : ScriptableObject
  17. {
  18. #region data
  19. [SerializeField]
  20. private Shader _renderShader;
  21. [SerializeField]
  22. private Shader _outlineShader;
  23. private Material _renderMaterial;
  24. private Material _outlineMaterial;
  25. private MaterialPropertyBlock _props;
  26. private Mesh _fullscreenTriangleMesh;
  27. private float[][] _gaussSamples;
  28. private bool _useDrawMesh;
  29. #endregion
  30. #region interface
  31. /// <summary>
  32. /// Minimum value of outline width parameter.
  33. /// </summary>
  34. /// <seealso cref="MaxWidth"/>
  35. public const int MinWidth = 1;
  36. /// <summary>
  37. /// Maximum value of outline width parameter.
  38. /// </summary>
  39. /// <remarks>
  40. /// If the value is changed here, it should be adjusted in Outline.shader as well.
  41. /// </remarks>
  42. /// <seealso cref="MinWidth"/>
  43. public const int MaxWidth = 32;
  44. /// <summary>
  45. /// Minimum value of outline intensity parameter.
  46. /// </summary>
  47. /// <seealso cref="MaxIntensity"/>
  48. /// <seealso cref="SolidIntensity"/>
  49. public const int MinIntensity = 1;
  50. /// <summary>
  51. /// Maximum value of outline intensity parameter.
  52. /// </summary>
  53. /// <seealso cref="MinIntensity"/>
  54. /// <seealso cref="SolidIntensity"/>
  55. public const int MaxIntensity = 64;
  56. /// <summary>
  57. /// Value of outline intensity parameter that is treated as solid fill.
  58. /// </summary>
  59. /// <seealso cref="MinIntensity"/>
  60. /// <seealso cref="MaxIntensity"/>
  61. public const int SolidIntensity = 100;
  62. /// <summary>
  63. /// Minimum value of outline alpha cutoff parameter.
  64. /// </summary>
  65. /// <seealso cref="MaxAlphaCutoff"/>
  66. public const float MinAlphaCutoff = 0;
  67. /// <summary>
  68. /// Maximum value of outline alpha cutoff parameter.
  69. /// </summary>
  70. /// <seealso cref="MinAlphaCutoff"/>
  71. public const float MaxAlphaCutoff = 1;
  72. /// <summary>
  73. /// Name of _MainTex shader parameter.
  74. /// </summary>
  75. public const string MainTexName = "_MainTex";
  76. /// <summary>
  77. /// Name of _MaskTex shader parameter.
  78. /// </summary>
  79. public const string MaskTexName = "_MaskTex";
  80. /// <summary>
  81. /// Name of _TempTex shader parameter.
  82. /// </summary>
  83. public const string TempTexName = "_TempTex";
  84. /// <summary>
  85. /// Name of _Color shader parameter.
  86. /// </summary>
  87. public const string ColorName = "_Color";
  88. /// <summary>
  89. /// Name of _Width shader parameter.
  90. /// </summary>
  91. public const string WidthName = "_Width";
  92. /// <summary>
  93. /// Name of _Intensity shader parameter.
  94. /// </summary>
  95. public const string IntensityName = "_Intensity";
  96. /// <summary>
  97. /// Name of _Cutoff shader parameter.
  98. /// </summary>
  99. public const string AlphaCutoffName = "_Cutoff";
  100. /// <summary>
  101. /// Name of _GaussSamples shader parameter.
  102. /// </summary>
  103. public const string GaussSamplesName = "_GaussSamples";
  104. /// <summary>
  105. /// Name of the _USE_DRAWMESH shader feature.
  106. /// </summary>
  107. public const string UseDrawMeshFeatureName = "_USE_DRAWMESH";
  108. /// <summary>
  109. /// Name of the outline effect.
  110. /// </summary>
  111. public const string EffectName = "Outline";
  112. /// <summary>
  113. /// Tooltip text for <see cref="OutlineResources"/> field.
  114. /// </summary>
  115. public const string OutlineResourcesTooltip = "Outline resources to use (shaders, materials etc). Do not change defaults unless you know what you're doing.";
  116. /// <summary>
  117. /// Tooltip text for <see cref="OutlineLayerCollection"/> field.
  118. /// </summary>
  119. public const string OutlineLayerCollectionTooltip = "Collection of outline layers to use. This can be used to share outline settings between multiple cameras.";
  120. /// <summary>
  121. /// Tooltip text for outline <see cref="LayerMask"/> field.
  122. /// </summary>
  123. public const string OutlineLayerMaskTooltip = "Layer mask for outined objects.";
  124. /// <summary>
  125. /// Tooltip text for outline <see cref="LayerMask"/> field.
  126. /// </summary>
  127. public const string OutlineRenderingLayerMaskTooltip = "Rendering layer mask for outined objects.";
  128. /// <summary>
  129. /// Index of the default pass in <see cref="RenderShader"/>.
  130. /// </summary>
  131. public const int RenderShaderDefaultPassId = 0;
  132. /// <summary>
  133. /// Index of the alpha-test pass in <see cref="RenderShader"/>.
  134. /// </summary>
  135. public const int RenderShaderAlphaTestPassId = 1;
  136. /// <summary>
  137. /// Index of the HPass in <see cref="OutlineShader"/>.
  138. /// </summary>
  139. public const int OutlineShaderHPassId = 0;
  140. /// <summary>
  141. /// Index of the VPass in <see cref="OutlineShader"/>.
  142. /// </summary>
  143. public const int OutlineShaderVPassId = 1;
  144. /// <summary>
  145. /// SRP not supported message.
  146. /// </summary>
  147. internal const string SrpNotSupported = "{0} works with built-in render pipeline only. It does not support SRP (including URP and HDRP).";
  148. /// <summary>
  149. /// Post-processing not supported message.
  150. /// </summary>
  151. internal const string PpNotSupported = "{0} does not support Unity Post-processing stack v2. It might not work as expected.";
  152. /// <summary>
  153. /// Hashed name of _MainTex shader parameter.
  154. /// </summary>
  155. public readonly int MainTexId = Shader.PropertyToID(MainTexName);
  156. /// <summary>
  157. /// Texture identifier for _MainTex shader parameter.
  158. /// </summary>
  159. public readonly RenderTargetIdentifier MainTex = new RenderTargetIdentifier(MainTexName);
  160. /// <summary>
  161. /// Hashed name of _MaskTex shader parameter.
  162. /// </summary>
  163. public readonly int MaskTexId = Shader.PropertyToID(MaskTexName);
  164. /// <summary>
  165. /// Texture identifier for _MaskTex shader parameter.
  166. /// </summary>
  167. public readonly RenderTargetIdentifier MaskTex = new RenderTargetIdentifier(MaskTexName);
  168. /// <summary>
  169. /// Hashed name of _TempTex shader parameter.
  170. /// </summary>
  171. public readonly int TempTexId = Shader.PropertyToID(TempTexName);
  172. /// <summary>
  173. /// Texture identifier for _TempTex shader parameter.
  174. /// </summary>
  175. public readonly RenderTargetIdentifier TempTex = new RenderTargetIdentifier(TempTexName);
  176. /// <summary>
  177. /// Hashed name of _Color shader parameter.
  178. /// </summary>
  179. public readonly int ColorId = Shader.PropertyToID(ColorName);
  180. /// <summary>
  181. /// Hashed name of _Width shader parameter.
  182. /// </summary>
  183. public readonly int WidthId = Shader.PropertyToID(WidthName);
  184. /// <summary>
  185. /// Hashed name of _Intensity shader parameter.
  186. /// </summary>
  187. public readonly int IntensityId = Shader.PropertyToID(IntensityName);
  188. /// <summary>
  189. /// Hashed name of _Cutoff shader parameter.
  190. /// </summary>
  191. public readonly int AlphaCutoffId = Shader.PropertyToID(AlphaCutoffName);
  192. /// <summary>
  193. /// Hashed name of _GaussSamples shader parameter.
  194. /// </summary>
  195. public readonly int GaussSamplesId = Shader.PropertyToID(GaussSamplesName);
  196. /// <summary>
  197. /// Temp materials list. Used by <see cref="OutlineRenderer"/> to avoid GC allocations.
  198. /// </summary>
  199. internal readonly List<Material> TmpMaterials = new List<Material>();
  200. /// <summary>
  201. /// Gets a <see cref="Shader"/> that renders objects outlined with a solid while color.
  202. /// </summary>
  203. public Shader RenderShader
  204. {
  205. get
  206. {
  207. return _renderShader;
  208. }
  209. }
  210. /// <summary>
  211. /// Gets a <see cref="Shader"/> that renders outline around the mask, that was generated with <see cref="RenderShader"/>.
  212. /// </summary>
  213. public Shader OutlineShader
  214. {
  215. get
  216. {
  217. return _outlineShader;
  218. }
  219. }
  220. /// <summary>
  221. /// Gets a <see cref="RenderShader"/>-based material.
  222. /// </summary>
  223. public Material RenderMaterial
  224. {
  225. get
  226. {
  227. if (_renderMaterial == null)
  228. {
  229. UnityEngine.Debug.Assert(_renderShader != null, "No RenderShader is set in outline resources.", this);
  230. _renderMaterial = new Material(_renderShader)
  231. {
  232. name = "Outline - RenderColor",
  233. hideFlags = HideFlags.HideAndDontSave
  234. };
  235. }
  236. return _renderMaterial;
  237. }
  238. }
  239. /// <summary>
  240. /// Gets a <see cref="OutlineShader"/>-based material.
  241. /// </summary>
  242. public Material OutlineMaterial
  243. {
  244. get
  245. {
  246. if (_outlineMaterial == null)
  247. {
  248. UnityEngine.Debug.Assert(_outlineShader != null, "No OutlineShader is set in outline resources.", this);
  249. _outlineMaterial = new Material(_outlineShader)
  250. {
  251. name = "Outline - Main",
  252. hideFlags = HideFlags.HideAndDontSave
  253. };
  254. if (_useDrawMesh)
  255. {
  256. _outlineMaterial.EnableKeyword(UseDrawMeshFeatureName);
  257. }
  258. }
  259. return _outlineMaterial;
  260. }
  261. }
  262. /// <summary>
  263. /// Gets a <see cref="MaterialPropertyBlock"/> for <see cref="VPassBlendMaterial"/>.
  264. /// </summary>
  265. public MaterialPropertyBlock Properties
  266. {
  267. get
  268. {
  269. if (_props is null)
  270. {
  271. _props = new MaterialPropertyBlock();
  272. }
  273. return _props;
  274. }
  275. }
  276. /// <summary>
  277. /// Gets or sets a fullscreen triangle mesh. The mesh is lazy-initialized on the first access.
  278. /// </summary>
  279. /// <remarks>
  280. /// This is used by <see cref="OutlineRenderer"/> to avoid Blit() calls and use DrawMesh() passing
  281. /// this mesh as the first argument. When running on a device with Shader Model 3.5 support this
  282. /// should not be used at all, as the vertices are generated in vertex shader with DrawProcedural() call.
  283. /// </remarks>
  284. /// <seealso cref="OutlineRenderer"/>
  285. public Mesh FullscreenTriangleMesh
  286. {
  287. get
  288. {
  289. if (_fullscreenTriangleMesh == null)
  290. {
  291. _fullscreenTriangleMesh = new Mesh()
  292. {
  293. name = "Outline - FullscreenTriangle",
  294. hideFlags = HideFlags.HideAndDontSave,
  295. vertices = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(3, -1, 0), new Vector3(-1, 3, 0) },
  296. triangles = new int[] { 0, 1, 2 }
  297. };
  298. _fullscreenTriangleMesh.UploadMeshData(true);
  299. }
  300. return _fullscreenTriangleMesh;
  301. }
  302. set
  303. {
  304. _fullscreenTriangleMesh = value;
  305. }
  306. }
  307. /// <summary>
  308. /// Gets or sets a value indicating whether <see cref="FullscreenTriangleMesh"/> is used for image effects rendering even when procedural rendering is available.
  309. /// </summary>
  310. public bool UseFullscreenTriangleMesh
  311. {
  312. get
  313. {
  314. return _useDrawMesh;
  315. }
  316. set
  317. {
  318. if (_useDrawMesh != value)
  319. {
  320. _useDrawMesh = value;
  321. if (_outlineMaterial)
  322. {
  323. if (_useDrawMesh)
  324. {
  325. _outlineMaterial.EnableKeyword(UseDrawMeshFeatureName);
  326. }
  327. else
  328. {
  329. _outlineMaterial.DisableKeyword(UseDrawMeshFeatureName);
  330. }
  331. }
  332. }
  333. }
  334. }
  335. /// <summary>
  336. /// Gets a value indicating whether the instance is in valid state.
  337. /// </summary>
  338. public bool IsValid => RenderShader && OutlineShader;
  339. /// <summary>
  340. /// Returns a <see cref="MaterialPropertyBlock"/> instance initialized with values from <paramref name="settings"/>.
  341. /// </summary>
  342. public MaterialPropertyBlock GetProperties(IOutlineSettings settings)
  343. {
  344. if (_props is null)
  345. {
  346. _props = new MaterialPropertyBlock();
  347. }
  348. _props.SetFloat(WidthId, settings.OutlineWidth);
  349. _props.SetColor(ColorId, settings.OutlineColor);
  350. if ((settings.OutlineRenderMode & OutlineRenderFlags.Blurred) != 0)
  351. {
  352. _props.SetFloat(IntensityId, settings.OutlineIntensity);
  353. }
  354. else
  355. {
  356. _props.SetFloat(IntensityId, SolidIntensity);
  357. }
  358. return _props;
  359. }
  360. /// <summary>
  361. /// Gets cached gauss samples for the specified outline <paramref name="width"/>.
  362. /// </summary>
  363. public float[] GetGaussSamples(int width)
  364. {
  365. var index = Mathf.Clamp(width, 1, MaxWidth) - 1;
  366. if (_gaussSamples is null)
  367. {
  368. _gaussSamples = new float[MaxWidth][];
  369. }
  370. if (_gaussSamples[index] is null)
  371. {
  372. _gaussSamples[index] = GetGaussSamples(width, null);
  373. }
  374. return _gaussSamples[index];
  375. }
  376. /// <summary>
  377. /// Resets the resources to defaults.
  378. /// </summary>
  379. public void ResetToDefaults()
  380. {
  381. _renderShader = Shader.Find("Hidden/UnityFx/OutlineColor");
  382. _outlineShader = Shader.Find("Hidden/UnityFx/Outline");
  383. }
  384. /// <summary>
  385. /// Calculates value of Gauss function for the specified <paramref name="x"/> and <paramref name="stdDev"/> values.
  386. /// </summary>
  387. /// <seealso href="https://en.wikipedia.org/wiki/Gaussian_blur"/>
  388. /// <seealso href="https://en.wikipedia.org/wiki/Normal_distribution"/>
  389. public static float Gauss(float x, float stdDev)
  390. {
  391. var stdDev2 = stdDev * stdDev * 2;
  392. var a = 1 / Mathf.Sqrt(Mathf.PI * stdDev2);
  393. var gauss = a * Mathf.Pow((float)Math.E, -x * x / stdDev2);
  394. return gauss;
  395. }
  396. /// <summary>
  397. /// Samples Gauss function for the specified <paramref name="width"/>.
  398. /// </summary>
  399. /// <seealso href="https://en.wikipedia.org/wiki/Normal_distribution"/>
  400. public static float[] GetGaussSamples(int width, float[] samples)
  401. {
  402. // NOTE: According to '3 sigma' rule there is no reason to have StdDev less then width / 3.
  403. // In practice blur looks best when StdDev is within range [width / 3, width / 2].
  404. var stdDev = width * 0.5f;
  405. if (samples is null)
  406. {
  407. samples = new float[MaxWidth];
  408. }
  409. for (var i = 0; i < width; i++)
  410. {
  411. samples[i] = Gauss(i, stdDev);
  412. }
  413. return samples;
  414. }
  415. /// <summary>
  416. /// Writes a console warning if SRP is detected.
  417. /// </summary>
  418. public static void LogSrpNotSupported(UnityEngine.Object obj)
  419. {
  420. if (GraphicsSettings.renderPipelineAsset)
  421. {
  422. UnityEngine.Debug.LogWarningFormat(obj, SrpNotSupported, obj.GetType().Name);
  423. }
  424. }
  425. /// <summary>
  426. /// Writes a console warning if Post Processing Stack v2 is detected.
  427. /// </summary>
  428. [Conditional("UNITY_POST_PROCESSING_STACK_V2")]
  429. public static void LogPpNotSupported(UnityEngine.Object obj)
  430. {
  431. UnityEngine.Debug.LogWarningFormat(obj, PpNotSupported, obj.GetType().Name);
  432. }
  433. #endregion
  434. #region ScriptableObject
  435. private void OnValidate()
  436. {
  437. if (_renderMaterial)
  438. {
  439. _renderMaterial.shader = _renderShader;
  440. }
  441. if (_outlineMaterial)
  442. {
  443. _outlineMaterial.shader = _outlineShader;
  444. }
  445. }
  446. #endregion
  447. }
  448. }