Эх сурвалжийг харах

Merge branch '4.1' of https://github.com/esotericsoftware/spine-runtimes into 4.1

Mario Zechner 3 жил өмнө
parent
commit
135d97466d

+ 2 - 0
CHANGELOG.md

@@ -316,6 +316,8 @@
   * Added example component `SkeletonRenderTexture` to render a `SkeletonRenderer` to a `RenderTexture`, mainly for proper transparency. Added an example scene named `RenderTexture FadeOut Transparency` that demonstrates usage for a fadeout transparency effect.
   * Added another fadeout example component named `SkeletonRenderTextureFadeout` which takes over transparency fadeout when enabled. You can use this component as-is, attach it in disabled state and enable it to start a fadeout effect.
   * Timeline clips now offer an additional `Alpha` parameter for setting a custom constant mix alpha value other than 1.0, just as `TrackEntry.Alpha`. Defaults to 1.0.
+  * `SkeletonGraphic` now provides additional render callback delegates `OnInstructionsPrepared`, `AssignMeshOverrideSingleRenderer` and `AssignMeshOverrideMultipleRenderers`. `OnInstructionsPrepared` is raised at the end of LateUpdate after render instructions are done, target renderers are prepared, and the mesh is ready to be generated. The two `AssignMeshOverride` delegates allow separate code to take over mesh and material assignment of a `SkeletonGraphic` component.
+  * Added example component `SkeletonGraphicRenderTexture` to render a `SkeletonGraphic` to a `RenderTexture` (similar as `SkeletonRenderTexture`), mainly for proper transparency. Extended example scene `RenderTexture FadeOut Transparency` accordingly.
 
 * **Changes of default values**
 

+ 1 - 1
spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp

@@ -967,7 +967,7 @@ Animation *SkeletonJson::readAnimation(Json *root, SkeletonData *skeletonData) {
 				toColor(color2, Json::getString(keyMap, "dark", 0), false);
 
 				for (frame = 0, bezier = 0;; ++frame) {
-					timeline->setFrame(frame, time, color.r, color.g, color.b, color.a, color2.g, color2.g, color2.b);
+					timeline->setFrame(frame, time, color.r, color.g, color.b, color.a, color2.r, color2.g, color2.b);
 					nextMap = keyMap->_next;
 					if (!nextMap) {
 						// timeline.shrink(); // BOZO

+ 1 - 1
spine-godot/README.md

@@ -2,7 +2,7 @@
 
 The spine-godot runtime provides functionality to load, manipulate and render [Spine](http://esotericsoftware.com) skeletal animation data using [Godot](https://godotengine.org/). spine-godot is based on [spine-cpp](../spine-cpp).
 
-# See the [spine-godot documentation](http://esotericsoftware.com/spine-godot]) for in-depth information.
+# See the [spine-godot documentation](http://esotericsoftware.com/spine-godot) for in-depth information.
 
 ## Licensing
 

+ 274 - 17
spine-unity/Assets/Spine Examples/Other Examples/RenderTexture FadeOut Transparency.unity

@@ -153,7 +153,7 @@ RectTransform:
   m_LocalScale: {x: 1, y: 1, z: 1}
   m_Children: []
   m_Father: {fileID: 592567554}
-  m_RootOrder: 0
+  m_RootOrder: 1
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_AnchorMin: {x: 1, y: 1}
   m_AnchorMax: {x: 1, y: 1}
@@ -256,6 +256,8 @@ MonoBehaviour:
     materialsInsideMask: []
     materialsOutsideMask: []
   disableRenderingOnOverride: 1
+  updateTiming: 1
+  unscaledTime: 0
   _animationName: run
   loop: 1
   timeScale: 1
@@ -316,11 +318,11 @@ Transform:
   m_PrefabAsset: {fileID: 0}
   m_GameObject: {fileID: 334034152}
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
-  m_LocalPosition: {x: -5.93, y: 0, z: 5.66}
+  m_LocalPosition: {x: -7.83, y: 0, z: 5.66}
   m_LocalScale: {x: 1, y: 1, z: 1}
   m_Children: []
   m_Father: {fileID: 0}
-  m_RootOrder: 4
+  m_RootOrder: 3
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!1 &541830406
 GameObject:
@@ -352,7 +354,7 @@ RectTransform:
   m_LocalScale: {x: 1, y: 1, z: 1}
   m_Children: []
   m_Father: {fileID: 592567554}
-  m_RootOrder: 1
+  m_RootOrder: 2
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
   m_AnchorMin: {x: 1, y: 1}
   m_AnchorMax: {x: 1, y: 1}
@@ -433,9 +435,11 @@ RectTransform:
   m_LocalPosition: {x: 0, y: 0, z: 0}
   m_LocalScale: {x: 0, y: 0, z: 0}
   m_Children:
+  - {fileID: 1911967440}
   - {fileID: 71621967}
   - {fileID: 541830407}
   - {fileID: 1682675646}
+  - {fileID: 1735507358}
   m_Father: {fileID: 1799507978}
   m_RootOrder: 0
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -488,6 +492,41 @@ Canvas:
   m_SortingLayerID: 0
   m_SortingOrder: 0
   m_TargetDisplay: 0
+--- !u!1 &1089682726
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1089682727}
+  m_Layer: 0
+  m_Name: CustomRenderRect
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &1089682727
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1089682726}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1911967440}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0.5, y: 0.5}
+  m_AnchorMax: {x: 0.5, y: 0.5}
+  m_AnchoredPosition: {x: 126.27, y: 7.130005}
+  m_SizeDelta: {x: 1893.1, y: 1654.617}
+  m_Pivot: {x: 0.5, y: 0.5}
 --- !u!1 &1368805070
 GameObject:
   m_ObjectHideFlags: 0
@@ -532,6 +571,7 @@ MonoBehaviour:
   m_Name: 
   m_EditorClassIdentifier: 
   renderTextureFadeout: {fileID: 1786065619}
+  renderTextureFadeoutCanvas: {fileID: 1911967443}
   normalSkeletonRenderer: {fileID: 334034153}
 --- !u!1 &1369381599
 GameObject:
@@ -805,12 +845,12 @@ RectTransform:
   m_LocalScale: {x: 1, y: 1, z: 1}
   m_Children: []
   m_Father: {fileID: 592567554}
-  m_RootOrder: 2
+  m_RootOrder: 3
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
-  m_AnchorMin: {x: 0.5, y: 0}
-  m_AnchorMax: {x: 0.5, y: 0.5}
-  m_AnchoredPosition: {x: -331.4, y: -186}
-  m_SizeDelta: {x: 1176.1, y: -365}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 0.6932292, y: 0.18750001}
+  m_AnchoredPosition: {x: 18.079224, y: 1.5001221}
+  m_SizeDelta: {x: -44.942017, y: 10}
   m_Pivot: {x: 0.5, y: 0.5}
 --- !u!114 &1682675647
 MonoBehaviour:
@@ -847,12 +887,13 @@ MonoBehaviour:
     m_LineSpacing: 1
   m_Text: 'This scene demonstrates the problems of using conventional alpha transparency
     for a fadeout effect (left), and how this problem can be fixed by using a RenderTexture
-    (right).
+    (center and right).
 
 
-    Spineboy on the right uses a SkeletonRenderTexture component to
-    render the overlapping mesh to a RenderTexture first and then draw this texture
-    to the scene at once using a single quad.'
+    The two Spineboys on the right use SkeletonRenderTexture
+    and SkeletonGraphicRenderTexture components to render the overlapping mesh to
+    a RenderTexture first and then draw this texture to the scene at once using a
+    single quad.'
 --- !u!222 &1682675648
 CanvasRenderer:
   m_ObjectHideFlags: 0
@@ -861,6 +902,87 @@ CanvasRenderer:
   m_PrefabAsset: {fileID: 0}
   m_GameObject: {fileID: 1682675645}
   m_CullTransparentMesh: 1
+--- !u!1 &1735507357
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1735507358}
+  - component: {fileID: 1735507360}
+  - component: {fileID: 1735507359}
+  m_Layer: 5
+  m_Name: SkeletonGraphic Notes
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &1735507358
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1735507357}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 592567554}
+  m_RootOrder: 4
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0.6932292, y: 0}
+  m_AnchorMax: {x: 1, y: 0.18750001}
+  m_AnchoredPosition: {x: 6.5009766, y: -2.5753784}
+  m_SizeDelta: {x: -49.462, y: 1.8482}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1735507359
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1735507357}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_RaycastTarget: 1
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_FontData:
+    m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+    m_FontSize: 26
+    m_FontStyle: 0
+    m_BestFit: 0
+    m_MinSize: 2
+    m_MaxSize: 40
+    m_Alignment: 2
+    m_AlignByGeometry: 0
+    m_RichText: 1
+    m_HorizontalOverflow: 0
+    m_VerticalOverflow: 1
+    m_LineSpacing: 1
+  m_Text: SkeletonGraphicRenderTexture supports setting a RectTransform as 'Custom
+    Render Rect' to define render texture bounds different from the SkeletonGraphic
+    rect.
+--- !u!222 &1735507360
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1735507357}
+  m_CullTransparentMesh: 1
 --- !u!1 &1786065613
 GameObject:
   m_ObjectHideFlags: 0
@@ -895,11 +1017,11 @@ MonoBehaviour:
   m_Name: 
   m_EditorClassIdentifier: 
   color: {r: 1, g: 1, b: 1, a: 1}
-  quadMaterial: {fileID: 2100000, guid: 4c507f887c6274a44a603d96e0eabf2a, type: 2}
-  targetCamera: {fileID: 0}
   maxRenderTextureSize: 1024
   quad: {fileID: 0}
   renderTexture: {fileID: 0}
+  targetCamera: {fileID: 0}
+  quadMaterial: {fileID: 2100000, guid: 4c507f887c6274a44a603d96e0eabf2a, type: 2}
 --- !u!114 &1786065615
 MonoBehaviour:
   m_ObjectHideFlags: 0
@@ -935,6 +1057,8 @@ MonoBehaviour:
     materialsInsideMask: []
     materialsOutsideMask: []
   disableRenderingOnOverride: 1
+  updateTiming: 1
+  unscaledTime: 0
   _animationName: run
   loop: 1
   timeScale: 1
@@ -995,11 +1119,11 @@ Transform:
   m_PrefabAsset: {fileID: 0}
   m_GameObject: {fileID: 1786065613}
   m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
-  m_LocalPosition: {x: 3.32, y: 0, z: 5.66}
+  m_LocalPosition: {x: 2.38, y: 0, z: 5.66}
   m_LocalScale: {x: 1, y: 1, z: 1}
   m_Children: []
   m_Father: {fileID: 0}
-  m_RootOrder: 3
+  m_RootOrder: 4
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
 --- !u!114 &1786065619
 MonoBehaviour:
@@ -1045,3 +1169,136 @@ Transform:
   m_Father: {fileID: 0}
   m_RootOrder: 6
   m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1911967439
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1911967440}
+  - component: {fileID: 1911967442}
+  - component: {fileID: 1911967441}
+  - component: {fileID: 1911967444}
+  - component: {fileID: 1911967443}
+  m_Layer: 0
+  m_Name: SkeletonGraphic (spineboy-pro)
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &1911967440
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1911967439}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 0.3, y: 0.3, z: 0.3}
+  m_Children:
+  - {fileID: 1089682727}
+  m_Father: {fileID: 592567554}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0.5, y: 0.5}
+  m_AnchorMax: {x: 1, y: 0.5}
+  m_AnchoredPosition: {x: 84, y: -317.51}
+  m_SizeDelta: {x: 1185.6, y: 1302.059}
+  m_Pivot: {x: 0.63858336, y: 0.010301443}
+--- !u!114 &1911967441
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1911967439}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: d85b887af7e6c3f45a2e2d2920d641bc, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 2100000, guid: b66cf7a186d13054989b33a5c90044e4, type: 2}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_RaycastTarget: 1
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  skeletonDataAsset: {fileID: 11400000, guid: af38a3de26ed9b84abc2fe7c7f3b209d, type: 2}
+  additiveMaterial: {fileID: 2100000, guid: 2e8245019faeb8c43b75f9ca3ac8ee34, type: 2}
+  multiplyMaterial: {fileID: 2100000, guid: e74a1f8978a7da348a721508d0d58834, type: 2}
+  screenMaterial: {fileID: 2100000, guid: bab24c479f34eec45be6ea8595891569, type: 2}
+  initialSkinName: default
+  initialFlipX: 0
+  initialFlipY: 0
+  startingAnimation: run
+  startingLoop: 1
+  timeScale: 1
+  freeze: 0
+  updateWhenInvisible: 3
+  allowMultipleCanvasRenderers: 0
+  canvasRenderers: []
+  separatorSlotNames: []
+  enableSeparatorSlots: 0
+  separatorParts: []
+  updateSeparatorPartLocation: 1
+  disableMeshAssignmentOnOverride: 1
+  meshGenerator:
+    settings:
+      useClipping: 1
+      zSpacing: 0
+      pmaVertexColors: 1
+      tintBlack: 0
+      canvasGroupTintBlack: 0
+      calculateTangents: 0
+      addNormals: 0
+      immutableTriangles: 0
+  updateTiming: 1
+  unscaledTime: 0
+--- !u!222 &1911967442
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1911967439}
+  m_CullTransparentMesh: 0
+--- !u!114 &1911967443
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1911967439}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 5fc94f89310427643babb41e000a8462, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  fadeoutSeconds: 2
+--- !u!114 &1911967444
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1911967439}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 6cbe1f11426513d49ad8e21e9d6643f7, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  color: {r: 1, g: 1, b: 1, a: 1}
+  maxRenderTextureSize: 1024
+  quad: {fileID: 0}
+  renderTexture: {fileID: 0}
+  targetCamera: {fileID: 0}
+  customRenderRect: {fileID: 1089682727}
+  meshRendererMaterialForTexture:
+  - texture: {fileID: 2800000, guid: 4ea2c33e839afb34c98f66e892b3b2d2, type: 3}
+    material: {fileID: 2100000, guid: f89bbf05902e77242a3ad20f3c927353, type: 2}

+ 9 - 7
spine-unity/Assets/Spine Examples/Scripts/RenderTextureFadeoutExample.cs

@@ -39,6 +39,7 @@ namespace Spine.Unity.Examples {
 	public class RenderTextureFadeoutExample : MonoBehaviour {
 
 		public SkeletonRenderTextureFadeout renderTextureFadeout;
+		public SkeletonRenderTextureFadeout renderTextureFadeoutCanvas;
 		public SkeletonRenderer normalSkeletonRenderer;
 
 		float fadeoutSeconds = 2.0f;
@@ -47,8 +48,9 @@ namespace Spine.Unity.Examples {
 		IEnumerator Start () {
 			while (true) {
 				StartFadeoutBad();
-				StartFadeoutGood();
-				yield return new WaitForSeconds(5.0f);
+				StartFadeoutGood(renderTextureFadeout);
+				StartFadeoutGood(renderTextureFadeoutCanvas);
+				yield return new WaitForSeconds(fadeoutSeconds + 1.0f);
 			}
 		}
 		void Update () {
@@ -75,12 +77,12 @@ namespace Spine.Unity.Examples {
 			fadeoutSecondsRemaining = fadeoutSeconds;
 		}
 
-		void StartFadeoutGood () {
-			renderTextureFadeout.gameObject.SetActive(true);
+		void StartFadeoutGood (SkeletonRenderTextureFadeout fadeoutComponent) {
+			fadeoutComponent.gameObject.SetActive(true);
 			// enabling the SkeletonRenderTextureFadeout component starts the fadeout.
-			renderTextureFadeout.enabled = true;
-			renderTextureFadeout.OnFadeoutComplete -= DisableGameObject;
-			renderTextureFadeout.OnFadeoutComplete += DisableGameObject;
+			fadeoutComponent.enabled = true;
+			fadeoutComponent.OnFadeoutComplete -= DisableGameObject;
+			fadeoutComponent.OnFadeoutComplete += DisableGameObject;
 		}
 
 		void DisableGameObject (SkeletonRenderTextureFadeout target) {

+ 259 - 0
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonRenderTexture/SkeletonGraphicRenderTexture.cs

@@ -0,0 +1,259 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2022, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#if UNITY_2017_2_OR_NEWER
+#define HAS_VECTOR2INT
+#endif
+
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.Rendering;
+using UnityEngine.UI;
+
+namespace Spine.Unity.Examples {
+
+	/// <summary>
+	/// When enabled, this component renders a skeleton to a RenderTexture and
+	/// then draws this RenderTexture at a UI RawImage quad of the same size.
+	/// This allows changing transparency at a single quad, which produces a more
+	/// natural fadeout effect.
+	/// Note: It is recommended to keep this component disabled as much as possible
+	/// because of the additional rendering overhead. Only enable it when alpha blending is required.
+	/// </summary>
+	[RequireComponent(typeof(SkeletonGraphic))]
+	public class SkeletonGraphicRenderTexture : SkeletonRenderTextureBase {
+#if HAS_VECTOR2INT
+		[System.Serializable]
+		public struct TextureMaterialPair {
+			public Texture texture;
+			public Material material;
+
+			public TextureMaterialPair (Texture texture, Material material) {
+				this.texture = texture;
+				this.material = material;
+			}
+		}
+
+		public RectTransform customRenderRect;
+		protected SkeletonGraphic skeletonGraphic;
+		public List<TextureMaterialPair> meshRendererMaterialForTexture = new List<TextureMaterialPair>();
+		protected CanvasRenderer quadCanvasRenderer;
+		protected RawImage quadRawImage;
+		protected readonly Vector3[] worldCorners = new Vector3[4];
+
+		protected override void Awake () {
+			base.Awake();
+			skeletonGraphic = this.GetComponent<SkeletonGraphic>();
+			if (targetCamera == null) {
+				targetCamera = skeletonGraphic.canvas.worldCamera;
+				if (targetCamera == null)
+					targetCamera = Camera.main;
+			}
+			CreateQuadChild();
+		}
+
+		void CreateQuadChild () {
+			quad = new GameObject(this.name + " RenderTexture", typeof(CanvasRenderer), typeof(RawImage));
+			quad.transform.SetParent(this.transform.parent, false);
+			quadCanvasRenderer = quad.GetComponent<CanvasRenderer>();
+			quadRawImage = quad.GetComponent<RawImage>();
+
+			quadMesh = new Mesh();
+			quadMesh.MarkDynamic();
+			quadMesh.name = "RenderTexture Quad";
+			quadMesh.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
+		}
+
+		void Reset () {
+			skeletonGraphic = this.GetComponent<SkeletonGraphic>();
+			AtlasAssetBase[] atlasAssets = skeletonGraphic.SkeletonDataAsset.atlasAssets;
+			for (int i = 0; i < atlasAssets.Length; ++i) {
+				foreach (var material in atlasAssets[i].Materials) {
+					if (material.mainTexture != null) {
+						meshRendererMaterialForTexture.Add(
+							new TextureMaterialPair(material.mainTexture, material));
+					}
+				}
+			}
+		}
+
+		void OnEnable () {
+			skeletonGraphic.OnInstructionsPrepared += PrepareQuad;
+			skeletonGraphic.AssignMeshOverrideSingleRenderer += RenderSingleMeshToRenderTexture;
+			skeletonGraphic.AssignMeshOverrideMultipleRenderers += RenderMultipleMeshesToRenderTexture;
+			skeletonGraphic.disableMeshAssignmentOnOverride = true;
+			skeletonGraphic.OnMeshAndMaterialsUpdated += RenderOntoQuad;
+			var canvasRenderers = skeletonGraphic.canvasRenderers;
+			for (int i = 0; i < canvasRenderers.Count; ++i)
+				canvasRenderers[i].cull = true;
+
+			if (quadCanvasRenderer)
+				quadCanvasRenderer.gameObject.SetActive(true);
+		}
+
+		void OnDisable () {
+			skeletonGraphic.OnInstructionsPrepared -= PrepareQuad;
+			skeletonGraphic.AssignMeshOverrideSingleRenderer -= RenderSingleMeshToRenderTexture;
+			skeletonGraphic.AssignMeshOverrideMultipleRenderers -= RenderMultipleMeshesToRenderTexture;
+			skeletonGraphic.disableMeshAssignmentOnOverride = false;
+			skeletonGraphic.OnMeshAndMaterialsUpdated -= RenderOntoQuad;
+			var canvasRenderers = skeletonGraphic.canvasRenderers;
+			for (int i = 0; i < canvasRenderers.Count; ++i)
+				canvasRenderers[i].cull = false;
+
+			if (quadCanvasRenderer)
+				quadCanvasRenderer.gameObject.SetActive(false);
+			if (renderTexture)
+				RenderTexture.ReleaseTemporary(renderTexture);
+			allocatedRenderTextureSize = Vector2Int.zero;
+		}
+
+		void PrepareQuad (SkeletonRendererInstruction instruction) {
+			PrepareForMesh();
+			SetupQuad();
+		}
+
+		void RenderOntoQuad (SkeletonGraphic skeletonRenderer) {
+			AssignAtQuad();
+		}
+
+		protected void PrepareForMesh () {
+			// We need to get the min/max of all four corners, rotation of the skeleton
+			// in combination with perspective projection otherwise might lead to incorrect
+			// screen space min/max.
+			RectTransform rectTransform = customRenderRect ? customRenderRect : skeletonGraphic.rectTransform;
+			rectTransform.GetWorldCorners(worldCorners);
+
+			RenderMode canvasRenderMode = skeletonGraphic.canvas.renderMode;
+			Vector3 screenCorner0, screenCorner1, screenCorner2, screenCorner3;
+			// note: world corners are ordered bottom left, top left, top right, bottom right.
+			// This corresponds to 0, 3, 1, 2 in our desired order.
+			if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
+				screenCorner0 = worldCorners[0];
+				screenCorner1 = worldCorners[3];
+				screenCorner2 = worldCorners[1];
+				screenCorner3 = worldCorners[2];
+			} else {
+				screenCorner0 = targetCamera.WorldToScreenPoint(worldCorners[0]);
+				screenCorner1 = targetCamera.WorldToScreenPoint(worldCorners[3]);
+				screenCorner2 = targetCamera.WorldToScreenPoint(worldCorners[1]);
+				screenCorner3 = targetCamera.WorldToScreenPoint(worldCorners[2]);
+			}
+
+			// To avoid perspective distortion when rotated, we project all vertices
+			// onto a plane parallel to the view frustum near plane.
+			// Avoids the requirement of 'noperspective' vertex attribute interpolation modifier in shaders.
+			float averageScreenDepth = (screenCorner0.z + screenCorner1.z + screenCorner2.z + screenCorner3.z) / 4.0f;
+			screenCorner0.z = screenCorner1.z = screenCorner2.z = screenCorner3.z = averageScreenDepth;
+
+			if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
+				worldCornerNoDistortion0 = screenCorner0;
+				worldCornerNoDistortion1 = screenCorner1;
+				worldCornerNoDistortion2 = screenCorner2;
+				worldCornerNoDistortion3 = screenCorner3;
+			} else {
+				worldCornerNoDistortion0 = targetCamera.ScreenToWorldPoint(screenCorner0);
+				worldCornerNoDistortion1 = targetCamera.ScreenToWorldPoint(screenCorner1);
+				worldCornerNoDistortion2 = targetCamera.ScreenToWorldPoint(screenCorner2);
+				worldCornerNoDistortion3 = targetCamera.ScreenToWorldPoint(screenCorner3);
+			}
+			Vector3 screenSpaceMin, screenSpaceMax;
+			PrepareTextureMapping(out screenSpaceMin, out screenSpaceMax,
+				screenCorner0, screenCorner1, screenCorner2, screenCorner3);
+			PrepareCommandBuffer(targetCamera, screenSpaceMin, screenSpaceMax);
+		}
+
+		protected Material MeshRendererMaterialForTexture (Texture texture) {
+			return meshRendererMaterialForTexture.Find(x => x.texture == texture).material;
+		}
+
+		protected void RenderSingleMeshToRenderTexture (Mesh mesh, Material graphicMaterial, Texture texture) {
+			Material meshRendererMaterial = MeshRendererMaterialForTexture(texture);
+			commandBuffer.DrawMesh(mesh, transform.localToWorldMatrix, meshRendererMaterial, 0, -1);
+			Graphics.ExecuteCommandBuffer(commandBuffer);
+		}
+
+		protected void RenderMultipleMeshesToRenderTexture (int meshCount,
+			Mesh[] meshes, Material[] graphicMaterials, Texture[] textures) {
+
+			for (int i = 0; i < meshCount; ++i) {
+				Material meshRendererMaterial = MeshRendererMaterialForTexture(textures[i]);
+				commandBuffer.DrawMesh(meshes[i], transform.localToWorldMatrix, meshRendererMaterial, 0, -1);
+			}
+			Graphics.ExecuteCommandBuffer(commandBuffer);
+		}
+
+		protected void SetupQuad () {
+			quadRawImage.texture = this.renderTexture;
+			quadRawImage.color = color;
+			quadCanvasRenderer.SetColor(color);
+
+			var srcRectTransform = skeletonGraphic.rectTransform;
+			var dstRectTransform = quadRawImage.rectTransform;
+
+			dstRectTransform.anchorMin = srcRectTransform.anchorMin;
+			dstRectTransform.anchorMax = srcRectTransform.anchorMax;
+			dstRectTransform.anchoredPosition = srcRectTransform.anchoredPosition;
+			dstRectTransform.pivot = srcRectTransform.pivot;
+			dstRectTransform.localScale = srcRectTransform.localScale;
+			dstRectTransform.sizeDelta = srcRectTransform.sizeDelta;
+			dstRectTransform.rotation = srcRectTransform.rotation;
+		}
+
+		protected void PrepareCommandBuffer (Camera targetCamera, Vector3 screenSpaceMin, Vector3 screenSpaceMax) {
+			commandBuffer.Clear();
+			commandBuffer.SetRenderTarget(renderTexture);
+			commandBuffer.ClearRenderTarget(true, true, Color.clear);
+
+			Rect canvasRect = skeletonGraphic.canvas.pixelRect;
+
+			Matrix4x4 projectionMatrix = Matrix4x4.Ortho(
+				canvasRect.x, canvasRect.x + canvasRect.width,
+				canvasRect.y, canvasRect.y + canvasRect.height,
+				float.MinValue, float.MaxValue);
+
+			RenderMode canvasRenderMode = skeletonGraphic.canvas.renderMode;
+			if (canvasRenderMode == RenderMode.ScreenSpaceOverlay) {
+				commandBuffer.SetViewMatrix(Matrix4x4.identity);
+				commandBuffer.SetProjectionMatrix(projectionMatrix);
+			} else {
+				commandBuffer.SetViewMatrix(targetCamera.worldToCameraMatrix);
+				commandBuffer.SetProjectionMatrix(targetCamera.projectionMatrix);
+			}
+
+			Vector2 targetCameraViewportSize = targetCamera.pixelRect.size;
+			commandBuffer.SetViewport(new Rect(-screenSpaceMin, targetCameraViewportSize));
+		}
+
+		protected override void AssignMeshAtRenderer () {
+			quadCanvasRenderer.SetMesh(quadMesh);
+		}
+#endif // HAS_VECTOR2INT
+	}
+}

+ 11 - 0
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonRenderTexture/SkeletonGraphicRenderTexture.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6cbe1f11426513d49ad8e21e9d6643f7
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 4 - 74
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonRenderTexture/SkeletonRenderTexture.cs

@@ -35,7 +35,6 @@
 #define HAS_GET_SHARED_MATERIALS
 #endif
 
-using System;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine.Rendering;
@@ -54,20 +53,11 @@ namespace Spine.Unity.Examples {
 	public class SkeletonRenderTexture : SkeletonRenderTextureBase {
 #if HAS_GET_SHARED_MATERIALS
 		public Material quadMaterial;
-		public Camera targetCamera;
 		protected SkeletonRenderer skeletonRenderer;
 		protected MeshRenderer meshRenderer;
 		protected MeshFilter meshFilter;
 		protected MeshRenderer quadMeshRenderer;
 		protected MeshFilter quadMeshFilter;
-		protected Vector3 worldCornerNoDistortion0;
-		protected Vector3 worldCornerNoDistortion1;
-		protected Vector3 worldCornerNoDistortion2;
-		protected Vector3 worldCornerNoDistortion3;
-		protected Vector2 uvCorner0;
-		protected Vector2 uvCorner1;
-		protected Vector2 uvCorner2;
-		protected Vector2 uvCorner3;
 
 		private MaterialPropertyBlock propertyBlock;
 		private readonly List<Material> materials = new List<Material>();
@@ -160,40 +150,13 @@ namespace Spine.Unity.Examples {
 			worldCornerNoDistortion2 = targetCamera.ScreenToWorldPoint(screenCorner2);
 			worldCornerNoDistortion3 = targetCamera.ScreenToWorldPoint(screenCorner3);
 
-			Vector3 screenSpaceMin =
-				Vector3.Min(screenCorner0, Vector3.Min(screenCorner1,
-				Vector3.Min(screenCorner2, screenCorner3)));
-			Vector3 screenSpaceMax =
-				Vector3.Max(screenCorner0, Vector3.Max(screenCorner1,
-				Vector3.Max(screenCorner2, screenCorner3)));
-			// ensure we are on whole pixel borders
-			screenSpaceMin.x = Mathf.Floor(screenSpaceMin.x);
-			screenSpaceMin.y = Mathf.Floor(screenSpaceMin.y);
-			screenSpaceMax.x = Mathf.Ceil(screenSpaceMax.x);
-			screenSpaceMax.y = Mathf.Ceil(screenSpaceMax.y);
-
-			// inverse-map screenCornerN to screenSpaceMin/screenSpaceMax area to get UV coordinates
-			uvCorner0 = InverseLerp(screenSpaceMin, screenSpaceMax, screenCorner0);
-			uvCorner1 = InverseLerp(screenSpaceMin, screenSpaceMax, screenCorner1);
-			uvCorner2 = InverseLerp(screenSpaceMin, screenSpaceMax, screenCorner2);
-			uvCorner3 = InverseLerp(screenSpaceMin, screenSpaceMax, screenCorner3);
-
-			requiredRenderTextureSize = new Vector2Int(
-				Math.Min(maxRenderTextureSize, Math.Abs((int)screenSpaceMax.x - (int)screenSpaceMin.x)),
-				Math.Min(maxRenderTextureSize, Math.Abs((int)screenSpaceMax.y - (int)screenSpaceMin.y)));
-
-			PrepareRenderTexture();
+			Vector3 screenSpaceMin, screenSpaceMax;
+			PrepareTextureMapping(out screenSpaceMin, out screenSpaceMax,
+				screenCorner0, screenCorner1, screenCorner2, screenCorner3);
 			PrepareCommandBuffer(targetCamera, screenSpaceMin, screenSpaceMax);
 		}
 
-		protected Vector2 InverseLerp (Vector2 a, Vector2 b, Vector2 value) {
-			return new Vector2(
-				(value.x - a.x) / (b.x - a.x),
-				(value.y - a.y) / (b.y - a.y));
-		}
-
 		protected void PrepareCommandBuffer (Camera targetCamera, Vector3 screenSpaceMin, Vector3 screenSpaceMax) {
-
 			commandBuffer.Clear();
 			commandBuffer.SetRenderTarget(renderTexture);
 			commandBuffer.ClearRenderTarget(true, true, Color.clear);
@@ -214,40 +177,7 @@ namespace Spine.Unity.Examples {
 			Graphics.ExecuteCommandBuffer(commandBuffer);
 		}
 
-		protected void AssignAtQuad () {
-			Transform quadTransform = quadMeshRenderer.transform;
-			quadTransform.position = this.transform.position;
-			quadTransform.rotation = this.transform.rotation;
-			quadTransform.localScale = this.transform.localScale;
-
-			Vector3 v0 = quadTransform.InverseTransformPoint(worldCornerNoDistortion0);
-			Vector3 v1 = quadTransform.InverseTransformPoint(worldCornerNoDistortion1);
-			Vector3 v2 = quadTransform.InverseTransformPoint(worldCornerNoDistortion2);
-			Vector3 v3 = quadTransform.InverseTransformPoint(worldCornerNoDistortion3);
-			Vector3[] vertices = new Vector3[4] { v0, v1, v2, v3 };
-
-			quadMesh.vertices = vertices;
-
-			int[] indices = new int[6] { 0, 2, 1, 2, 3, 1 };
-			quadMesh.triangles = indices;
-
-			Vector3[] normals = new Vector3[4] {
-				-Vector3.forward,
-				-Vector3.forward,
-				-Vector3.forward,
-				-Vector3.forward
-			};
-			quadMesh.normals = normals;
-
-			float maxU = (float)requiredRenderTextureSize.x / (float)allocatedRenderTextureSize.x;
-			float maxV = (float)requiredRenderTextureSize.y / (float)allocatedRenderTextureSize.y;
-			Vector2[] uv = new Vector2[4] {
-				new Vector2(uvCorner0.x * maxU, uvCorner0.y * maxV),
-				new Vector2(uvCorner1.x * maxU, uvCorner1.y * maxV),
-				new Vector2(uvCorner2.x * maxU, uvCorner2.y * maxV),
-				new Vector2(uvCorner3.x * maxU, uvCorner3.y * maxV),
-			};
-			quadMesh.uv = uv;
+		protected override void AssignMeshAtRenderer () {
 			quadMeshFilter.mesh = quadMesh;
 			quadMeshRenderer.sharedMaterial.mainTexture = this.renderTexture;
 			quadMeshRenderer.sharedMaterial.color = color;

+ 82 - 8
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonRenderTexture/SkeletonRenderTextureBase.cs

@@ -27,31 +27,38 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-#if UNITY_2019_3_OR_NEWER
-#define HAS_FORCE_RENDER_OFF
-#endif
-
-#if UNITY_2018_2_OR_NEWER
-#define HAS_GET_SHARED_MATERIALS
+#if UNITY_2017_2_OR_NEWER
+#define HAS_VECTOR2INT
 #endif
 
+using System;
 using UnityEngine;
 using UnityEngine.Rendering;
 
 namespace Spine.Unity.Examples {
 
 	public abstract class SkeletonRenderTextureBase : MonoBehaviour {
-#if HAS_GET_SHARED_MATERIALS
+#if HAS_VECTOR2INT
 		public Color color = Color.white;
 		public int maxRenderTextureSize = 1024;
 		public GameObject quad;
 		protected Mesh quadMesh;
 		public RenderTexture renderTexture;
+		public Camera targetCamera;
 
 		protected CommandBuffer commandBuffer;
 		protected Vector2Int requiredRenderTextureSize;
 		protected Vector2Int allocatedRenderTextureSize;
 
+		protected Vector3 worldCornerNoDistortion0;
+		protected Vector3 worldCornerNoDistortion1;
+		protected Vector3 worldCornerNoDistortion2;
+		protected Vector3 worldCornerNoDistortion3;
+		protected Vector2 uvCorner0;
+		protected Vector2 uvCorner1;
+		protected Vector2 uvCorner2;
+		protected Vector2 uvCorner3;
+
 		protected virtual void Awake () {
 			commandBuffer = new CommandBuffer();
 		}
@@ -61,6 +68,34 @@ namespace Spine.Unity.Examples {
 				RenderTexture.ReleaseTemporary(renderTexture);
 		}
 
+		protected void PrepareTextureMapping (out Vector3 screenSpaceMin, out Vector3 screenSpaceMax,
+			Vector3 screenCorner0, Vector3 screenCorner1, Vector3 screenCorner2, Vector3 screenCorner3) {
+
+			screenSpaceMin =
+				Vector3.Min(screenCorner0, Vector3.Min(screenCorner1,
+				Vector3.Min(screenCorner2, screenCorner3)));
+			screenSpaceMax =
+				Vector3.Max(screenCorner0, Vector3.Max(screenCorner1,
+				Vector3.Max(screenCorner2, screenCorner3)));
+			// ensure we are on whole pixel borders
+			screenSpaceMin.x = Mathf.Floor(screenSpaceMin.x);
+			screenSpaceMin.y = Mathf.Floor(screenSpaceMin.y);
+			screenSpaceMax.x = Mathf.Ceil(screenSpaceMax.x);
+			screenSpaceMax.y = Mathf.Ceil(screenSpaceMax.y);
+
+			// inverse-map screenCornerN to screenSpaceMin/screenSpaceMax area to get UV coordinates
+			uvCorner0 = MathUtilities.InverseLerp(screenSpaceMin, screenSpaceMax, screenCorner0);
+			uvCorner1 = MathUtilities.InverseLerp(screenSpaceMin, screenSpaceMax, screenCorner1);
+			uvCorner2 = MathUtilities.InverseLerp(screenSpaceMin, screenSpaceMax, screenCorner2);
+			uvCorner3 = MathUtilities.InverseLerp(screenSpaceMin, screenSpaceMax, screenCorner3);
+
+			requiredRenderTextureSize = new Vector2Int(
+				Math.Min(maxRenderTextureSize, Math.Abs((int)screenSpaceMax.x - (int)screenSpaceMin.x)),
+				Math.Min(maxRenderTextureSize, Math.Abs((int)screenSpaceMax.y - (int)screenSpaceMin.y)));
+
+			PrepareRenderTexture();
+		}
+
 		protected void PrepareRenderTexture () {
 			Vector2Int textureSize = new Vector2Int(
 				Mathf.NextPowerOfTwo(requiredRenderTextureSize.x),
@@ -74,6 +109,45 @@ namespace Spine.Unity.Examples {
 				allocatedRenderTextureSize = textureSize;
 			}
 		}
-#endif
+
+		protected void AssignAtQuad () {
+			Transform quadTransform = quad.transform;
+			quadTransform.position = this.transform.position;
+			quadTransform.rotation = this.transform.rotation;
+			quadTransform.localScale = this.transform.localScale;
+
+			Vector3 v0 = quadTransform.InverseTransformPoint(worldCornerNoDistortion0);
+			Vector3 v1 = quadTransform.InverseTransformPoint(worldCornerNoDistortion1);
+			Vector3 v2 = quadTransform.InverseTransformPoint(worldCornerNoDistortion2);
+			Vector3 v3 = quadTransform.InverseTransformPoint(worldCornerNoDistortion3);
+			Vector3[] vertices = new Vector3[4] { v0, v1, v2, v3 };
+
+			quadMesh.vertices = vertices;
+
+			int[] indices = new int[6] { 0, 1, 2, 2, 1, 3 };
+			quadMesh.triangles = indices;
+
+			Vector3[] normals = new Vector3[4] {
+				-Vector3.forward,
+				-Vector3.forward,
+				-Vector3.forward,
+				-Vector3.forward
+			};
+			quadMesh.normals = normals;
+
+			float maxU = (float)requiredRenderTextureSize.x / (float)allocatedRenderTextureSize.x;
+			float maxV = (float)requiredRenderTextureSize.y / (float)allocatedRenderTextureSize.y;
+			Vector2[] uv = new Vector2[4] {
+				new Vector2(uvCorner0.x * maxU, uvCorner0.y * maxV),
+				new Vector2(uvCorner1.x * maxU, uvCorner1.y * maxV),
+				new Vector2(uvCorner2.x * maxU, uvCorner2.y * maxV),
+				new Vector2(uvCorner3.x * maxU, uvCorner3.y * maxV),
+			};
+			quadMesh.uv = uv;
+			AssignMeshAtRenderer();
+		}
+
+		protected abstract void AssignMeshAtRenderer ();
+#endif // HAS_VECTOR2INT
 	}
 }

+ 11 - 0
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonRenderTexture/SkeletonRenderTextureBase.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ccf9b5e5034b0ea45962f9cf32168dd9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 156 - 45
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs

@@ -183,6 +183,59 @@ namespace Spine.Unity {
 		#endregion
 
 		#region Overrides
+		// API for taking over rendering.
+		/// <summary>When true, no meshes and materials are assigned at CanvasRenderers if the used override
+		/// AssignMeshOverrideSingleRenderer or AssignMeshOverrideMultipleRenderers is non-null.</summary>
+		public bool disableMeshAssignmentOnOverride = true;
+		/// <summary>Delegate type for overriding mesh and material assignment,
+		/// used when <c>allowMultipleCanvasRenderers</c> is false.</summary>
+		/// <param name="mesh">Mesh normally assigned at the main CanvasRenderer.</param>
+		/// <param name="graphicMaterial">Material normally assigned at the main CanvasRenderer.</param>
+		/// <param name="texture">Texture normally assigned at the main CanvasRenderer.</param>
+		public delegate void MeshAssignmentDelegateSingle (Mesh mesh, Material graphicMaterial, Texture texture);
+		/// <param name="meshCount">Number of meshes. Don't use <c>meshes.Length</c> as this might be higher
+		/// due to pre-allocated entries.</param>
+		/// <param name="meshes">Mesh array where each element is normally assigned to one of the <c>canvasRenderers</c>.</param>
+		/// <param name="graphicMaterials">Material array where each element is normally assigned to one of the <c>canvasRenderers</c>.</param>
+		/// <param name="textures">Texture array where each element is normally assigned to one of the <c>canvasRenderers</c>.</param>
+		public delegate void MeshAssignmentDelegateMultiple (int meshCount, Mesh[] meshes, Material[] graphicMaterials, Texture[] textures);
+		event MeshAssignmentDelegateSingle assignMeshOverrideSingle;
+		event MeshAssignmentDelegateMultiple assignMeshOverrideMultiple;
+
+		/// <summary>Allows separate code to take over mesh and material assignment for this SkeletonGraphic component.
+		/// Used when <c>allowMultipleCanvasRenderers</c> is false.</summary>
+		public event MeshAssignmentDelegateSingle AssignMeshOverrideSingleRenderer {
+			add {
+				assignMeshOverrideSingle += value;
+				if (disableMeshAssignmentOnOverride && assignMeshOverrideSingle != null) {
+					Initialize(false);
+				}
+			}
+			remove {
+				assignMeshOverrideSingle -= value;
+				if (disableMeshAssignmentOnOverride && assignMeshOverrideSingle == null) {
+					Initialize(false);
+				}
+			}
+		}
+		/// <summary>Allows separate code to take over mesh and material assignment for this SkeletonGraphic component.
+		/// Used when <c>allowMultipleCanvasRenderers</c> is true.</summary>
+		public event MeshAssignmentDelegateMultiple AssignMeshOverrideMultipleRenderers {
+			add {
+				assignMeshOverrideMultiple += value;
+				if (disableMeshAssignmentOnOverride && assignMeshOverrideMultiple != null) {
+					Initialize(false);
+				}
+			}
+			remove {
+				assignMeshOverrideMultiple -= value;
+				if (disableMeshAssignmentOnOverride && assignMeshOverrideMultiple == null) {
+					Initialize(false);
+				}
+			}
+		}
+
+
 		[System.NonSerialized] readonly Dictionary<Texture, Texture> customTextureOverride = new Dictionary<Texture, Texture>();
 		/// <summary>Use this Dictionary to override a Texture with a different Texture.</summary>
 		public Dictionary<Texture, Texture> CustomTextureOverride { get { return customTextureOverride; } }
@@ -334,6 +387,8 @@ namespace Spine.Unity {
 			if (updateMode != UpdateMode.FullUpdate) return;
 
 			PrepareInstructionsAndRenderers();
+			if (OnInstructionsPrepared != null)
+				OnInstructionsPrepared(this.currentInstructions);
 			SetVerticesDirty(); // triggers Rebuild and avoids potential double-update in a single frame
 		}
 
@@ -390,12 +445,19 @@ namespace Spine.Unity {
 		public bool IsValid { get { return skeleton != null; } }
 
 		public delegate void SkeletonRendererDelegate (SkeletonGraphic skeletonGraphic);
+		public delegate void InstructionDelegate (SkeletonRendererInstruction instruction);
 
 		/// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
 		public event SkeletonRendererDelegate OnRebuild;
 
-		/// <summary>OnMeshAndMaterialsUpdated is at the end of LateUpdate after the Mesh and
-		/// all materials have been updated.</summary>
+		/// <summary>OnInstructionsPrepared is raised at the end of <c>LateUpdate</c> after render instructions
+		/// are done, target renderers are prepared, and the mesh is ready to be generated.</summary>
+		public event InstructionDelegate OnInstructionsPrepared;
+
+		/// <summary>OnMeshAndMaterialsUpdated is raised at the end of <c>Rebuild</c> after the Mesh and
+		/// all materials have been updated. Note that some Unity API calls are not permitted to be issued from
+		/// <c>Rebuild</c>, so you may want to subscribe to <see cref="OnInstructionsPrepared"/> instead
+		/// from where you can issue such preparation calls.</summary>
 		public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
 
 		protected Spine.AnimationState state;
@@ -411,6 +473,12 @@ namespace Spine.Unity {
 		DoubleBuffered<Spine.Unity.MeshRendererBuffers.SmartMesh> meshBuffers;
 		SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
 		readonly ExposedList<Mesh> meshes = new ExposedList<Mesh>();
+		readonly ExposedList<Material> usedMaterials = new ExposedList<Material>();
+		readonly ExposedList<Texture> usedTextures = new ExposedList<Texture>();
+
+		public ExposedList<Mesh> MeshesMultipleCanvasRenderers { get { return meshes; } }
+		public ExposedList<Material> MaterialsMultipleCanvasRenderers { get { return usedMaterials; } }
+		public ExposedList<Texture> TexturesMultipleCanvasRenderers { get { return usedTextures; } }
 
 		public Mesh GetLastMesh () {
 			return meshBuffers.GetCurrent().mesh;
@@ -513,6 +581,8 @@ namespace Spine.Unity {
 			for (int i = 0; i < canvasRenderers.Count; ++i)
 				canvasRenderers[i].Clear();
 			DestroyMeshes();
+			usedMaterials.Clear();
+			usedTextures.Clear();
 			DisposeMeshBuffers();
 		}
 
@@ -601,6 +671,7 @@ namespace Spine.Unity {
 				int submeshCount = currentInstructions.submeshInstructions.Count;
 				EnsureCanvasRendererCount(submeshCount);
 				EnsureMeshesCount(submeshCount);
+				EnsureUsedTexturesAndMaterialsCount(submeshCount);
 				EnsureSeparatorPartCount();
 				PrepareRendererGameObjects(currentInstructions);
 			}
@@ -618,6 +689,7 @@ namespace Spine.Unity {
 			if (!this.allowMultipleCanvasRenderers) {
 				UpdateMeshSingleCanvasRenderer(currentInstructions);
 			} else {
+				UpdateMaterialsMultipleCanvasRenderers(currentInstructions);
 				UpdateMeshMultipleCanvasRenderers(currentInstructions);
 			}
 
@@ -675,19 +747,67 @@ namespace Spine.Unity {
 			if (updateTriangles) meshGenerator.FillTriangles(mesh);
 			meshGenerator.FillLateVertexData(mesh);
 
-			canvasRenderer.SetMesh(mesh);
 			smartMesh.instructionUsed.Set(currentInstructions);
+			if (assignMeshOverrideSingle != null)
+				assignMeshOverrideSingle(mesh, this.canvasRenderer.GetMaterial(), this.mainTexture);
+
+			bool assignAtCanvasRenderer = (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride);
+			if (assignAtCanvasRenderer)
+				canvasRenderer.SetMesh(mesh);
+			else
+				canvasRenderer.SetMesh(null);
 
 			if (currentInstructions.submeshInstructions.Count > 0) {
 				var material = currentInstructions.submeshInstructions.Items[0].material;
 				if (material != null && baseTexture != material.mainTexture) {
 					baseTexture = material.mainTexture;
-					if (overrideTexture == null)
+					if (overrideTexture == null && assignAtCanvasRenderer)
 						canvasRenderer.SetTexture(this.mainTexture);
 				}
 			}
+		}
+
+		protected void UpdateMaterialsMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) {
+			int submeshCount = currentInstructions.submeshInstructions.Count;
+			bool useOriginalTextureAndMaterial = (customMaterialOverride.Count == 0 && customTextureOverride.Count == 0);
+
+			BlendModeMaterials blendModeMaterials = skeletonDataAsset.blendModeMaterials;
+			bool hasBlendModeMaterials = blendModeMaterials.RequiresBlendModeMaterials;
+
+			bool pmaVertexColors = meshGenerator.settings.pmaVertexColors;
+			Material[] usedMaterialItems = usedMaterials.Items;
+			Texture[] usedTextureItems = usedTextures.Items;
+			for (int i = 0; i < submeshCount; i++) {
+				var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
+				var submeshMaterial = submeshInstructionItem.material;
+				if (useOriginalTextureAndMaterial) {
+					usedTextureItems[i] = submeshMaterial.mainTexture;
+					if (!hasBlendModeMaterials) {
+						usedMaterialItems[i] = this.materialForRendering;
+					} else {
+						BlendMode blendMode = blendModeMaterials.BlendModeForMaterial(submeshMaterial);
+						Material usedMaterial = this.materialForRendering;
+						if (blendMode == BlendMode.Additive && !pmaVertexColors && additiveMaterial) {
+							usedMaterial = additiveMaterial;
+						} else if (blendMode == BlendMode.Multiply && multiplyMaterial)
+							usedMaterial = multiplyMaterial;
+						else if (blendMode == BlendMode.Screen && screenMaterial)
+							usedMaterial = screenMaterial;
+						usedMaterialItems[i] = submeshGraphics[i].GetModifiedMaterial(usedMaterial);
+					}
+				} else {
+					var originalTexture = submeshMaterial.mainTexture;
+					Material usedMaterial;
+					Texture usedTexture;
+					if (!customMaterialOverride.TryGetValue(originalTexture, out usedMaterial))
+						usedMaterial = material;
+					if (!customTextureOverride.TryGetValue(originalTexture, out usedTexture))
+						usedTexture = originalTexture;
 
-			//this.UpdateMaterial(); // note: This would allocate memory.
+					usedMaterialItems[i] = submeshGraphics[i].GetModifiedMaterial(usedMaterial);
+					usedTextureItems[i] = usedTexture;
+				}
+			}
 		}
 
 		protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) {
@@ -705,6 +825,9 @@ namespace Spine.Unity {
 			bool mainCullTransparentMesh = this.canvasRenderer.cullTransparentMesh;
 #endif
 			bool pmaVertexColors = meshGenerator.settings.pmaVertexColors;
+			Material[] usedMaterialItems = usedMaterials.Items;
+			Texture[] usedTextureItems = usedTextures.Items;
+			bool assignAtCanvasRenderer = (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride);
 			for (int i = 0; i < submeshCount; i++) {
 				var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
 				meshGenerator.Begin();
@@ -717,53 +840,31 @@ namespace Spine.Unity {
 				meshGenerator.FillTriangles(targetMesh);
 				meshGenerator.FillLateVertexData(targetMesh);
 
-				var submeshMaterial = submeshInstructionItem.material;
 				var canvasRenderer = canvasRenderers[i];
-				canvasRenderer.SetMesh(targetMesh);
-				canvasRenderer.materialCount = 1;
+				if (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride)
+					canvasRenderer.SetMesh(targetMesh);
+				else
+					canvasRenderer.SetMesh(null);
 
 				SkeletonSubmeshGraphic submeshGraphic = submeshGraphics[i];
-				if (useOriginalTextureAndMaterial) {
-					Texture usedTexture = submeshMaterial.mainTexture;
-					if (!hasBlendModeMaterials)
-						canvasRenderer.SetMaterial(this.materialForRendering, usedTexture);
-					else {
-						bool allowCullTransparentMesh = true;
-						BlendMode blendMode = blendModeMaterials.BlendModeForMaterial(submeshMaterial);
-						Material usedMaterial = this.materialForRendering;
-						if (blendMode == BlendMode.Normal) {
-							if (submeshInstructionItem.hasPMAAdditiveSlot)
-								allowCullTransparentMesh = false;
-						} else if (blendMode == BlendMode.Additive) {
-							if (pmaVertexColors)
-								allowCullTransparentMesh = false;
-							else if (additiveMaterial)
-								usedMaterial = additiveMaterial;
-						} else if (blendMode == BlendMode.Multiply && multiplyMaterial)
-							usedMaterial = multiplyMaterial;
-						else if (blendMode == BlendMode.Screen && screenMaterial)
-							usedMaterial = screenMaterial;
-
-						usedMaterial = submeshGraphic.GetModifiedMaterial(usedMaterial);
-						canvasRenderer.SetMaterial(usedMaterial, usedTexture);
+				if (useOriginalTextureAndMaterial && hasBlendModeMaterials) {
+					bool allowCullTransparentMesh = true;
+					BlendMode materialBlendMode = blendModeMaterials.BlendModeForMaterial(usedMaterialItems[i]);
+					if ((materialBlendMode == BlendMode.Normal && submeshInstructionItem.hasPMAAdditiveSlot) ||
+						(materialBlendMode == BlendMode.Additive && pmaVertexColors)) {
+						allowCullTransparentMesh = false;
+					}
 #if HAS_CULL_TRANSPARENT_MESH
-						canvasRenderer.cullTransparentMesh = allowCullTransparentMesh ?
-							mainCullTransparentMesh : false;
+					canvasRenderer.cullTransparentMesh = allowCullTransparentMesh ?
+						mainCullTransparentMesh : false;
 #endif
-					}
-				} else {
-					var originalTexture = submeshMaterial.mainTexture;
-					Material usedMaterial;
-					Texture usedTexture;
-					if (!customMaterialOverride.TryGetValue(originalTexture, out usedMaterial))
-						usedMaterial = material;
-					if (!customTextureOverride.TryGetValue(originalTexture, out usedTexture))
-						usedTexture = originalTexture;
-
-					usedMaterial = submeshGraphic.GetModifiedMaterial(usedMaterial);
-					canvasRenderer.SetMaterial(usedMaterial, usedTexture);
 				}
+				canvasRenderer.materialCount = 1;
+				if (assignAtCanvasRenderer)
+					canvasRenderer.SetMaterial(usedMaterialItems[i], usedTextureItems[i]);
 			}
+			if (assignMeshOverrideMultiple != null)
+				assignMeshOverrideMultiple(submeshCount, meshesItems, usedMaterialItems, usedTextureItems);
 		}
 
 		protected void EnsureCanvasRendererCount (int targetCount) {
@@ -856,6 +957,16 @@ namespace Spine.Unity {
 				meshes.Add(SpineMesh.NewSkeletonMesh());
 		}
 
+		protected void EnsureUsedTexturesAndMaterialsCount (int targetCount) {
+			int oldCount = usedMaterials.Count;
+			usedMaterials.EnsureCapacity(targetCount);
+			usedTextures.EnsureCapacity(targetCount);
+			for (int i = oldCount; i < targetCount; i++) {
+				usedMaterials.Add(null);
+				usedTextures.Add(null);
+			}
+		}
+
 		protected void DestroyMeshes () {
 			foreach (var mesh in meshes) {
 #if UNITY_EDITOR

+ 71 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/MathUtilities.cs

@@ -0,0 +1,71 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2022, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+
+namespace Spine.Unity {
+	public static class MathUtilities {
+		public static float InverseLerp (float a, float b, float value) {
+			return (value - a) / (b - a);
+		}
+
+		/// <summary>
+		/// Returns the linear interpolation ratio of <c>a</c> to <c>b</c> that <c>value</c> lies on.
+		/// This is the t value that fulfills <c>value = lerp(a, b, t)</c>.
+		/// </summary>
+		public static Vector2 InverseLerp (Vector2 a, Vector2 b, Vector2 value) {
+			return new Vector2(
+				(value.x - a.x) / (b.x - a.x),
+				(value.y - a.y) / (b.y - a.y));
+		}
+
+		/// <summary>
+		/// Returns the linear interpolation ratio of <c>a</c> to <c>b</c> that <c>value</c> lies on.
+		/// This is the t value that fulfills <c>value = lerp(a, b, t)</c>.
+		/// </summary>
+		public static Vector3 InverseLerp (Vector3 a, Vector3 b, Vector3 value) {
+			return new Vector3(
+				(value.x - a.x) / (b.x - a.x),
+				(value.y - a.y) / (b.y - a.y),
+				(value.z - a.z) / (b.z - a.z));
+		}
+
+		/// <summary>
+		/// Returns the linear interpolation ratio of <c>a</c> to <c>b</c> that <c>value</c> lies on.
+		/// This is the t value that fulfills <c>value = lerp(a, b, t)</c>.
+		/// </summary>
+		public static Vector4 InverseLerp (Vector4 a, Vector4 b, Vector4 value) {
+			return new Vector4(
+				(value.x - a.x) / (b.x - a.x),
+				(value.y - a.y) / (b.y - a.y),
+				(value.z - a.z) / (b.z - a.z),
+				(value.w - a.w) / (b.w - a.w));
+		}
+	}
+}

+ 11 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/MathUtilities.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ef2b5da9383ed474d895b702a9baf79e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: