SkeletonGraphicRenderTexture.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated July 28, 2023. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2023, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software or
  13. * otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
  27. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. #if UNITY_2017_2_OR_NEWER
  30. #define HAS_VECTOR2INT
  31. #endif
  32. using System.Collections.Generic;
  33. using UnityEngine;
  34. using UnityEngine.Rendering;
  35. using UnityEngine.UI;
  36. namespace Spine.Unity.Examples {
  37. /// <summary>
  38. /// When enabled, this component renders a skeleton to a RenderTexture and
  39. /// then draws this RenderTexture at a UI SkeletonSubmeshGraphic quad of the same size.
  40. /// This allows changing transparency at a single quad, which produces a more
  41. /// natural fadeout effect.
  42. /// Note: It is recommended to keep this component disabled as much as possible
  43. /// because of the additional rendering overhead. Only enable it when alpha blending is required.
  44. /// </summary>
  45. [RequireComponent(typeof(SkeletonGraphic))]
  46. public class SkeletonGraphicRenderTexture : SkeletonRenderTextureBase {
  47. #if HAS_VECTOR2INT
  48. [System.Serializable]
  49. public struct TextureMaterialPair {
  50. public Texture texture;
  51. public Material material;
  52. public TextureMaterialPair (Texture texture, Material material) {
  53. this.texture = texture;
  54. this.material = material;
  55. }
  56. }
  57. public RectTransform customRenderRect;
  58. protected SkeletonGraphic skeletonGraphic;
  59. public List<TextureMaterialPair> meshRendererMaterialForTexture = new List<TextureMaterialPair>();
  60. protected CanvasRenderer quadCanvasRenderer;
  61. protected SkeletonSubmeshGraphic quadMaskableGraphic;
  62. protected readonly Vector3[] worldCorners = new Vector3[4];
  63. public void ResetMeshRendererMaterials () {
  64. meshRendererMaterialForTexture.Clear();
  65. AtlasAssetBase[] atlasAssets = skeletonGraphic.SkeletonDataAsset.atlasAssets;
  66. for (int i = 0; i < atlasAssets.Length; ++i) {
  67. foreach (Material material in atlasAssets[i].Materials) {
  68. if (material.mainTexture != null) {
  69. meshRendererMaterialForTexture.Add(
  70. new TextureMaterialPair(material.mainTexture, material));
  71. }
  72. }
  73. }
  74. }
  75. protected override void Awake () {
  76. base.Awake();
  77. skeletonGraphic = this.GetComponent<SkeletonGraphic>();
  78. if (targetCamera == null) {
  79. targetCamera = skeletonGraphic.canvas.worldCamera;
  80. if (targetCamera == null)
  81. targetCamera = Camera.main;
  82. }
  83. CreateQuadChild();
  84. }
  85. void CreateQuadChild () {
  86. quad = new GameObject(this.name + " RenderTexture", typeof(CanvasRenderer), typeof(SkeletonSubmeshGraphic));
  87. quad.transform.SetParent(this.transform.parent, false);
  88. quadCanvasRenderer = quad.GetComponent<CanvasRenderer>();
  89. quadMaskableGraphic = quad.GetComponent<SkeletonSubmeshGraphic>();
  90. quadMesh = new Mesh();
  91. quadMesh.MarkDynamic();
  92. quadMesh.name = "RenderTexture Quad";
  93. quadMesh.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
  94. if (quadMaterial == null) {
  95. quadMaterial = new Material(Shader.Find("Spine/SkeletonGraphic"));
  96. quadMaterial.EnableKeyword("_CANVAS_GROUP_COMPATIBLE");
  97. }
  98. }
  99. void Reset () {
  100. skeletonGraphic = this.GetComponent<SkeletonGraphic>();
  101. ResetMeshRendererMaterials();
  102. #if UNITY_EDITOR
  103. string[] assets = UnityEditor.AssetDatabase.FindAssets("t:material RenderQuadGraphicMaterial");
  104. if (assets.Length > 0) {
  105. string materialPath = UnityEditor.AssetDatabase.GUIDToAssetPath(assets[0]);
  106. quadMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(materialPath);
  107. }
  108. #endif
  109. }
  110. void OnEnable () {
  111. skeletonGraphic.OnInstructionsPrepared += PrepareQuad;
  112. skeletonGraphic.AssignMeshOverrideSingleRenderer += RenderSingleMeshToRenderTexture;
  113. skeletonGraphic.AssignMeshOverrideMultipleRenderers += RenderMultipleMeshesToRenderTexture;
  114. skeletonGraphic.disableMeshAssignmentOnOverride = true;
  115. skeletonGraphic.OnMeshAndMaterialsUpdated += RenderOntoQuad;
  116. skeletonGraphic.OnAnimationRebuild += OnRebuild;
  117. List<CanvasRenderer> canvasRenderers = skeletonGraphic.canvasRenderers;
  118. for (int i = 0; i < canvasRenderers.Count; ++i)
  119. canvasRenderers[i].cull = true;
  120. if (quadCanvasRenderer)
  121. quadCanvasRenderer.gameObject.SetActive(true);
  122. }
  123. void OnDisable () {
  124. skeletonGraphic.OnInstructionsPrepared -= PrepareQuad;
  125. skeletonGraphic.AssignMeshOverrideSingleRenderer -= RenderSingleMeshToRenderTexture;
  126. skeletonGraphic.AssignMeshOverrideMultipleRenderers -= RenderMultipleMeshesToRenderTexture;
  127. skeletonGraphic.disableMeshAssignmentOnOverride = false;
  128. skeletonGraphic.OnMeshAndMaterialsUpdated -= RenderOntoQuad;
  129. skeletonGraphic.OnAnimationRebuild -= OnRebuild;
  130. List<CanvasRenderer> canvasRenderers = skeletonGraphic.canvasRenderers;
  131. for (int i = 0; i < canvasRenderers.Count; ++i)
  132. canvasRenderers[i].cull = false;
  133. if (quadCanvasRenderer)
  134. quadCanvasRenderer.gameObject.SetActive(false);
  135. if (renderTexture)
  136. RenderTexture.ReleaseTemporary(renderTexture);
  137. allocatedRenderTextureSize = Vector2Int.zero;
  138. }
  139. void PrepareQuad (SkeletonRendererInstruction instruction) {
  140. PrepareForMesh();
  141. SetupQuad();
  142. }
  143. void RenderOntoQuad (SkeletonGraphic skeletonRenderer) {
  144. AssignAtQuad();
  145. }
  146. void OnRebuild (ISkeletonAnimation skeletonGraphic) {
  147. ResetMeshRendererMaterials();
  148. }
  149. protected void PrepareForMesh () {
  150. // We need to get the min/max of all four corners, rotation of the skeleton
  151. // in combination with perspective projection otherwise might lead to incorrect
  152. // screen space min/max.
  153. RectTransform rectTransform = customRenderRect ? customRenderRect : skeletonGraphic.rectTransform;
  154. rectTransform.GetWorldCorners(worldCorners);
  155. RenderMode canvasRenderMode = skeletonGraphic.canvas.renderMode;
  156. Vector3 screenCorner0, screenCorner1, screenCorner2, screenCorner3;
  157. // note: world corners are ordered bottom left, top left, top right, bottom right.
  158. // This corresponds to 0, 3, 1, 2 in our desired order.
  159. if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
  160. screenCorner0 = worldCorners[0];
  161. screenCorner1 = worldCorners[3];
  162. screenCorner2 = worldCorners[1];
  163. screenCorner3 = worldCorners[2];
  164. } else {
  165. screenCorner0 = targetCamera.WorldToScreenPoint(worldCorners[0]);
  166. screenCorner1 = targetCamera.WorldToScreenPoint(worldCorners[3]);
  167. screenCorner2 = targetCamera.WorldToScreenPoint(worldCorners[1]);
  168. screenCorner3 = targetCamera.WorldToScreenPoint(worldCorners[2]);
  169. }
  170. // To avoid perspective distortion when rotated, we project all vertices
  171. // onto a plane parallel to the view frustum near plane.
  172. // Avoids the requirement of 'noperspective' vertex attribute interpolation modifier in shaders.
  173. float averageScreenDepth = (screenCorner0.z + screenCorner1.z + screenCorner2.z + screenCorner3.z) / 4.0f;
  174. screenCorner0.z = screenCorner1.z = screenCorner2.z = screenCorner3.z = averageScreenDepth;
  175. if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
  176. worldCornerNoDistortion0 = screenCorner0;
  177. worldCornerNoDistortion1 = screenCorner1;
  178. worldCornerNoDistortion2 = screenCorner2;
  179. worldCornerNoDistortion3 = screenCorner3;
  180. } else {
  181. worldCornerNoDistortion0 = targetCamera.ScreenToWorldPoint(screenCorner0);
  182. worldCornerNoDistortion1 = targetCamera.ScreenToWorldPoint(screenCorner1);
  183. worldCornerNoDistortion2 = targetCamera.ScreenToWorldPoint(screenCorner2);
  184. worldCornerNoDistortion3 = targetCamera.ScreenToWorldPoint(screenCorner3);
  185. }
  186. Vector3 screenSpaceMin, screenSpaceMax;
  187. PrepareTextureMapping(out screenSpaceMin, out screenSpaceMax,
  188. screenCorner0, screenCorner1, screenCorner2, screenCorner3);
  189. PrepareCommandBuffer(targetCamera, screenSpaceMin, screenSpaceMax);
  190. }
  191. protected Material MeshRendererMaterialForTexture (Texture texture) {
  192. return meshRendererMaterialForTexture.Find(x => x.texture == texture).material;
  193. }
  194. protected void RenderSingleMeshToRenderTexture (Mesh mesh, Material graphicMaterial, Texture texture) {
  195. if (mesh.subMeshCount == 0) return;
  196. Material meshRendererMaterial = MeshRendererMaterialForTexture(texture);
  197. foreach (int shaderPass in shaderPasses)
  198. commandBuffer.DrawMesh(mesh, transform.localToWorldMatrix, meshRendererMaterial, 0, shaderPass);
  199. Graphics.ExecuteCommandBuffer(commandBuffer);
  200. }
  201. protected void RenderMultipleMeshesToRenderTexture (int meshCount,
  202. Mesh[] meshes, Material[] graphicMaterials, Texture[] textures) {
  203. for (int i = 0; i < meshCount; ++i) {
  204. Mesh mesh = meshes[i];
  205. if (mesh.subMeshCount == 0) continue;
  206. Material meshRendererMaterial = MeshRendererMaterialForTexture(textures[i]);
  207. foreach (int shaderPass in shaderPasses)
  208. commandBuffer.DrawMesh(mesh, transform.localToWorldMatrix, meshRendererMaterial, 0, shaderPass);
  209. }
  210. Graphics.ExecuteCommandBuffer(commandBuffer);
  211. }
  212. protected void SetupQuad () {
  213. quadCanvasRenderer.SetMaterial(quadMaterial, this.renderTexture);
  214. quadMaskableGraphic.color = color;
  215. quadCanvasRenderer.SetColor(color);
  216. RectTransform srcRectTransform = skeletonGraphic.rectTransform;
  217. RectTransform dstRectTransform = quadMaskableGraphic.rectTransform;
  218. dstRectTransform.anchorMin = srcRectTransform.anchorMin;
  219. dstRectTransform.anchorMax = srcRectTransform.anchorMax;
  220. dstRectTransform.anchoredPosition = srcRectTransform.anchoredPosition;
  221. dstRectTransform.pivot = srcRectTransform.pivot;
  222. dstRectTransform.localScale = srcRectTransform.localScale;
  223. dstRectTransform.sizeDelta = srcRectTransform.sizeDelta;
  224. dstRectTransform.rotation = srcRectTransform.rotation;
  225. }
  226. protected void PrepareCommandBuffer (Camera targetCamera, Vector3 screenSpaceMin, Vector3 screenSpaceMax) {
  227. commandBuffer.Clear();
  228. commandBuffer.SetRenderTarget(renderTexture);
  229. commandBuffer.ClearRenderTarget(true, true, Color.clear);
  230. Rect canvasRect = skeletonGraphic.canvas.pixelRect;
  231. Matrix4x4 projectionMatrix = Matrix4x4.Ortho(
  232. canvasRect.x, canvasRect.x + canvasRect.width,
  233. canvasRect.y, canvasRect.y + canvasRect.height,
  234. float.MinValue, float.MaxValue);
  235. RenderMode canvasRenderMode = skeletonGraphic.canvas.renderMode;
  236. if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
  237. commandBuffer.SetViewMatrix(Matrix4x4.identity);
  238. commandBuffer.SetProjectionMatrix(projectionMatrix);
  239. } else {
  240. commandBuffer.SetViewMatrix(targetCamera.worldToCameraMatrix);
  241. commandBuffer.SetProjectionMatrix(targetCamera.projectionMatrix);
  242. }
  243. Vector2 targetCameraViewportSize = targetCamera.pixelRect.size;
  244. Rect viewportRect = new Rect(-screenSpaceMin * downScaleFactor, targetCameraViewportSize * downScaleFactor);
  245. commandBuffer.SetViewport(viewportRect);
  246. }
  247. protected override void AssignMeshAtRenderer () {
  248. quadCanvasRenderer.SetMesh(quadMesh);
  249. }
  250. #endif // HAS_VECTOR2INT
  251. }
  252. }