SkeletonGraphicRenderTexture.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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. protected override void Awake () {
  64. base.Awake();
  65. skeletonGraphic = this.GetComponent<SkeletonGraphic>();
  66. if (targetCamera == null) {
  67. targetCamera = skeletonGraphic.canvas.worldCamera;
  68. if (targetCamera == null)
  69. targetCamera = Camera.main;
  70. }
  71. CreateQuadChild();
  72. }
  73. void CreateQuadChild () {
  74. quad = new GameObject(this.name + " RenderTexture", typeof(CanvasRenderer), typeof(SkeletonSubmeshGraphic));
  75. quad.transform.SetParent(this.transform.parent, false);
  76. quadCanvasRenderer = quad.GetComponent<CanvasRenderer>();
  77. quadMaskableGraphic = quad.GetComponent<SkeletonSubmeshGraphic>();
  78. quadMesh = new Mesh();
  79. quadMesh.MarkDynamic();
  80. quadMesh.name = "RenderTexture Quad";
  81. quadMesh.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
  82. if (quadMaterial == null) {
  83. quadMaterial = new Material(Shader.Find("Spine/SkeletonGraphic"));
  84. quadMaterial.EnableKeyword("_CANVAS_GROUP_COMPATIBLE");
  85. }
  86. }
  87. void Reset () {
  88. skeletonGraphic = this.GetComponent<SkeletonGraphic>();
  89. AtlasAssetBase[] atlasAssets = skeletonGraphic.SkeletonDataAsset.atlasAssets;
  90. for (int i = 0; i < atlasAssets.Length; ++i) {
  91. foreach (Material material in atlasAssets[i].Materials) {
  92. if (material.mainTexture != null) {
  93. meshRendererMaterialForTexture.Add(
  94. new TextureMaterialPair(material.mainTexture, material));
  95. }
  96. }
  97. }
  98. #if UNITY_EDITOR
  99. string[] assets = UnityEditor.AssetDatabase.FindAssets("t:material RenderQuadGraphicMaterial");
  100. if (assets.Length > 0) {
  101. string materialPath = UnityEditor.AssetDatabase.GUIDToAssetPath(assets[0]);
  102. quadMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(materialPath);
  103. }
  104. #endif
  105. }
  106. void OnEnable () {
  107. skeletonGraphic.OnInstructionsPrepared += PrepareQuad;
  108. skeletonGraphic.AssignMeshOverrideSingleRenderer += RenderSingleMeshToRenderTexture;
  109. skeletonGraphic.AssignMeshOverrideMultipleRenderers += RenderMultipleMeshesToRenderTexture;
  110. skeletonGraphic.disableMeshAssignmentOnOverride = true;
  111. skeletonGraphic.OnMeshAndMaterialsUpdated += RenderOntoQuad;
  112. List<CanvasRenderer> canvasRenderers = skeletonGraphic.canvasRenderers;
  113. for (int i = 0; i < canvasRenderers.Count; ++i)
  114. canvasRenderers[i].cull = true;
  115. if (quadCanvasRenderer)
  116. quadCanvasRenderer.gameObject.SetActive(true);
  117. }
  118. void OnDisable () {
  119. skeletonGraphic.OnInstructionsPrepared -= PrepareQuad;
  120. skeletonGraphic.AssignMeshOverrideSingleRenderer -= RenderSingleMeshToRenderTexture;
  121. skeletonGraphic.AssignMeshOverrideMultipleRenderers -= RenderMultipleMeshesToRenderTexture;
  122. skeletonGraphic.disableMeshAssignmentOnOverride = false;
  123. skeletonGraphic.OnMeshAndMaterialsUpdated -= RenderOntoQuad;
  124. List<CanvasRenderer> canvasRenderers = skeletonGraphic.canvasRenderers;
  125. for (int i = 0; i < canvasRenderers.Count; ++i)
  126. canvasRenderers[i].cull = false;
  127. if (quadCanvasRenderer)
  128. quadCanvasRenderer.gameObject.SetActive(false);
  129. if (renderTexture)
  130. RenderTexture.ReleaseTemporary(renderTexture);
  131. allocatedRenderTextureSize = Vector2Int.zero;
  132. }
  133. void PrepareQuad (SkeletonRendererInstruction instruction) {
  134. PrepareForMesh();
  135. SetupQuad();
  136. }
  137. void RenderOntoQuad (SkeletonGraphic skeletonRenderer) {
  138. AssignAtQuad();
  139. }
  140. protected void PrepareForMesh () {
  141. // We need to get the min/max of all four corners, rotation of the skeleton
  142. // in combination with perspective projection otherwise might lead to incorrect
  143. // screen space min/max.
  144. RectTransform rectTransform = customRenderRect ? customRenderRect : skeletonGraphic.rectTransform;
  145. rectTransform.GetWorldCorners(worldCorners);
  146. RenderMode canvasRenderMode = skeletonGraphic.canvas.renderMode;
  147. Vector3 screenCorner0, screenCorner1, screenCorner2, screenCorner3;
  148. // note: world corners are ordered bottom left, top left, top right, bottom right.
  149. // This corresponds to 0, 3, 1, 2 in our desired order.
  150. if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
  151. screenCorner0 = worldCorners[0];
  152. screenCorner1 = worldCorners[3];
  153. screenCorner2 = worldCorners[1];
  154. screenCorner3 = worldCorners[2];
  155. } else {
  156. screenCorner0 = targetCamera.WorldToScreenPoint(worldCorners[0]);
  157. screenCorner1 = targetCamera.WorldToScreenPoint(worldCorners[3]);
  158. screenCorner2 = targetCamera.WorldToScreenPoint(worldCorners[1]);
  159. screenCorner3 = targetCamera.WorldToScreenPoint(worldCorners[2]);
  160. }
  161. // To avoid perspective distortion when rotated, we project all vertices
  162. // onto a plane parallel to the view frustum near plane.
  163. // Avoids the requirement of 'noperspective' vertex attribute interpolation modifier in shaders.
  164. float averageScreenDepth = (screenCorner0.z + screenCorner1.z + screenCorner2.z + screenCorner3.z) / 4.0f;
  165. screenCorner0.z = screenCorner1.z = screenCorner2.z = screenCorner3.z = averageScreenDepth;
  166. if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
  167. worldCornerNoDistortion0 = screenCorner0;
  168. worldCornerNoDistortion1 = screenCorner1;
  169. worldCornerNoDistortion2 = screenCorner2;
  170. worldCornerNoDistortion3 = screenCorner3;
  171. } else {
  172. worldCornerNoDistortion0 = targetCamera.ScreenToWorldPoint(screenCorner0);
  173. worldCornerNoDistortion1 = targetCamera.ScreenToWorldPoint(screenCorner1);
  174. worldCornerNoDistortion2 = targetCamera.ScreenToWorldPoint(screenCorner2);
  175. worldCornerNoDistortion3 = targetCamera.ScreenToWorldPoint(screenCorner3);
  176. }
  177. Vector3 screenSpaceMin, screenSpaceMax;
  178. PrepareTextureMapping(out screenSpaceMin, out screenSpaceMax,
  179. screenCorner0, screenCorner1, screenCorner2, screenCorner3);
  180. PrepareCommandBuffer(targetCamera, screenSpaceMin, screenSpaceMax);
  181. }
  182. protected Material MeshRendererMaterialForTexture (Texture texture) {
  183. return meshRendererMaterialForTexture.Find(x => x.texture == texture).material;
  184. }
  185. protected void RenderSingleMeshToRenderTexture (Mesh mesh, Material graphicMaterial, Texture texture) {
  186. Material meshRendererMaterial = MeshRendererMaterialForTexture(texture);
  187. foreach (int shaderPass in shaderPasses)
  188. commandBuffer.DrawMesh(mesh, transform.localToWorldMatrix, meshRendererMaterial, 0, shaderPass);
  189. Graphics.ExecuteCommandBuffer(commandBuffer);
  190. }
  191. protected void RenderMultipleMeshesToRenderTexture (int meshCount,
  192. Mesh[] meshes, Material[] graphicMaterials, Texture[] textures) {
  193. for (int i = 0; i < meshCount; ++i) {
  194. Material meshRendererMaterial = MeshRendererMaterialForTexture(textures[i]);
  195. foreach (int shaderPass in shaderPasses)
  196. commandBuffer.DrawMesh(meshes[i], transform.localToWorldMatrix, meshRendererMaterial, 0, shaderPass);
  197. }
  198. Graphics.ExecuteCommandBuffer(commandBuffer);
  199. }
  200. protected void SetupQuad () {
  201. quadCanvasRenderer.SetMaterial(quadMaterial, this.renderTexture);
  202. quadMaskableGraphic.color = color;
  203. quadCanvasRenderer.SetColor(color);
  204. RectTransform srcRectTransform = skeletonGraphic.rectTransform;
  205. RectTransform dstRectTransform = quadMaskableGraphic.rectTransform;
  206. dstRectTransform.anchorMin = srcRectTransform.anchorMin;
  207. dstRectTransform.anchorMax = srcRectTransform.anchorMax;
  208. dstRectTransform.anchoredPosition = srcRectTransform.anchoredPosition;
  209. dstRectTransform.pivot = srcRectTransform.pivot;
  210. dstRectTransform.localScale = srcRectTransform.localScale;
  211. dstRectTransform.sizeDelta = srcRectTransform.sizeDelta;
  212. dstRectTransform.rotation = srcRectTransform.rotation;
  213. }
  214. protected void PrepareCommandBuffer (Camera targetCamera, Vector3 screenSpaceMin, Vector3 screenSpaceMax) {
  215. commandBuffer.Clear();
  216. commandBuffer.SetRenderTarget(renderTexture);
  217. commandBuffer.ClearRenderTarget(true, true, Color.clear);
  218. Rect canvasRect = skeletonGraphic.canvas.pixelRect;
  219. Matrix4x4 projectionMatrix = Matrix4x4.Ortho(
  220. canvasRect.x, canvasRect.x + canvasRect.width,
  221. canvasRect.y, canvasRect.y + canvasRect.height,
  222. float.MinValue, float.MaxValue);
  223. RenderMode canvasRenderMode = skeletonGraphic.canvas.renderMode;
  224. if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
  225. commandBuffer.SetViewMatrix(Matrix4x4.identity);
  226. commandBuffer.SetProjectionMatrix(projectionMatrix);
  227. } else {
  228. commandBuffer.SetViewMatrix(targetCamera.worldToCameraMatrix);
  229. commandBuffer.SetProjectionMatrix(targetCamera.projectionMatrix);
  230. }
  231. Vector2 targetCameraViewportSize = targetCamera.pixelRect.size;
  232. Rect viewportRect = new Rect(-screenSpaceMin * downScaleFactor, targetCameraViewportSize * downScaleFactor);
  233. commandBuffer.SetViewport(viewportRect);
  234. }
  235. protected override void AssignMeshAtRenderer () {
  236. quadCanvasRenderer.SetMesh(quadMesh);
  237. }
  238. #endif // HAS_VECTOR2INT
  239. }
  240. }