Selaa lähdekoodia

[unity] Added `RenderCombinedMesh` sample component for combined outlines with `SkeletonRenderSeparator` or multiple atlas pages.

Harald Csaszar 2 vuotta sitten
vanhempi
commit
ed75bffc6f

+ 6 - 0
CHANGELOG.md

@@ -105,6 +105,12 @@
   * Added `SkeletonGraphic.MeshScale` property to allow access to calculated mesh scale. `MeshScale` is based on (1) Canvas pixels per unit, and (2) `RectTransform` bounds when using `Layout Scale Mode` other than `None` at `SkeletonGraphic` which scales the skeleton mesh to fit the parent `RectTransform` bounds accordingly.
   * Added `updateSeparatorPartScale` property to `SkeletonGraphic` to let render separator parts follow the scale (lossy scale) of the `SkeletonGraphic` GameObject. Defaults to `false` to maintain existing behaviour.
   * Added experimental `EditorSkeletonPlayer` component to allow Editor playback of the initial animation set at `SkeletonAnimation` or `SkeletonGraphic` components. Add this component to your skeleton GameObject to enable the in-editor animation preview. Allows configurations for continuous playback when selected, deselected, and alternative single-frame preview by setting `Fixed Track Time` to any value other than 0. Limitations: At skeletons with variable material count the Inspector preview may be too unresponsive. It is then recommended to disable the `EditorSkeletonPlayer` component (at the top of the Inspector) to make it responsive again, then you can disable `Play When Selected` and re-enable the component to preview playback only when deselected. 
+  * Added example component `RenderCombinedMesh` to render a combined mesh of multiple meshes or submeshes. This is required by `OutlineOnly` shaders to render a combined outline when using `SkeletonRenderSeparator` or multiple atlas pages which would normally lead to outlines around individual parts. To add a combined outline to your SkeletenRenderer:
+    1) Add a child GameObject and move it a bit back (e.g. position Z = 0.01).
+    2) Add a `RenderCombinedMesh` component, provided in the `Spine Examples/Scripts/Sample Components` directory.
+    3) Copy the original material, add *_Outline* to its name and set the shader to your outline-only shader like `Universal Render Pipeline/Spine/Outline/Skeleton-OutlineOnly` or `Spine/Outline/OutlineOnly-ZWrite`.
+    4) Assign this *_Outline* material at the new child GameObject's `MeshRenderer` component.
+    If you are using `SkeletonRenderSeparator` and need to enable and disable the `SkeletonRenderSeparator` component at runtime, you can increase the `RenderCombinedMesh` `Reference Renderers` array by one and assign the `SkeletonRenderer` itself at the last entry after the parts renderers. Disabled `MeshRenderer` components will be skipped when combining the final mesh, so the combined mesh is automatically filled from the desired active renderers.
 
 * **Breaking changes**
   * Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.

+ 253 - 0
spine-unity/Assets/Spine Examples/Scripts/Sample Components/RenderCombinedMesh.cs

@@ -0,0 +1,253 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated July 28, 2023. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2023, 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_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEW_PREFAB_SYSTEM
+#endif
+
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace Spine.Unity.Examples {
+
+#if NEW_PREFAB_SYSTEM
+	[ExecuteAlways]
+#else
+	[ExecuteInEditMode]
+#endif
+	public class RenderCombinedMesh : MonoBehaviour {
+		public SkeletonRenderer skeletonRenderer;
+		public SkeletonRenderSeparator renderSeparator;
+		public MeshRenderer[] referenceRenderers;
+
+		bool updateViaSkeletonCallback = false;
+		MeshFilter[] referenceMeshFilters;
+		MeshRenderer ownRenderer;
+		MeshFilter ownMeshFilter;
+
+		protected DoubleBuffered<Mesh> doubleBufferedMesh;
+		protected ExposedList<Vector3> positionBuffer;
+		protected ExposedList<Color32> colorBuffer;
+		protected ExposedList<Vector2> uvBuffer;
+		protected ExposedList<int> indexBuffer;
+
+#if UNITY_EDITOR
+		private void Reset () {
+			if (skeletonRenderer == null)
+				skeletonRenderer = this.GetComponentInParent<SkeletonRenderer>();
+			GatherRenderers();
+
+			Awake();
+			if (referenceRenderers.Length > 0)
+				ownRenderer.sharedMaterial = referenceRenderers[0].sharedMaterial;
+
+			LateUpdate();
+		}
+#endif
+		protected void GatherRenderers () {
+			referenceRenderers = this.GetComponentsInChildren<MeshRenderer>();
+			if (referenceRenderers.Length == 0 ||
+				(referenceRenderers.Length == 1 && referenceRenderers[0].gameObject == this.gameObject)) {
+				Transform parent = this.transform.parent;
+				if (parent)
+					referenceRenderers = parent.GetComponentsInChildren<MeshRenderer>();
+			}
+			referenceRenderers = referenceRenderers.Where(
+				(val, idx) => val.gameObject != this.gameObject && val.enabled).ToArray();
+		}
+
+		void Awake () {
+			if (skeletonRenderer == null)
+				skeletonRenderer = this.GetComponentInParent<SkeletonRenderer>();
+			if (referenceRenderers == null || referenceRenderers.Length == 0) {
+				GatherRenderers();
+			}
+
+			if (renderSeparator == null) {
+				if (skeletonRenderer)
+					renderSeparator = skeletonRenderer.GetComponent<SkeletonRenderSeparator>();
+				else
+					renderSeparator = this.GetComponentInParent<SkeletonRenderSeparator>();
+			}
+
+			int count = referenceRenderers.Length;
+			referenceMeshFilters = new MeshFilter[count];
+			for (int i = 0; i < count; ++i) {
+				referenceMeshFilters[i] = referenceRenderers[i].GetComponent<MeshFilter>();
+			}
+
+			ownRenderer = this.GetComponent<MeshRenderer>();
+			if (ownRenderer == null)
+				ownRenderer = this.gameObject.AddComponent<MeshRenderer>();
+			ownMeshFilter = this.GetComponent<MeshFilter>();
+			if (ownMeshFilter == null)
+				ownMeshFilter = this.gameObject.AddComponent<MeshFilter>();
+		}
+
+		void OnEnable () {
+#if UNITY_EDITOR
+			if (Application.isPlaying)
+				Awake();
+#endif
+			if (skeletonRenderer) {
+				skeletonRenderer.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
+				skeletonRenderer.OnMeshAndMaterialsUpdated += UpdateOnCallback;
+				updateViaSkeletonCallback = true;
+			}
+			if (renderSeparator) {
+				renderSeparator.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
+				renderSeparator.OnMeshAndMaterialsUpdated += UpdateOnCallback;
+				updateViaSkeletonCallback = true;
+			}
+		}
+
+		void OnDisable () {
+			if (skeletonRenderer)
+				skeletonRenderer.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
+			if (renderSeparator)
+				renderSeparator.OnMeshAndMaterialsUpdated -= UpdateOnCallback;
+		}
+
+		void OnDestroy () {
+			for (int i  = 0; i < 2; ++i) {
+				Mesh mesh = doubleBufferedMesh.GetNext();
+#if UNITY_EDITOR
+				if (Application.isEditor && !Application.isPlaying)
+					UnityEngine.Object.DestroyImmediate(mesh);
+				else
+					UnityEngine.Object.Destroy(mesh);
+#else
+					UnityEngine.Object.Destroy(mesh);
+#endif
+			}
+		}
+
+		void LateUpdate () {
+#if UNITY_EDITOR
+			if (!Application.isPlaying) {
+				UpdateMesh();
+				return;
+			}
+#endif
+
+			if (updateViaSkeletonCallback)
+				return;
+			UpdateMesh();
+		}
+
+		void UpdateOnCallback (SkeletonRenderer r) {
+			UpdateMesh();
+		}
+
+		protected void EnsureBufferSizes (int combinedVertexCount, int combinedIndexCount) {
+			if (positionBuffer == null) {
+				positionBuffer = new ExposedList<Vector3>(combinedVertexCount);
+				uvBuffer = new ExposedList<Vector2>(combinedVertexCount);
+				colorBuffer = new ExposedList<Color32>(combinedVertexCount);
+				indexBuffer = new ExposedList<int>(combinedIndexCount);
+			}
+
+			if (positionBuffer.Count < combinedVertexCount) {
+				positionBuffer.Resize(combinedVertexCount);
+				uvBuffer.Resize(combinedVertexCount);
+				colorBuffer.Resize(combinedVertexCount);
+			}
+			if (indexBuffer.Count < combinedIndexCount) {
+				indexBuffer.Resize(combinedIndexCount);
+			}
+		}
+
+		void InitMesh () {
+			if (doubleBufferedMesh == null) {
+				doubleBufferedMesh = new DoubleBuffered<Mesh>();
+				for (int i = 0; i < 2; ++i) {
+					Mesh combinedMesh = doubleBufferedMesh.GetNext();
+					combinedMesh.MarkDynamic();
+					combinedMesh.name = "RenderCombinedMesh" + i;
+					combinedMesh.subMeshCount = 1;
+				}
+			}
+		}
+
+		void UpdateMesh () {
+			InitMesh();
+			int combinedVertexCount = 0;
+			int combinedIndexCount = 0;
+			GetCombinedMeshInfo(ref combinedVertexCount, ref combinedIndexCount);
+
+			EnsureBufferSizes(combinedVertexCount, combinedIndexCount);
+
+			int combinedV = 0;
+			int combinedI = 0;
+			for (int r = 0, rendererCount = referenceMeshFilters.Length; r < rendererCount; ++r) {
+				MeshFilter meshFilter = referenceMeshFilters[r];
+				Mesh mesh = meshFilter.sharedMesh;
+				if (mesh == null) continue;
+
+				int vertexCount = mesh.vertexCount;
+				Vector3[] positions = mesh.vertices;
+				Vector2[] uvs = mesh.uv;
+				Color32[] colors = mesh.colors32;
+
+				System.Array.Copy(positions, 0, this.positionBuffer.Items, combinedV, vertexCount);
+				System.Array.Copy(uvs, 0, this.uvBuffer.Items, combinedV, vertexCount);
+				System.Array.Copy(colors, 0, this.colorBuffer.Items, combinedV, vertexCount);
+				combinedV += vertexCount;
+
+				for (int s = 0, submeshCount = mesh.subMeshCount; s < submeshCount; ++s) {
+					int submeshIndexCount = (int)mesh.GetIndexCount(s);
+					int[] submeshIndices = mesh.GetIndices(s);
+					System.Array.Copy(submeshIndices, 0, this.indexBuffer.Items, combinedI, submeshIndexCount);
+					combinedI += submeshIndexCount;
+				}
+			}
+
+			Mesh combinedMesh = doubleBufferedMesh.GetNext();
+			combinedMesh.SetVertices(this.positionBuffer.Items, 0, this.positionBuffer.Count);
+			combinedMesh.SetUVs(0, this.uvBuffer.Items, 0, this.uvBuffer.Count);
+			combinedMesh.SetColors(this.colorBuffer.Items, 0, this.colorBuffer.Count);
+			combinedMesh.SetTriangles(this.indexBuffer.Items, 0, this.indexBuffer.Count, 0);
+			ownMeshFilter.sharedMesh = combinedMesh;
+		}
+
+		void GetCombinedMeshInfo (ref int vertexCount, ref int indexCount) {
+			for (int r = 0, rendererCount = referenceMeshFilters.Length; r < rendererCount; ++r) {
+				MeshFilter meshFilter = referenceMeshFilters[r];
+				Mesh mesh = meshFilter.sharedMesh;
+				if (mesh == null) continue;
+
+				vertexCount += mesh.vertexCount;
+				for (int s = 0, submeshCount = mesh.subMeshCount; s < submeshCount; ++s) {
+					indexCount += (int)mesh.GetIndexCount(s);
+				}
+			}
+		}
+	}
+}

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

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

+ 1 - 1
spine-unity/Assets/Spine Examples/package.json

@@ -2,7 +2,7 @@
   "name": "com.esotericsoftware.spine.spine-unity-examples",
   "displayName": "spine-unity Runtime Examples",
   "description": "This plugin provides example scenes and scripts for the spine-unity runtime.",
-  "version": "4.1.13",
+  "version": "4.1.21",
   "unity": "2018.3",
   "author": {
     "name": "Esoteric Software",