No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SpriteOutlineRenderer.cs 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. using System.Collections.Generic;
  2. using Unity.Mathematics;
  3. using UnityEditor.U2D.Sprites;
  4. using UnityEngine;
  5. namespace UnityEditor.U2D.Animation
  6. {
  7. internal class SpriteOutlineRenderer
  8. {
  9. class OutlineRenderTexture
  10. {
  11. public Texture outlineTexture;
  12. public bool dirty;
  13. }
  14. Material m_OutlineMaterial;
  15. Material m_BitMaskMaterial;
  16. Material m_EdgeOutlineMaterial;
  17. Mesh m_CircleMesh;
  18. Dictionary<string, OutlineRenderTexture> m_OutlineTextureCache = new ();
  19. SkinningEvents m_EventSystem;
  20. static readonly int k_OutlineColorProperty = Shader.PropertyToID("_OutlineColor");
  21. static readonly int k_OutlineSizeProperty = Shader.PropertyToID("_OutlineSize");
  22. static readonly int k_AdjustLinearForGammaProperty = Shader.PropertyToID("_AdjustLinearForGamma");
  23. const int k_ReferenceTextureSize = 1024;
  24. public SpriteOutlineRenderer(SkinningEvents eventSystem)
  25. {
  26. m_EdgeOutlineMaterial = new Material(Shader.Find("Hidden/2D-Animation-SpriteEdgeOutline")) { hideFlags = HideFlags.HideAndDontSave };
  27. m_BitMaskMaterial = new Material(Shader.Find("Hidden/2D-Animation-SpriteBitmask")) { hideFlags = HideFlags.HideAndDontSave };
  28. m_OutlineMaterial = new Material(Shader.Find("Hidden/2D-Animation-SpriteOutline")) { hideFlags = HideFlags.HideAndDontSave };
  29. m_EventSystem = eventSystem;
  30. m_EventSystem.meshPreviewChanged.AddListener(OnMeshPreviewChanged);
  31. m_EventSystem.selectedSpriteChanged.AddListener(OnSelectionChanged);
  32. m_CircleMesh = GenerateCircleMesh();
  33. }
  34. public void Dispose()
  35. {
  36. DestroyMaterialsAndMeshes();
  37. DestroyTextures();
  38. m_EventSystem.meshPreviewChanged.RemoveListener(OnMeshPreviewChanged);
  39. m_EventSystem.selectedSpriteChanged.RemoveListener(OnSelectionChanged);
  40. }
  41. internal void RenderSpriteOutline(ISpriteEditor spriteEditor, SpriteCache sprite)
  42. {
  43. if (spriteEditor == null || sprite == null)
  44. return;
  45. if (SelectionOutlineSettings.selectedSpriteOutlineSize < 0.01f || SelectionOutlineSettings.outlineColor.a < 0.01f)
  46. return;
  47. var mesh = GetMesh(sprite);
  48. if (mesh == null)
  49. return;
  50. UnityEngine.Profiling.Profiler.BeginSample("SpriteOutlineRenderer::RenderSpriteOutline");
  51. var vertices = mesh.vertices;
  52. var edges = sprite.GetMesh().edges;
  53. var multMatrix = Handles.matrix * sprite.GetLocalToWorldMatrixFromMode();
  54. var texture = spriteEditor.GetDataProvider<ITextureDataProvider>().texture;
  55. var outlineSize = SelectionOutlineSettings.selectedSpriteOutlineSize;
  56. var outlineColor = SelectionOutlineSettings.outlineColor;
  57. var adjustForGamma = PlayerSettings.colorSpace == ColorSpace.Linear ? 1.0f : 0.0f;
  58. if (edges != null && edges.Length > 0 && vertices.Length > 0)
  59. {
  60. var finalOutlineSize = outlineSize / spriteEditor.zoomLevel;
  61. DrawEdgeOutline(edges, vertices, multMatrix, finalOutlineSize, outlineColor, adjustForGamma);
  62. }
  63. else // Fallback: Draw using the Sobel filter.
  64. {
  65. var finalOutlineSize = Mathf.Max(texture.width, texture.height) * outlineSize / k_ReferenceTextureSize;
  66. DrawMeshOutline(mesh, sprite, multMatrix, finalOutlineSize, outlineColor, adjustForGamma);
  67. }
  68. UnityEngine.Profiling.Profiler.EndSample();
  69. }
  70. void DrawEdgeOutline(int2[] edges, Vector3[] vertices, Matrix4x4 multMatrix, float outlineSize, Color outlineColor, float adjustForGamma)
  71. {
  72. m_EdgeOutlineMaterial.SetColor(k_OutlineColorProperty, outlineColor);
  73. m_EdgeOutlineMaterial.SetFloat(k_AdjustLinearForGammaProperty, adjustForGamma);
  74. m_EdgeOutlineMaterial.SetPass(0);
  75. var edgeCount = edges.Length;
  76. var vertexCount = vertices.Length;
  77. GL.PushMatrix();
  78. GL.MultMatrix(multMatrix);
  79. GL.Begin(GL.QUADS);
  80. for (var i = 0; i < edgeCount; i++)
  81. {
  82. var currentEdge = edges[i];
  83. if (currentEdge.x < 0 || currentEdge.y < 0 || currentEdge.x >= vertexCount || currentEdge.y >= vertexCount)
  84. continue;
  85. var start = vertices[edges[i].x];
  86. var end = vertices[edges[i].y];
  87. var direction = (end - start).normalized;
  88. var right = Vector3.Cross(Vector3.forward, direction) * outlineSize;
  89. GL.Vertex(start - right);
  90. GL.Vertex(start + right);
  91. GL.Vertex(end + right);
  92. GL.Vertex(end - right);
  93. }
  94. GL.End();
  95. GL.PopMatrix();
  96. for (var i = 0; i < edgeCount; i++)
  97. {
  98. var currentEdge = edges[i];
  99. if (currentEdge.x < 0 || currentEdge.y < 0 || currentEdge.x >= vertexCount || currentEdge.y >= vertexCount)
  100. continue;
  101. var start = vertices[edges[i].x];
  102. var end = vertices[edges[i].y];
  103. Graphics.DrawMeshNow(m_CircleMesh, multMatrix * Matrix4x4.TRS(start, Quaternion.identity, Vector3.one * outlineSize));
  104. Graphics.DrawMeshNow(m_CircleMesh, multMatrix * Matrix4x4.TRS(end, Quaternion.identity, Vector3.one * outlineSize));
  105. }
  106. }
  107. void DrawMeshOutline(Mesh mesh, SpriteCache spriteCache, Matrix4x4 multMatrix, float outlineSize, Color outlineColor, float adjustForGamma)
  108. {
  109. TryRegenerateMaskTexture(spriteCache);
  110. m_OutlineMaterial.SetColor(k_OutlineColorProperty, outlineColor);
  111. m_OutlineMaterial.SetFloat(k_AdjustLinearForGammaProperty, adjustForGamma);
  112. m_OutlineMaterial.SetFloat(k_OutlineSizeProperty, outlineSize);
  113. m_OutlineMaterial.SetPass(0);
  114. GL.PushMatrix();
  115. GL.MultMatrix(multMatrix);
  116. var meshBoundsRect = new Rect(mesh.bounds.min.x, mesh.bounds.min.y, mesh.bounds.size.x, mesh.bounds.size.y);
  117. GL.Begin(GL.QUADS);
  118. GL.Color(Color.white);
  119. GL.TexCoord(new Vector3(0, 0, 0));
  120. GL.Vertex3(meshBoundsRect.xMin, meshBoundsRect.yMin, 0);
  121. GL.TexCoord(new Vector3(1, 0, 0));
  122. GL.Vertex3(meshBoundsRect.xMax, meshBoundsRect.yMin, 0);
  123. GL.TexCoord(new Vector3(1, 1, 0));
  124. GL.Vertex3(meshBoundsRect.xMax, meshBoundsRect.yMax, 0);
  125. GL.TexCoord(new Vector3(0, 1, 0));
  126. GL.Vertex3(meshBoundsRect.xMin, meshBoundsRect.yMax, 0);
  127. GL.End();
  128. GL.PopMatrix();
  129. }
  130. Texture GenerateOutlineTexture(SpriteCache spriteCache, RenderTexture reuseRT)
  131. {
  132. if (spriteCache == null)
  133. return null;
  134. var mesh = GetMesh(spriteCache);
  135. if (mesh == null || (int)mesh.bounds.size.x == 0 || (int)mesh.bounds.size.y == 0)
  136. return null;
  137. var bounds = mesh.bounds;
  138. UnityEngine.Profiling.Profiler.BeginSample("SpriteOutlineRenderer::GenerateOutlineTexture");
  139. if (reuseRT == null || reuseRT.width != (int)bounds.size.x || reuseRT.height != (int)bounds.size.y)
  140. {
  141. UnityEngine.Profiling.Profiler.BeginSample("SpriteOutlineRenderer::CreateRT");
  142. if(reuseRT != null)
  143. Object.DestroyImmediate(reuseRT);
  144. reuseRT = new RenderTexture((int)bounds.size.x, (int)bounds.size.y, 24, RenderTextureFormat.ARGBHalf) { filterMode = FilterMode.Bilinear };
  145. UnityEngine.Profiling.Profiler.EndSample();
  146. }
  147. var oldRT = RenderTexture.active;
  148. Graphics.SetRenderTarget(reuseRT);
  149. m_BitMaskMaterial.SetPass(0);
  150. UnityEngine.Profiling.Profiler.BeginSample("SpriteOutlineRenderer::DrawMesh");
  151. GL.Clear(false, true, new Color(0, 0, 0, 0));
  152. GL.PushMatrix();
  153. GL.LoadOrtho();
  154. var h = bounds.size.y * 0.5f;
  155. var w = h * (bounds.size.x / bounds.size.y);
  156. GL.LoadProjectionMatrix(Matrix4x4.Ortho(-w, w, -h, h, -1, 1));
  157. GL.Begin(GL.QUADS);
  158. GL.Color(Color.white);
  159. Graphics.DrawMeshNow(mesh, Matrix4x4.Translate(-bounds.center));
  160. GL.End();
  161. GL.PopMatrix();
  162. Graphics.SetRenderTarget(oldRT);
  163. UnityEngine.Profiling.Profiler.EndSample();
  164. UnityEngine.Profiling.Profiler.EndSample();
  165. return reuseRT;
  166. }
  167. static Mesh GetMesh(SpriteCache sprite)
  168. {
  169. var meshPreview = sprite.GetMeshPreview();
  170. if (meshPreview == null)
  171. return null;
  172. return meshPreview.mesh.vertexCount > 0 ? meshPreview.mesh : meshPreview.defaultMesh;
  173. }
  174. static Mesh GenerateCircleMesh()
  175. {
  176. const int triangleVerts = 9;
  177. var verts = new Vector3[triangleVerts];
  178. for (var i = 1; i < verts.Length; i++)
  179. {
  180. verts[i] = Quaternion.Euler(0, 0, i * 360f / (verts.Length - 1)) * Vector3.up;
  181. }
  182. var indices = new int[(verts.Length - 1) * 3];
  183. var index = 0;
  184. for (var i = 1; i < triangleVerts; i++)
  185. {
  186. indices[index++] = 0;
  187. indices[index++] = i;
  188. indices[index++] = i + 1 < triangleVerts ? i + 1 : 1;
  189. }
  190. return new Mesh { vertices = verts, triangles = indices, hideFlags = HideFlags.HideAndDontSave};
  191. }
  192. void OnMeshPreviewChanged(MeshPreviewCache mesh)
  193. {
  194. AddOrUpdateMaskTexture(mesh.sprite, true);
  195. }
  196. void OnSelectionChanged(SpriteCache spriteCache)
  197. {
  198. AddOrUpdateMaskTexture(spriteCache, false);
  199. }
  200. void DestroyMaterialsAndMeshes()
  201. {
  202. if(m_EdgeOutlineMaterial != null)
  203. Object.DestroyImmediate(m_EdgeOutlineMaterial);
  204. if (m_BitMaskMaterial != null)
  205. Object.DestroyImmediate(m_BitMaskMaterial);
  206. if (m_OutlineMaterial != null)
  207. Object.DestroyImmediate(m_OutlineMaterial);
  208. if(m_CircleMesh != null)
  209. Object.DestroyImmediate(m_CircleMesh);
  210. }
  211. void DestroyTextures()
  212. {
  213. if (m_OutlineTextureCache != null)
  214. {
  215. foreach (var value in m_OutlineTextureCache.Values)
  216. {
  217. if (value != null && value.outlineTexture != null)
  218. Object.DestroyImmediate(value.outlineTexture);
  219. }
  220. m_OutlineTextureCache.Clear();
  221. }
  222. }
  223. void AddOrUpdateMaskTexture(SpriteCache sprite, bool regenerate)
  224. {
  225. if (m_OutlineTextureCache != null && sprite != null)
  226. {
  227. if (!m_OutlineTextureCache.ContainsKey(sprite.id))
  228. m_OutlineTextureCache.Add(sprite.id, new OutlineRenderTexture() {dirty = true});
  229. var outlineTextureCache = m_OutlineTextureCache[sprite.id];
  230. outlineTextureCache.dirty |= regenerate;
  231. }
  232. }
  233. void TryRegenerateMaskTexture(SpriteCache sprite)
  234. {
  235. var selectedSprite = sprite.skinningCache.selectedSprite;
  236. var outlineTextureCache = m_OutlineTextureCache[sprite.id];
  237. if (sprite == selectedSprite)
  238. {
  239. if (outlineTextureCache.dirty || outlineTextureCache.outlineTexture == null)
  240. {
  241. outlineTextureCache.outlineTexture = GenerateOutlineTexture(sprite, (RenderTexture)outlineTextureCache.outlineTexture);
  242. if (outlineTextureCache.outlineTexture != null)
  243. {
  244. outlineTextureCache.outlineTexture.hideFlags = HideFlags.HideAndDontSave;
  245. outlineTextureCache.dirty = false;
  246. }
  247. }
  248. m_OutlineMaterial.mainTexture = outlineTextureCache.outlineTexture;
  249. }
  250. }
  251. }
  252. }