SkeletonGraphicRenderTexture.cs 11 KB

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