SkeletonRenderer.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated January 1, 2020. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2020, 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_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
  30. #define NEW_PREFAB_SYSTEM
  31. #endif
  32. #if UNITY_2018_1_OR_NEWER
  33. #define PER_MATERIAL_PROPERTY_BLOCKS
  34. #endif
  35. #if UNITY_2017_1_OR_NEWER
  36. #define BUILT_IN_SPRITE_MASK_COMPONENT
  37. #endif
  38. #define SPINE_OPTIONAL_RENDEROVERRIDE
  39. #define SPINE_OPTIONAL_MATERIALOVERRIDE
  40. using System.Collections.Generic;
  41. using UnityEngine;
  42. namespace Spine.Unity {
  43. /// <summary>Base class of animated Spine skeleton components. This component manages and renders a skeleton.</summary>
  44. #if NEW_PREFAB_SYSTEM
  45. [ExecuteAlways]
  46. #else
  47. [ExecuteInEditMode]
  48. #endif
  49. [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer)), DisallowMultipleComponent]
  50. [HelpURL("http://esotericsoftware.com/spine-unity-rendering")]
  51. public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent, IHasSkeletonDataAsset {
  52. [SerializeField] public SkeletonDataAsset skeletonDataAsset;
  53. #region Initialization settings
  54. /// <summary>Skin name to use when the Skeleton is initialized.</summary>
  55. [SerializeField] [SpineSkin(defaultAsEmptyString:true)] public string initialSkinName;
  56. /// <summary>Enable this parameter when overwriting the Skeleton's skin from an editor script.
  57. /// Otherwise any changes will be overwritten by the next inspector update.</summary>
  58. #if UNITY_EDITOR
  59. public bool EditorSkipSkinSync {
  60. get { return editorSkipSkinSync; }
  61. set { editorSkipSkinSync = value; }
  62. }
  63. protected bool editorSkipSkinSync = false;
  64. #endif
  65. /// <summary>Flip X and Y to use when the Skeleton is initialized.</summary>
  66. [SerializeField] public bool initialFlipX, initialFlipY;
  67. #endregion
  68. #region Advanced Render Settings
  69. // Submesh Separation
  70. /// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
  71. [UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")][SerializeField][SpineSlot] protected string[] separatorSlotNames = new string[0];
  72. /// <summary>Slots that determine where the render is split. This is used by components such as SkeletonRenderSeparator so that the skeleton can be rendered by two separate renderers on different GameObjects.</summary>
  73. [System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
  74. // Render Settings
  75. [Range(-0.1f, 0f)] public float zSpacing;
  76. /// <summary>Use Spine's clipping feature. If false, ClippingAttachments will be ignored.</summary>
  77. public bool useClipping = true;
  78. /// <summary>If true, triangles will not be updated. Enable this as an optimization if the skeleton does not make use of attachment swapping or hiding, or draw order keys. Otherwise, setting this to false may cause errors in rendering.</summary>
  79. public bool immutableTriangles = false;
  80. /// <summary>Multiply vertex color RGB with vertex color alpha. Set this to true if the shader used for rendering is a premultiplied alpha shader. Setting this to false disables single-batch additive slots.</summary>
  81. public bool pmaVertexColors = true;
  82. /// <summary>Clears the state of the render and skeleton when this component or its GameObject is disabled. This prevents previous state from being retained when it is enabled again. When pooling your skeleton, setting this to true can be helpful.</summary>
  83. public bool clearStateOnDisable = false;
  84. /// <summary>If true, second colors on slots will be added to the output Mesh as UV2 and UV3. A special "tint black" shader that interprets UV2 and UV3 as black point colors is required to render this properly.</summary>
  85. public bool tintBlack = false;
  86. /// <summary>If true, the renderer assumes the skeleton only requires one Material and one submesh to render. This allows the MeshGenerator to skip checking for changes in Materials. Enable this as an optimization if the skeleton only uses one Material.</summary>
  87. /// <remarks>This disables SkeletonRenderSeparator functionality.</remarks>
  88. public bool singleSubmesh = false;
  89. #if PER_MATERIAL_PROPERTY_BLOCKS
  90. /// <summary> Applies only when 3+ submeshes are used (2+ materials with alternating order, e.g. "A B A").
  91. /// If true, GPU instancing is disabled at all materials and MaterialPropertyBlocks are assigned at each
  92. /// material to prevent aggressive batching of submeshes by e.g. the LWRP renderer, leading to incorrect
  93. /// draw order (e.g. "A1 B A2" changed to "A1A2 B").
  94. /// You can disable this parameter when everything is drawn correctly to save the additional performance cost.
  95. /// </summary>
  96. public bool fixDrawOrder = false;
  97. #endif
  98. /// <summary>If true, the mesh generator adds normals to the output mesh. For better performance and reduced memory requirements, use a shader that assumes the desired normal.</summary>
  99. [UnityEngine.Serialization.FormerlySerializedAs("calculateNormals")] public bool addNormals = false;
  100. /// <summary>If true, tangents are calculated every frame and added to the Mesh. Enable this when using a shader that uses lighting that requires tangents.</summary>
  101. public bool calculateTangents = false;
  102. #if BUILT_IN_SPRITE_MASK_COMPONENT
  103. /// <summary>This enum controls the mode under which the sprite will interact with the masking system.</summary>
  104. /// <remarks>Interaction modes with <see cref="UnityEngine.SpriteMask"/> components are identical to Unity's <see cref="UnityEngine.SpriteRenderer"/>,
  105. /// see https://docs.unity3d.com/ScriptReference/SpriteMaskInteraction.html. </remarks>
  106. public SpriteMaskInteraction maskInteraction = SpriteMaskInteraction.None;
  107. [System.Serializable]
  108. public class SpriteMaskInteractionMaterials {
  109. public bool AnyMaterialCreated {
  110. get {
  111. return materialsMaskDisabled.Length > 0 ||
  112. materialsInsideMask.Length > 0 ||
  113. materialsOutsideMask.Length > 0;
  114. }
  115. }
  116. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.None"/>.</summary>
  117. public Material[] materialsMaskDisabled = new Material[0];
  118. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
  119. public Material[] materialsInsideMask = new Material[0];
  120. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
  121. public Material[] materialsOutsideMask = new Material[0];
  122. }
  123. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes.</summary>
  124. public SpriteMaskInteractionMaterials maskMaterials = new SpriteMaskInteractionMaterials();
  125. /// <summary>Shader property ID used for the Stencil comparison function.</summary>
  126. public static readonly int STENCIL_COMP_PARAM_ID = Shader.PropertyToID("_StencilComp");
  127. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.None"/>.</summary>
  128. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_NONE = UnityEngine.Rendering.CompareFunction.Always;
  129. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
  130. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE = UnityEngine.Rendering.CompareFunction.LessEqual;
  131. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
  132. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE = UnityEngine.Rendering.CompareFunction.Greater;
  133. #if UNITY_EDITOR
  134. private static bool haveStencilParametersBeenFixed = false;
  135. #endif
  136. #endif // #if BUILT_IN_SPRITE_MASK_COMPONENT
  137. #endregion
  138. #region Overrides
  139. #if SPINE_OPTIONAL_RENDEROVERRIDE
  140. // These are API for anything that wants to take over rendering for a SkeletonRenderer.
  141. public bool disableRenderingOnOverride = true;
  142. public delegate void InstructionDelegate (SkeletonRendererInstruction instruction);
  143. event InstructionDelegate generateMeshOverride;
  144. /// <summary>Allows separate code to take over rendering for this SkeletonRenderer component. The subscriber is passed a SkeletonRendererInstruction argument to determine how to render a skeleton.</summary>
  145. public event InstructionDelegate GenerateMeshOverride {
  146. add {
  147. generateMeshOverride += value;
  148. if (disableRenderingOnOverride && generateMeshOverride != null) {
  149. Initialize(false);
  150. meshRenderer.enabled = false;
  151. }
  152. }
  153. remove {
  154. generateMeshOverride -= value;
  155. if (disableRenderingOnOverride && generateMeshOverride == null) {
  156. Initialize(false);
  157. meshRenderer.enabled = true;
  158. }
  159. }
  160. }
  161. /// <summary> Occurs after the vertex data is populated every frame, before the vertices are pushed into the mesh.</summary>
  162. public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
  163. #endif
  164. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  165. [System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
  166. /// <summary>Use this Dictionary to override a Material with a different Material.</summary>
  167. public Dictionary<Material, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
  168. #endif
  169. [System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
  170. /// <summary>Use this Dictionary to use a different Material to render specific Slots.</summary>
  171. public Dictionary<Slot, Material> CustomSlotMaterials { get { return customSlotMaterials; } }
  172. #endregion
  173. #region Mesh Generator
  174. [System.NonSerialized] readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
  175. readonly MeshGenerator meshGenerator = new MeshGenerator();
  176. [System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers();
  177. #endregion
  178. #region Cached component references
  179. MeshRenderer meshRenderer;
  180. MeshFilter meshFilter;
  181. #endregion
  182. #region Skeleton
  183. [System.NonSerialized] public bool valid;
  184. [System.NonSerialized] public Skeleton skeleton;
  185. public Skeleton Skeleton {
  186. get {
  187. Initialize(false);
  188. return skeleton;
  189. }
  190. }
  191. #endregion
  192. public delegate void SkeletonRendererDelegate (SkeletonRenderer skeletonRenderer);
  193. /// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
  194. public event SkeletonRendererDelegate OnRebuild;
  195. /// <summary>OnMeshAndMaterialsUpdated is at the end of LateUpdate after the Mesh and
  196. /// all materials have been updated.</summary>
  197. public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
  198. public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
  199. #region Runtime Instantiation
  200. public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
  201. return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset);
  202. }
  203. /// <summary>Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.</summary>
  204. /// <typeparam name="T">T should be SkeletonRenderer or any of its derived classes.</typeparam>
  205. public static T AddSpineComponent<T> (GameObject gameObject, SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
  206. var c = gameObject.AddComponent<T>();
  207. if (skeletonDataAsset != null) {
  208. c.skeletonDataAsset = skeletonDataAsset;
  209. c.Initialize(false);
  210. }
  211. return c;
  212. }
  213. /// <summary>Applies MeshGenerator settings to the SkeletonRenderer and its internal MeshGenerator.</summary>
  214. public void SetMeshSettings (MeshGenerator.Settings settings) {
  215. this.calculateTangents = settings.calculateTangents;
  216. this.immutableTriangles = settings.immutableTriangles;
  217. this.pmaVertexColors = settings.pmaVertexColors;
  218. this.tintBlack = settings.tintBlack;
  219. this.useClipping = settings.useClipping;
  220. this.zSpacing = settings.zSpacing;
  221. this.meshGenerator.settings = settings;
  222. }
  223. #endregion
  224. public virtual void Awake () {
  225. Initialize(false);
  226. }
  227. void OnDisable () {
  228. if (clearStateOnDisable && valid)
  229. ClearState();
  230. }
  231. void OnDestroy () {
  232. rendererBuffers.Dispose();
  233. valid = false;
  234. }
  235. /// <summary>
  236. /// Clears the previously generated mesh and resets the skeleton's pose.</summary>
  237. public virtual void ClearState () {
  238. var meshFilter = GetComponent<MeshFilter>();
  239. if (meshFilter != null) meshFilter.sharedMesh = null;
  240. currentInstructions.Clear();
  241. if (skeleton != null) skeleton.SetToSetupPose();
  242. }
  243. /// <summary>
  244. /// Sets a minimum buffer size for the internal MeshGenerator to prevent excess allocations during animation.
  245. /// </summary>
  246. public void EnsureMeshGeneratorCapacity (int minimumVertexCount) {
  247. meshGenerator.EnsureVertexCapacity(minimumVertexCount);
  248. }
  249. /// <summary>
  250. /// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary>
  251. /// <param name="overwrite">If set to <c>true</c>, it will overwrite internal objects if they were already generated. Otherwise, the initialized component will ignore subsequent calls to initialize.</param>
  252. public virtual void Initialize (bool overwrite) {
  253. if (valid && !overwrite)
  254. return;
  255. // Clear
  256. {
  257. // Note: do not reset meshFilter.sharedMesh or meshRenderer.sharedMaterial to null,
  258. // otherwise constant reloading will be triggered at prefabs.
  259. currentInstructions.Clear();
  260. rendererBuffers.Clear();
  261. meshGenerator.Begin();
  262. skeleton = null;
  263. valid = false;
  264. }
  265. if (skeletonDataAsset == null)
  266. return;
  267. SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
  268. if (skeletonData == null) return;
  269. valid = true;
  270. meshFilter = GetComponent<MeshFilter>();
  271. meshRenderer = GetComponent<MeshRenderer>();
  272. rendererBuffers.Initialize();
  273. skeleton = new Skeleton(skeletonData) {
  274. ScaleX = initialFlipX ? -1 : 1,
  275. ScaleY = initialFlipY ? -1 : 1
  276. };
  277. if (!string.IsNullOrEmpty(initialSkinName) && !string.Equals(initialSkinName, "default", System.StringComparison.Ordinal))
  278. skeleton.SetSkin(initialSkinName);
  279. separatorSlots.Clear();
  280. for (int i = 0; i < separatorSlotNames.Length; i++)
  281. separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
  282. LateUpdate(); // Generate mesh for the first frame it exists.
  283. if (OnRebuild != null)
  284. OnRebuild(this);
  285. #if UNITY_EDITOR
  286. if (!Application.isPlaying) {
  287. string errorMessage = null;
  288. if (MaterialChecks.IsMaterialSetupProblematic(this, ref errorMessage))
  289. Debug.LogWarningFormat(this, "Problematic material setup at {0}: {1}", this.name, errorMessage);
  290. }
  291. #endif
  292. }
  293. /// <summary>
  294. /// Generates a new UnityEngine.Mesh from the internal Skeleton.</summary>
  295. public virtual void LateUpdate () {
  296. if (!valid) return;
  297. #if UNITY_EDITOR && NEW_PREFAB_SYSTEM
  298. // Don't store mesh or material at the prefab, otherwise it will permanently reload
  299. var prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(this);
  300. if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) &&
  301. (prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) {
  302. return;
  303. }
  304. #endif
  305. #if SPINE_OPTIONAL_RENDEROVERRIDE
  306. bool doMeshOverride = generateMeshOverride != null;
  307. if ((!meshRenderer.enabled) && !doMeshOverride) return;
  308. #else
  309. const bool doMeshOverride = false;
  310. if (!meshRenderer.enabled) return;
  311. #endif
  312. var currentInstructions = this.currentInstructions;
  313. var workingSubmeshInstructions = currentInstructions.submeshInstructions;
  314. var currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance.
  315. bool updateTriangles;
  316. if (this.singleSubmesh) {
  317. // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
  318. MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, skeletonDataAsset.atlasAssets[0].PrimaryMaterial);
  319. // STEP 1.9. Post-process workingInstructions. ==================================================================================
  320. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  321. if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
  322. MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
  323. #endif
  324. // STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
  325. meshGenerator.settings = new MeshGenerator.Settings {
  326. pmaVertexColors = this.pmaVertexColors,
  327. zSpacing = this.zSpacing,
  328. useClipping = this.useClipping,
  329. tintBlack = this.tintBlack,
  330. calculateTangents = this.calculateTangents,
  331. addNormals = this.addNormals
  332. };
  333. meshGenerator.Begin();
  334. updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
  335. if (currentInstructions.hasActiveClipping) {
  336. meshGenerator.AddSubmesh(workingSubmeshInstructions.Items[0], updateTriangles);
  337. } else {
  338. meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
  339. }
  340. } else {
  341. // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
  342. MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles);
  343. // STEP 1.9. Post-process workingInstructions. ==================================================================================
  344. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  345. if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
  346. MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
  347. #endif
  348. #if SPINE_OPTIONAL_RENDEROVERRIDE
  349. if (doMeshOverride) {
  350. this.generateMeshOverride(currentInstructions);
  351. if (disableRenderingOnOverride) return;
  352. }
  353. #endif
  354. updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
  355. // STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
  356. meshGenerator.settings = new MeshGenerator.Settings {
  357. pmaVertexColors = this.pmaVertexColors,
  358. zSpacing = this.zSpacing,
  359. useClipping = this.useClipping,
  360. tintBlack = this.tintBlack,
  361. calculateTangents = this.calculateTangents,
  362. addNormals = this.addNormals
  363. };
  364. meshGenerator.Begin();
  365. if (currentInstructions.hasActiveClipping)
  366. meshGenerator.BuildMesh(currentInstructions, updateTriangles);
  367. else
  368. meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
  369. }
  370. if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
  371. // STEP 3. Move the mesh data into a UnityEngine.Mesh ===========================================================================
  372. var currentMesh = currentSmartMesh.mesh;
  373. meshGenerator.FillVertexData(currentMesh);
  374. rendererBuffers.UpdateSharedMaterials(workingSubmeshInstructions);
  375. bool materialsChanged = rendererBuffers.MaterialsChangedInLastUpdate();
  376. if (updateTriangles) { // Check if the triangles should also be updated.
  377. meshGenerator.FillTriangles(currentMesh);
  378. meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
  379. } else if (materialsChanged) {
  380. meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
  381. }
  382. if (materialsChanged && (this.maskMaterials.AnyMaterialCreated)) {
  383. this.maskMaterials = new SpriteMaskInteractionMaterials();
  384. }
  385. meshGenerator.FillLateVertexData(currentMesh);
  386. // STEP 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ===========
  387. meshFilter.sharedMesh = currentMesh;
  388. currentSmartMesh.instructionUsed.Set(currentInstructions);
  389. #if BUILT_IN_SPRITE_MASK_COMPONENT
  390. if (meshRenderer != null) {
  391. AssignSpriteMaskMaterials();
  392. }
  393. #endif
  394. #if PER_MATERIAL_PROPERTY_BLOCKS
  395. if (fixDrawOrder && meshRenderer.sharedMaterials.Length > 2) {
  396. SetMaterialSettingsToFixDrawOrder();
  397. }
  398. #endif
  399. if (OnMeshAndMaterialsUpdated != null)
  400. OnMeshAndMaterialsUpdated(this);
  401. }
  402. public void FindAndApplySeparatorSlots (string startsWith, bool clearExistingSeparators = true, bool updateStringArray = false) {
  403. if (string.IsNullOrEmpty(startsWith)) return;
  404. FindAndApplySeparatorSlots(
  405. (slotName) => slotName.StartsWith(startsWith),
  406. clearExistingSeparators,
  407. updateStringArray
  408. );
  409. }
  410. public void FindAndApplySeparatorSlots (System.Func<string, bool> slotNamePredicate, bool clearExistingSeparators = true, bool updateStringArray = false) {
  411. if (slotNamePredicate == null) return;
  412. if (!valid) return;
  413. if (clearExistingSeparators)
  414. separatorSlots.Clear();
  415. var slots = skeleton.slots;
  416. foreach (var slot in slots) {
  417. if (slotNamePredicate.Invoke(slot.data.name))
  418. separatorSlots.Add(slot);
  419. }
  420. if (updateStringArray) {
  421. var detectedSeparatorNames = new List<string>();
  422. foreach (var slot in skeleton.slots) {
  423. string slotName = slot.data.name;
  424. if (slotNamePredicate.Invoke(slotName))
  425. detectedSeparatorNames.Add(slotName);
  426. }
  427. if (!clearExistingSeparators) {
  428. string[] originalNames = this.separatorSlotNames;
  429. foreach (string originalName in originalNames)
  430. detectedSeparatorNames.Add(originalName);
  431. }
  432. this.separatorSlotNames = detectedSeparatorNames.ToArray();
  433. }
  434. }
  435. public void ReapplySeparatorSlotNames () {
  436. if (!valid)
  437. return;
  438. separatorSlots.Clear();
  439. for (int i = 0, n = separatorSlotNames.Length; i < n; i++) {
  440. var slot = skeleton.FindSlot(separatorSlotNames[i]);
  441. if (slot != null) {
  442. separatorSlots.Add(slot);
  443. }
  444. #if UNITY_EDITOR
  445. else if (!string.IsNullOrEmpty(separatorSlotNames[i]))
  446. {
  447. Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonDataAsset.skeletonJSON.name);
  448. }
  449. #endif
  450. }
  451. }
  452. #if BUILT_IN_SPRITE_MASK_COMPONENT
  453. private void AssignSpriteMaskMaterials()
  454. {
  455. #if UNITY_EDITOR
  456. if (!Application.isPlaying && !UnityEditor.EditorApplication.isUpdating) {
  457. EditorFixStencilCompParameters();
  458. }
  459. #endif
  460. if (Application.isPlaying) {
  461. if (maskInteraction != SpriteMaskInteraction.None && maskMaterials.materialsMaskDisabled.Length == 0)
  462. maskMaterials.materialsMaskDisabled = meshRenderer.sharedMaterials;
  463. }
  464. if (maskMaterials.materialsMaskDisabled.Length > 0 && maskMaterials.materialsMaskDisabled[0] != null &&
  465. maskInteraction == SpriteMaskInteraction.None) {
  466. this.meshRenderer.materials = maskMaterials.materialsMaskDisabled;
  467. }
  468. else if (maskInteraction == SpriteMaskInteraction.VisibleInsideMask) {
  469. if (maskMaterials.materialsInsideMask.Length == 0 || maskMaterials.materialsInsideMask[0] == null) {
  470. if (!InitSpriteMaskMaterialsInsideMask())
  471. return;
  472. }
  473. this.meshRenderer.materials = maskMaterials.materialsInsideMask;
  474. }
  475. else if (maskInteraction == SpriteMaskInteraction.VisibleOutsideMask) {
  476. if (maskMaterials.materialsOutsideMask.Length == 0 || maskMaterials.materialsOutsideMask[0] == null) {
  477. if (!InitSpriteMaskMaterialsOutsideMask())
  478. return;
  479. }
  480. this.meshRenderer.materials = maskMaterials.materialsOutsideMask;
  481. }
  482. }
  483. private bool InitSpriteMaskMaterialsInsideMask()
  484. {
  485. return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE, ref maskMaterials.materialsInsideMask);
  486. }
  487. private bool InitSpriteMaskMaterialsOutsideMask()
  488. {
  489. return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE, ref maskMaterials.materialsOutsideMask);
  490. }
  491. private bool InitSpriteMaskMaterialsForMaskType(UnityEngine.Rendering.CompareFunction maskFunction, ref Material[] materialsToFill)
  492. {
  493. #if UNITY_EDITOR
  494. if (!Application.isPlaying) {
  495. return false;
  496. }
  497. #endif
  498. var originalMaterials = maskMaterials.materialsMaskDisabled;
  499. materialsToFill = new Material[originalMaterials.Length];
  500. for (int i = 0; i < originalMaterials.Length; i++) {
  501. Material newMaterial = new Material(originalMaterials[i]);
  502. newMaterial.SetFloat(STENCIL_COMP_PARAM_ID, (int)maskFunction);
  503. materialsToFill[i] = newMaterial;
  504. }
  505. return true;
  506. }
  507. #if UNITY_EDITOR
  508. private void EditorFixStencilCompParameters() {
  509. if (!haveStencilParametersBeenFixed && HasAnyStencilComp0Material()) {
  510. haveStencilParametersBeenFixed = true;
  511. FixAllProjectMaterialsStencilCompParameters();
  512. }
  513. }
  514. private void FixAllProjectMaterialsStencilCompParameters() {
  515. string[] materialGUIDS = UnityEditor.AssetDatabase.FindAssets("t:material");
  516. foreach (var guid in materialGUIDS) {
  517. string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
  518. if (!string.IsNullOrEmpty(path)) {
  519. var mat = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(path);
  520. if (mat.HasProperty(STENCIL_COMP_PARAM_ID) && mat.GetFloat(STENCIL_COMP_PARAM_ID) == 0) {
  521. mat.SetFloat(STENCIL_COMP_PARAM_ID, (int)STENCIL_COMP_MASKINTERACTION_NONE);
  522. }
  523. }
  524. }
  525. UnityEditor.AssetDatabase.Refresh();
  526. UnityEditor.AssetDatabase.SaveAssets();
  527. }
  528. private bool HasAnyStencilComp0Material() {
  529. if (meshRenderer == null)
  530. return false;
  531. foreach (var mat in meshRenderer.sharedMaterials) {
  532. if (mat != null && mat.HasProperty(STENCIL_COMP_PARAM_ID)) {
  533. float currentCompValue = mat.GetFloat(STENCIL_COMP_PARAM_ID);
  534. if (currentCompValue == 0)
  535. return true;
  536. }
  537. }
  538. return false;
  539. }
  540. #endif // UNITY_EDITOR
  541. #endif //#if BUILT_IN_SPRITE_MASK_COMPONENT
  542. #if PER_MATERIAL_PROPERTY_BLOCKS
  543. private MaterialPropertyBlock reusedPropertyBlock;
  544. public static readonly int SUBMESH_DUMMY_PARAM_ID = Shader.PropertyToID("_Submesh");
  545. /// <summary>
  546. /// This method was introduced as a workaround for too aggressive submesh draw call batching,
  547. /// leading to incorrect draw order when 3+ materials are used at submeshes in alternating order.
  548. /// Otherwise, e.g. when using Lightweight Render Pipeline, deliberately separated draw calls
  549. /// "A1 B A2" are reordered to "A1A2 B", regardless of batching-related project settings.
  550. /// </summary>
  551. private void SetMaterialSettingsToFixDrawOrder() {
  552. if (reusedPropertyBlock == null) reusedPropertyBlock = new MaterialPropertyBlock();
  553. bool hasPerRendererBlock = meshRenderer.HasPropertyBlock();
  554. if (hasPerRendererBlock) {
  555. meshRenderer.GetPropertyBlock(reusedPropertyBlock);
  556. }
  557. for (int i = 0; i < meshRenderer.sharedMaterials.Length; ++i) {
  558. if (!hasPerRendererBlock) meshRenderer.GetPropertyBlock(reusedPropertyBlock, i);
  559. // Note: this parameter shall not exist at any shader, then Unity will create separate
  560. // material instances (not in terms of memory cost or leakage).
  561. reusedPropertyBlock.SetFloat(SUBMESH_DUMMY_PARAM_ID, i);
  562. meshRenderer.SetPropertyBlock(reusedPropertyBlock, i);
  563. meshRenderer.sharedMaterials[i].enableInstancing = false;
  564. }
  565. }
  566. #endif
  567. }
  568. }