SpriteOutlineRenderer.cs 13 KB

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