Browse Source

Merge branch '3.8' into 4.0-beta

Harald Csaszar 4 năm trước cách đây
mục cha
commit
6e2e46e198

+ 1 - 0
CHANGELOG.md

@@ -379,6 +379,7 @@
   * `SkeletonRenderer` components now provide an additional update mode `Only Event Timelines` at the `Update When Invisible` property. This mode saves additional timeline updates compared to update mode `Everything Except Mesh`.
   * Now all URP (Universal Render Pipeline) and LWRP (Lightweight Render Pipeline) shaders support SRP (Scriptable Render Pipeline) batching. See [Unity SRPBatcher documentation pages](https://docs.unity3d.com/Manual/SRPBatcher.html) for additional information.
   * Sprite shaders now provide four `Diffuse Ramp` modes as an Inspector Material parameter: `Hard`, `Soft`, `Old Hard` and `Old Soft`. In spine-unity 3.8 it defaults to `Old Hard` to keep the behaviour of existing projects unchanged. From 4.0 on it defaults to `Hard` for newly created materials while existing ones remain unchanged. Note that `Old Hard` and `Old Soft` ramp versions were using only the right half of the ramp texture, and additionally multiplying the light intensity by 2, both leading to brighter lighting than without a ramp texture active. The new ramp modes `Hard` and `Soft` use the full ramp texture and do not modify light intensity, being consistent with lighting without a ramp texture active.
+  * Added **native support for slot blend modes** `Additive`, `Multiply` and `Screen` with automatic assignment at newly imported skeleton assets. `BlendModeMaterialAssets` are now obsolete and replaced by the native properties at `SkeletonDataAsset`. The `SkeletonDataAsset` Inspector provides a new `Blend Modes - Upgrade` button to upgrade an obsolete `BlendModeMaterialAsset` to the native blend modes properties. This upgrade will be performed automatically on imported and re-imported assets in Unity 2020.1 and newer to prevent reported `BlendModeMaterialAsset` issues in these Unity versions. spine-unity 4.0 and newer will automatically perform this upgrade regardless of the Unity version.
 
 * **Changes of default values**
   * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`.

+ 28 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs

@@ -56,6 +56,7 @@ namespace Spine.Unity.Editor {
 
 		SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
 		SerializedProperty skeletonDataModifiers;
+		SerializedProperty blendModeMaterials;
 		#if SPINE_TK2D
 		SerializedProperty spriteCollection;
 		#endif
@@ -114,6 +115,7 @@ namespace Spine.Unity.Editor {
 			defaultMix = serializedObject.FindProperty("defaultMix");
 
 			skeletonDataModifiers = serializedObject.FindProperty("skeletonDataModifiers");
+			blendModeMaterials = serializedObject.FindProperty("blendModeMaterials");
 
 			#if SPINE_SKELETON_MECANIM
 			controller = serializedObject.FindProperty("controller");
@@ -125,7 +127,7 @@ namespace Spine.Unity.Editor {
 			#else
 			// Analysis disable once ConvertIfToOrExpression
 			if (newAtlasAssets) atlasAssets.isExpanded = true;
-#endif
+			#endif
 
 			// This handles the case where the managed editor assembly is unloaded before recompilation when code changes.
 			AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload;
@@ -286,6 +288,8 @@ namespace Spine.Unity.Editor {
 				EditorGUILayout.DelayedFloatField(scale); //EditorGUILayout.PropertyField(scale);
 				EditorGUILayout.Space();
 				EditorGUILayout.PropertyField(skeletonDataModifiers, true);
+
+				DrawBlendModeMaterialProperties();
 			}
 
 			// Texture source field.
@@ -311,6 +315,27 @@ namespace Spine.Unity.Editor {
 
 		}
 
+		void DrawBlendModeMaterialProperties () {
+			if (skeletonDataModifiers.arraySize > 0) {
+				EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5));
+				EditorGUILayout.PrefixLabel("Blend Modes");
+				if (GUILayout.Button(new GUIContent("Upgrade", "Upgrade BlendModeMaterialAsset to built-in BlendModeMaterials."), EditorStyles.miniButton, GUILayout.Width(65f))) {
+					foreach (SkeletonDataAsset skeletonData in targets) {
+						BlendModeMaterialsUtility.UpgradeBlendModeMaterials(skeletonData);
+					}
+				}
+				EditorGUILayout.EndHorizontal();
+			}
+			EditorGUI.BeginChangeCheck();
+			EditorGUILayout.PropertyField(blendModeMaterials, true);
+			if (EditorGUI.EndChangeCheck()) {
+				serializedObject.ApplyModifiedProperties();
+				foreach (SkeletonDataAsset skeletonData in targets) {
+					BlendModeMaterialsUtility.UpdateBlendModeMaterials(skeletonData);
+				}
+			}
+		}
+
 		void DrawSkeletonDataFields () {
 			using (new EditorGUILayout.HorizontalScope()) {
 				EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel);
@@ -331,6 +356,8 @@ namespace Spine.Unity.Editor {
 			EditorGUILayout.DelayedFloatField(scale); //EditorGUILayout.PropertyField(scale);
 			EditorGUILayout.Space();
 			EditorGUILayout.PropertyField(skeletonDataModifiers, true);
+
+			DrawBlendModeMaterialProperties();
 		}
 
 		void DrawAtlasAssetsFields () {

+ 5 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs

@@ -427,6 +427,8 @@ namespace Spine.Unity.Editor {
 						}
 
 						SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
+						BlendModeMaterialsUtility.UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData);
+
 						string currentHash = skeletonData != null ? skeletonData.Hash : null;
 
 #if SPINE_SKELETONMECANIM
@@ -884,6 +886,7 @@ namespace Spine.Unity.Editor {
 						skeletonDataAsset.skeletonJSON = spineJson;
 						skeletonDataAsset.defaultMix = SpineEditorUtilities.Preferences.defaultMix;
 						skeletonDataAsset.scale = SpineEditorUtilities.Preferences.defaultScale;
+						skeletonDataAsset.blendModeMaterials.applyAdditiveMaterial = !SpineEditorUtilities.Preferences.UsesPMAWorkflow;
 					}
 
 					AssetDatabase.CreateAsset(skeletonDataAsset, filePath);
@@ -891,7 +894,8 @@ namespace Spine.Unity.Editor {
 				} else {
 					skeletonDataAsset.atlasAssets = atlasAssets;
 					skeletonDataAsset.Clear();
-					skeletonDataAsset.GetSkeletonData(true);
+					var skeletonData = skeletonDataAsset.GetSkeletonData(true);
+					BlendModeMaterialsUtility.UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData);
 				}
 
 				return skeletonDataAsset;

+ 282 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs

@@ -0,0 +1,282 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, 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_2020_1_OR_NEWER
+#define UPGRADE_ALL_BLEND_MODE_MATERIALS
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System;
+
+namespace Spine.Unity.Editor {
+
+	public class BlendModeMaterialsUtility {
+
+		public const string MATERIAL_SUFFIX_MULTIPLY = "-Multiply";
+		public const string MATERIAL_SUFFIX_SCREEN = "-Screen";
+		public const string MATERIAL_SUFFIX_ADDITIVE = "-Additive";
+
+#if UPGRADE_ALL_BLEND_MODE_MATERIALS
+		public const bool ShallUpgradeBlendModeMaterials = true;
+#else
+		public const bool ShallUpgradeBlendModeMaterials = false;
+#endif
+
+		protected class TemplateMaterials {
+			public Material multiplyTemplate;
+			public Material screenTemplate;
+			public Material additiveTemplate;
+		};
+
+		public static void UpgradeBlendModeMaterials (SkeletonDataAsset skeletonDataAsset) {
+			var skeletonData = skeletonDataAsset.GetSkeletonData(true);
+			if (skeletonData == null)
+				return;
+			UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData, true);
+		}
+
+		public static void UpdateBlendModeMaterials (SkeletonDataAsset skeletonDataAsset) {
+			var skeletonData = skeletonDataAsset.GetSkeletonData(true);
+			if (skeletonData == null)
+				return;
+			UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData, false);
+		}
+
+		public static void UpdateBlendModeMaterials (SkeletonDataAsset skeletonDataAsset, ref SkeletonData skeletonData,
+			bool upgradeFromModifierAssets = ShallUpgradeBlendModeMaterials) {
+
+			TemplateMaterials templateMaterials = new TemplateMaterials();
+			bool anyMaterialsChanged = ClearUndesiredMaterialEntries(skeletonDataAsset);
+
+			var blendModesModifierAsset = FindBlendModeMaterialsModifierAsset(skeletonDataAsset);
+			if (blendModesModifierAsset) {
+				if (upgradeFromModifierAssets) {
+					TransferSettingsFromModifierAsset(blendModesModifierAsset,
+					skeletonDataAsset, templateMaterials);
+					UpdateBlendmodeMaterialsRequiredState(skeletonDataAsset, skeletonData);
+				}
+				else
+					return;
+			}
+			else {
+				if (!UpdateBlendmodeMaterialsRequiredState(skeletonDataAsset, skeletonData))
+					return;
+				AssignPreferencesTemplateMaterials(templateMaterials);
+			}
+			bool success = CreateAndAssignMaterials(skeletonDataAsset, templateMaterials, ref anyMaterialsChanged);
+			if (success) {
+				if (blendModesModifierAsset != null) {
+					RemoveObsoleteModifierAsset(blendModesModifierAsset, skeletonDataAsset);
+				}
+			}
+
+			skeletonDataAsset.Clear();
+			skeletonData = skeletonDataAsset.GetSkeletonData(true);
+			if (anyMaterialsChanged)
+				ReloadSceneSkeletons(skeletonDataAsset);
+			AssetDatabase.SaveAssets();
+		}
+
+		protected static bool ClearUndesiredMaterialEntries (SkeletonDataAsset skeletonDataAsset) {
+			Predicate<BlendModeMaterials.ReplacementMaterial> ifMaterialMissing = r => r.material == null;
+
+			bool anyMaterialsChanged = false;
+			if (!skeletonDataAsset.blendModeMaterials.applyAdditiveMaterial) {
+				anyMaterialsChanged |= skeletonDataAsset.blendModeMaterials.additiveMaterials.Count > 0;
+				skeletonDataAsset.blendModeMaterials.additiveMaterials.Clear();
+			}
+			else
+				anyMaterialsChanged |= skeletonDataAsset.blendModeMaterials.additiveMaterials.RemoveAll(ifMaterialMissing) != 0;
+			anyMaterialsChanged |= skeletonDataAsset.blendModeMaterials.multiplyMaterials.RemoveAll(ifMaterialMissing) != 0;
+			anyMaterialsChanged |= skeletonDataAsset.blendModeMaterials.screenMaterials.RemoveAll(ifMaterialMissing) != 0;
+			return anyMaterialsChanged;
+		}
+
+		protected static BlendModeMaterialsAsset FindBlendModeMaterialsModifierAsset (SkeletonDataAsset skeletonDataAsset) {
+			foreach (var modifierAsset in skeletonDataAsset.skeletonDataModifiers) {
+				if (modifierAsset is BlendModeMaterialsAsset)
+					return (BlendModeMaterialsAsset)modifierAsset;
+			}
+			return null;
+		}
+
+		protected static bool UpdateBlendmodeMaterialsRequiredState (SkeletonDataAsset skeletonDataAsset, SkeletonData skeletonData) {
+			return skeletonDataAsset.blendModeMaterials.UpdateBlendmodeMaterialsRequiredState(skeletonData);
+		}
+
+		protected static void TransferSettingsFromModifierAsset (BlendModeMaterialsAsset modifierAsset,
+			SkeletonDataAsset skeletonDataAsset, TemplateMaterials templateMaterials) {
+
+			skeletonDataAsset.blendModeMaterials.TransferSettingsFrom(modifierAsset);
+
+			templateMaterials.multiplyTemplate = modifierAsset.multiplyMaterialTemplate;
+			templateMaterials.screenTemplate = modifierAsset.screenMaterialTemplate;
+			templateMaterials.additiveTemplate = modifierAsset.additiveMaterialTemplate;
+		}
+
+		protected static void RemoveObsoleteModifierAsset (BlendModeMaterialsAsset modifierAsset,
+			SkeletonDataAsset skeletonDataAsset) {
+
+			skeletonDataAsset.skeletonDataModifiers.Remove(modifierAsset);
+			Debug.Log(string.Format("BlendModeMaterialsAsset upgraded to built-in BlendModeMaterials at SkeletonDataAsset '{0}'.",
+				skeletonDataAsset.name), skeletonDataAsset);
+			EditorUtility.SetDirty(skeletonDataAsset);
+		}
+
+		protected static void AssignPreferencesTemplateMaterials (TemplateMaterials templateMaterials) {
+
+			templateMaterials.multiplyTemplate = SpineEditorUtilities.Preferences.BlendModeMaterialMultiply;
+			templateMaterials.screenTemplate = SpineEditorUtilities.Preferences.BlendModeMaterialScreen;
+			templateMaterials.additiveTemplate = SpineEditorUtilities.Preferences.BlendModeMaterialAdditive;
+		}
+
+		protected static bool CreateAndAssignMaterials (SkeletonDataAsset skeletonDataAsset,
+			TemplateMaterials templateMaterials, ref bool anyReplacementMaterialsChanged) {
+
+			bool anyCreationFailed = false;
+			var blendModeMaterials = skeletonDataAsset.blendModeMaterials;
+			bool applyAdditiveMaterial = blendModeMaterials.applyAdditiveMaterial;
+
+			var skinEntries = new List<Skin.SkinEntry>();
+
+			skeletonDataAsset.Clear();
+			skeletonDataAsset.isUpgradingBlendModeMaterials = true;
+			SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
+
+			var slotsItems = skeletonData.Slots.Items;
+			for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
+				var slot = slotsItems[slotIndex];
+				if (slot.BlendMode == BlendMode.Normal) continue;
+				if (!applyAdditiveMaterial && slot.BlendMode == BlendMode.Additive) continue;
+
+				List<BlendModeMaterials.ReplacementMaterial> replacementMaterials = null;
+				Material materialTemplate = null;
+				string materialSuffix = null;
+				switch (slot.BlendMode) {
+					case BlendMode.Multiply:
+						replacementMaterials = blendModeMaterials.multiplyMaterials;
+						materialTemplate = templateMaterials.multiplyTemplate;
+						materialSuffix = MATERIAL_SUFFIX_MULTIPLY;
+						break;
+					case BlendMode.Screen:
+						replacementMaterials = blendModeMaterials.screenMaterials;
+						materialTemplate = templateMaterials.screenTemplate;
+						materialSuffix = MATERIAL_SUFFIX_SCREEN;
+						break;
+					case BlendMode.Additive:
+						replacementMaterials = blendModeMaterials.additiveMaterials;
+						materialTemplate = templateMaterials.additiveTemplate;
+						materialSuffix = MATERIAL_SUFFIX_ADDITIVE;
+						break;
+				}
+
+				skinEntries.Clear();
+				foreach (var skin in skeletonData.Skins)
+					skin.GetAttachments(slotIndex, skinEntries);
+
+				foreach (var entry in skinEntries) {
+					var renderableAttachment = entry.Attachment as IHasRendererObject;
+					if (renderableAttachment != null) {
+						var originalRegion = (AtlasRegion)renderableAttachment.RendererObject;
+						bool replacementExists = replacementMaterials.Exists(
+							replacement => replacement.pageName == originalRegion.page.name);
+						if (!replacementExists) {
+							bool createdNewMaterial;
+							var replacement = CreateOrLoadReplacementMaterial(originalRegion, materialTemplate, materialSuffix, out createdNewMaterial);
+							if (replacement != null) {
+								replacementMaterials.Add(replacement);
+								anyReplacementMaterialsChanged = true;
+								if (createdNewMaterial) {
+									Debug.Log(string.Format("Created blend mode Material '{0}' for SkeletonDataAsset '{1}'.",
+										replacement.material.name, skeletonDataAsset), replacement.material);
+								}
+							}
+							else {
+								Debug.LogError(string.Format("Failed creating blend mode Material for SkeletonDataAsset '{0}',"+
+									" atlas page '{1}', template '{2}'.",
+									skeletonDataAsset.name, originalRegion.page.name, materialTemplate.name),
+									skeletonDataAsset);
+								anyCreationFailed = true;
+							}
+						}
+					}
+				}
+			}
+
+			skeletonDataAsset.isUpgradingBlendModeMaterials = false;
+			EditorUtility.SetDirty(skeletonDataAsset);
+			return !anyCreationFailed;
+		}
+
+		protected static string GetBlendModeMaterialPath(AtlasPage originalPage, string materialSuffix) {
+			var originalMaterial = originalPage.rendererObject as Material;
+			var originalPath = AssetDatabase.GetAssetPath(originalMaterial);
+			return originalPath.Replace(".mat", materialSuffix + ".mat");
+		}
+
+		protected static BlendModeMaterials.ReplacementMaterial CreateOrLoadReplacementMaterial (
+			AtlasRegion originalRegion, Material materialTemplate, string materialSuffix, out bool createdNewMaterial) {
+
+			createdNewMaterial = false;
+			var newReplacement = new BlendModeMaterials.ReplacementMaterial();
+			var originalPage = originalRegion.page;
+			var originalMaterial = originalPage.rendererObject as Material;
+			var blendMaterialPath = GetBlendModeMaterialPath(originalPage, materialSuffix);
+
+			newReplacement.pageName = originalPage.name;
+			if (File.Exists(blendMaterialPath)) {
+				newReplacement.material = AssetDatabase.LoadAssetAtPath<Material>(blendMaterialPath);
+			}
+			else {
+				var blendModeMaterial = new Material(materialTemplate) {
+					name = originalMaterial.name + " " + materialTemplate.name,
+					mainTexture = originalMaterial.mainTexture
+				};
+				newReplacement.material = blendModeMaterial;
+
+				AssetDatabase.CreateAsset(blendModeMaterial, blendMaterialPath);
+				EditorUtility.SetDirty(blendModeMaterial);
+				createdNewMaterial = true;
+			}
+
+			if (newReplacement.material)
+				return newReplacement;
+			else
+				return null;
+		}
+
+		protected static void ReloadSceneSkeletons (SkeletonDataAsset skeletonDataAsset) {
+			if (SpineEditorUtilities.Preferences.autoReloadSceneSkeletons)
+				SpineEditorUtilities.DataReloadHandler.ReloadSceneSkeletonComponents(skeletonDataAsset);
+		}
+	}
+}

+ 11 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BlendModeMaterialsUtility.cs.meta

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

+ 65 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs

@@ -124,6 +124,32 @@ namespace Spine.Unity.Editor {
 			const string TEXTURE_SETTINGS_REFERENCE_KEY = "SPINE_TEXTURE_SETTINGS_REFERENCE";
 			public static string textureSettingsReference = SpinePreferences.DEFAULT_TEXTURE_SETTINGS_REFERENCE;
 
+			public static bool UsesPMAWorkflow {
+				get {
+					return SpinePreferences.IsPMAWorkflow(textureSettingsReference);
+				}
+			}
+
+			const string BLEND_MODE_MATERIAL_MULTIPLY_KEY = "SPINE_BLENDMODE_MATERIAL_MULTIPLY";
+			const string BLEND_MODE_MATERIAL_SCREEN_KEY = "SPINE_BLENDMODE_MATERIAL_SCREEN";
+			const string BLEND_MODE_MATERIAL_ADDITIVE_KEY = "SPINE_BLENDMODE_MATERIAL_ADDITIVE";
+			public static string blendModeMaterialMultiply = "";
+			public static string blendModeMaterialScreen = "";
+			public static string blendModeMaterialAdditive = "";
+			public const string DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL = SpinePreferences.DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL;
+			public const string DEFAULT_BLEND_MODE_SCREEN_MATERIAL = SpinePreferences.DEFAULT_BLEND_MODE_SCREEN_MATERIAL;
+			public const string DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL = SpinePreferences.DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL;
+
+			public static Material BlendModeMaterialMultiply {
+				get { return AssetDatabase.LoadAssetAtPath<Material>(blendModeMaterialMultiply); }
+			}
+			public static Material BlendModeMaterialScreen {
+				get { return AssetDatabase.LoadAssetAtPath<Material>(blendModeMaterialScreen); }
+			}
+			public static Material BlendModeMaterialAdditive {
+				get { return AssetDatabase.LoadAssetAtPath<Material>(blendModeMaterialAdditive); }
+			}
+
 			const string ATLASTXT_WARNING_KEY = "SPINE_ATLASTXT_WARNING";
 			public static bool atlasTxtImportWarning = SpinePreferences.DEFAULT_ATLASTXT_WARNING;
 
@@ -161,6 +187,9 @@ namespace Spine.Unity.Editor {
 				showHierarchyIcons = EditorPrefs.GetBool(SHOW_HIERARCHY_ICONS_KEY, SpinePreferences.DEFAULT_SHOW_HIERARCHY_ICONS);
 				setTextureImporterSettings = EditorPrefs.GetBool(SET_TEXTUREIMPORTER_SETTINGS_KEY, SpinePreferences.DEFAULT_SET_TEXTUREIMPORTER_SETTINGS);
 				textureSettingsReference = EditorPrefs.GetString(TEXTURE_SETTINGS_REFERENCE_KEY, SpinePreferences.DEFAULT_TEXTURE_SETTINGS_REFERENCE);
+				blendModeMaterialMultiply = EditorPrefs.GetString(BLEND_MODE_MATERIAL_MULTIPLY_KEY, "");
+				blendModeMaterialScreen = EditorPrefs.GetString(BLEND_MODE_MATERIAL_SCREEN_KEY, "");
+				blendModeMaterialAdditive = EditorPrefs.GetString(BLEND_MODE_MATERIAL_ADDITIVE_KEY, "");
 				autoReloadSceneSkeletons = EditorPrefs.GetBool(AUTO_RELOAD_SCENESKELETONS_KEY, SpinePreferences.DEFAULT_AUTO_RELOAD_SCENESKELETONS);
 				mecanimEventIncludeFolderName = EditorPrefs.GetBool(MECANIM_EVENT_INCLUDE_FOLDERNAME_KEY, SpinePreferences.DEFAULT_MECANIM_EVENT_INCLUDE_FOLDERNAME);
 				atlasTxtImportWarning = EditorPrefs.GetBool(ATLASTXT_WARNING_KEY, SpinePreferences.DEFAULT_ATLASTXT_WARNING);
@@ -246,6 +275,28 @@ namespace Spine.Unity.Editor {
 							EditorPrefs.SetString(TEXTURE_SETTINGS_REFERENCE_KEY, textureSettingsReference);
 						}
 					}
+
+					SpineEditorUtilities.MaterialPrefsField(ref blendModeMaterialMultiply, BLEND_MODE_MATERIAL_MULTIPLY_KEY, new GUIContent("Multiply Material", "Multiply blend mode Material template."));
+					if (string.IsNullOrEmpty(blendModeMaterialMultiply)) {
+						var blendModeMaterialMultiplyGUIDS = AssetDatabase.FindAssets(DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL);
+						if (blendModeMaterialMultiplyGUIDS.Length > 0) {
+							blendModeMaterialMultiply = AssetDatabase.GUIDToAssetPath(blendModeMaterialMultiplyGUIDS[0]);
+						}
+					}
+					SpineEditorUtilities.MaterialPrefsField(ref blendModeMaterialScreen, BLEND_MODE_MATERIAL_SCREEN_KEY, new GUIContent("Screen Material", "Screen blend mode Material template."));
+					if (string.IsNullOrEmpty(blendModeMaterialScreen)) {
+						var blendModeMaterialScreenGUIDS = AssetDatabase.FindAssets(DEFAULT_BLEND_MODE_SCREEN_MATERIAL);
+						if (blendModeMaterialScreenGUIDS.Length > 0) {
+							blendModeMaterialScreen = AssetDatabase.GUIDToAssetPath(blendModeMaterialScreenGUIDS[0]);
+						}
+					}
+					SpineEditorUtilities.MaterialPrefsField(ref blendModeMaterialAdditive, BLEND_MODE_MATERIAL_ADDITIVE_KEY, new GUIContent("Additive Material", "Additive blend mode Material template."));
+					if (string.IsNullOrEmpty(blendModeMaterialAdditive)) {
+						var blendModeMaterialAdditiveGUIDS = AssetDatabase.FindAssets(DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL);
+						if (blendModeMaterialAdditiveGUIDS.Length > 0) {
+							blendModeMaterialAdditive = AssetDatabase.GUIDToAssetPath(blendModeMaterialAdditiveGUIDS[0]);
+						}
+					}
 				}
 
 				EditorGUILayout.Space();
@@ -344,6 +395,16 @@ namespace Spine.Unity.Editor {
 			}
 		}
 
+		static void MaterialPrefsField (ref string currentValue, string editorPrefsKey, GUIContent label) {
+			EditorGUI.BeginChangeCheck();
+			EditorGUIUtility.wideMode = true;
+			var material = (EditorGUILayout.ObjectField(label, AssetDatabase.LoadAssetAtPath<Material>(currentValue), typeof(Object), false) as Material);
+			currentValue = material != null ? AssetDatabase.GetAssetPath(material) : "";
+			if (EditorGUI.EndChangeCheck()) {
+				EditorPrefs.SetString(editorPrefsKey, currentValue);
+			}
+		}
+
 		public static void FloatPropertyField (SerializedProperty property, GUIContent label, float min = float.NegativeInfinity, float max = float.PositiveInfinity) {
 			EditorGUI.BeginChangeCheck();
 			property.floatValue = EditorGUILayout.DelayedFloatField(label, property.floatValue);
@@ -357,6 +418,10 @@ namespace Spine.Unity.Editor {
 			property.stringValue = shader != null ? shader.name : fallbackShaderName;
 		}
 
+		public static void MaterialPropertyField (SerializedProperty property, GUIContent label) {
+			var material = (EditorGUILayout.ObjectField(label, AssetDatabase.LoadAssetAtPath<Material>(property.stringValue), typeof(Material), false) as Material);
+			property.stringValue = material ? AssetDatabase.GetAssetPath(material) : "";
+		}
 
 	#if NEW_PREFERENCES_SETTINGS_PROVIDER
 		public static void PresetAssetPropertyField (SerializedProperty property, GUIContent label) {

+ 63 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs

@@ -78,6 +78,65 @@ namespace Spine.Unity.Editor {
 		internal const string DEFAULT_TEXTURE_SETTINGS_REFERENCE = "";
 		public string textureSettingsReference = DEFAULT_TEXTURE_SETTINGS_REFERENCE;
 
+		public bool UsesPMAWorkflow {
+			get {
+				return IsPMAWorkflow(textureSettingsReference);
+			}
+		}
+		public static bool IsPMAWorkflow(string textureSettingsReference) {
+			if (textureSettingsReference == null)
+				return true;
+			string settingsReference = textureSettingsReference.ToLower();
+			if (settingsReference.Contains("straight") || !settingsReference.Contains("pma"))
+				return false;
+			return true;
+		}
+
+		public const string DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL = "SkeletonPMAMultiply";
+		public const string DEFAULT_BLEND_MODE_SCREEN_MATERIAL = "SkeletonPMAScreen";
+		public const string DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL = "SkeletonPMAAdditive";
+
+		public Material blendModeMaterialMultiply = null;
+		public Material blendModeMaterialScreen = null;
+		public Material blendModeMaterialAdditive = null;
+
+		public string FindPathOfAsset (string assetName) {
+			string typeSearchString = assetName;
+			string[] guids = AssetDatabase.FindAssets(typeSearchString);
+			if (guids.Length > 0) {
+				return AssetDatabase.GUIDToAssetPath(guids[0]);
+			}
+			return null;
+		}
+
+		public Material BlendModeMaterialMultiply {
+			get {
+				if (blendModeMaterialMultiply == null) {
+					string path = FindPathOfAsset(DEFAULT_BLEND_MODE_MULTIPLY_MATERIAL);
+					blendModeMaterialMultiply = AssetDatabase.LoadAssetAtPath<Material>(path);
+				}
+				return blendModeMaterialMultiply;
+			}
+		}
+		public Material BlendModeMaterialScreen {
+			get {
+				if (blendModeMaterialScreen == null) {
+					string path = FindPathOfAsset(DEFAULT_BLEND_MODE_SCREEN_MATERIAL);
+					blendModeMaterialScreen = AssetDatabase.LoadAssetAtPath<Material>(path);
+				}
+				return blendModeMaterialScreen;
+			}
+		}
+		public Material BlendModeMaterialAdditive {
+			get {
+				if (blendModeMaterialAdditive == null) {
+					string path = FindPathOfAsset(DEFAULT_BLEND_MODE_ADDITIVE_MATERIAL);
+					blendModeMaterialAdditive = AssetDatabase.LoadAssetAtPath<Material>(path);
+				}
+				return blendModeMaterialAdditive;
+			}
+		}
+
 		internal const bool DEFAULT_ATLASTXT_WARNING = true;
 		public bool atlasTxtImportWarning = DEFAULT_ATLASTXT_WARNING;
 
@@ -180,6 +239,10 @@ namespace Spine.Unity.Editor {
 							textureSettingsRef.stringValue = AssetDatabase.GUIDToAssetPath(pmaTextureSettingsReferenceGUIDS[0]);
 						}
 					}
+
+					EditorGUILayout.PropertyField(settings.FindProperty("blendModeMaterialMultiply"), new GUIContent("Multiply Material", "Multiply blend mode Material template."));
+					EditorGUILayout.PropertyField(settings.FindProperty("blendModeMaterialScreen"), new GUIContent("Screen Material", "Screen blend mode Material template."));
+					EditorGUILayout.PropertyField(settings.FindProperty("blendModeMaterialAdditive"), new GUIContent("Additive Material", "Additive blend mode Material template."));
 				}
 
 				EditorGUILayout.Space();

+ 144 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs

@@ -0,0 +1,144 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated January 1, 2020. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2020, 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 System;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+using Spine;
+
+namespace Spine.Unity {
+	[System.Serializable]
+	public class BlendModeMaterials {
+
+		[System.Serializable]
+		public class ReplacementMaterial {
+			public string pageName;
+			public Material material;
+		}
+
+		[SerializeField, HideInInspector] protected bool requiresBlendModeMaterials = false;
+		public bool applyAdditiveMaterial = false;
+
+		public List<ReplacementMaterial> additiveMaterials = new List<ReplacementMaterial>();
+		public List<ReplacementMaterial> multiplyMaterials = new List<ReplacementMaterial>();
+		public List<ReplacementMaterial> screenMaterials = new List<ReplacementMaterial>();
+
+		public bool RequiresBlendModeMaterials { get { return requiresBlendModeMaterials; } set { requiresBlendModeMaterials = value; } }
+
+	#if UNITY_EDITOR
+		public void TransferSettingsFrom (BlendModeMaterialsAsset modifierAsset) {
+			applyAdditiveMaterial = modifierAsset.applyAdditiveMaterial;
+		}
+
+		public bool UpdateBlendmodeMaterialsRequiredState (SkeletonData skeletonData) {
+			requiresBlendModeMaterials = false;
+
+			if (skeletonData == null) throw new ArgumentNullException("skeletonData");
+
+			var skinEntries = new List<Skin.SkinEntry>();
+			var slotsItems = skeletonData.Slots.Items;
+			for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
+				var slot = slotsItems[slotIndex];
+				if (slot.blendMode == BlendMode.Normal) continue;
+				if (!applyAdditiveMaterial && slot.blendMode == BlendMode.Additive) continue;
+
+				skinEntries.Clear();
+				foreach (var skin in skeletonData.Skins)
+					skin.GetAttachments(slotIndex, skinEntries);
+
+				foreach (var entry in skinEntries) {
+					if (entry.Attachment is IHasRendererObject) {
+						requiresBlendModeMaterials = true;
+						return true;
+					}
+				}
+			}
+			return false;
+		}
+	#endif
+		public void ApplyMaterials (SkeletonData skeletonData) {
+			if (skeletonData == null) throw new ArgumentNullException("skeletonData");
+			if (!requiresBlendModeMaterials)
+				return;
+
+			var skinEntries = new List<Skin.SkinEntry>();
+			var slotsItems = skeletonData.Slots.Items;
+			for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
+				var slot = slotsItems[slotIndex];
+				if (slot.blendMode == BlendMode.Normal) continue;
+				if (!applyAdditiveMaterial && slot.blendMode == BlendMode.Additive) continue;
+
+				List<ReplacementMaterial> replacementMaterials = null;
+				switch (slot.blendMode) {
+					case BlendMode.Multiply:
+						replacementMaterials = multiplyMaterials;
+						break;
+					case BlendMode.Screen:
+						replacementMaterials = screenMaterials;
+						break;
+					case BlendMode.Additive:
+						replacementMaterials = additiveMaterials;
+						break;
+				}
+				if (replacementMaterials == null)
+					continue;
+
+				skinEntries.Clear();
+				foreach (var skin in skeletonData.Skins)
+					skin.GetAttachments(slotIndex, skinEntries);
+
+				foreach (var entry in skinEntries) {
+					var renderableAttachment = entry.Attachment as IHasRendererObject;
+					if (renderableAttachment != null) {
+						renderableAttachment.RendererObject = CloneAtlasRegionWithMaterial(
+							(AtlasRegion)renderableAttachment.RendererObject, replacementMaterials);
+					}
+				}
+			}
+		}
+
+		protected AtlasRegion CloneAtlasRegionWithMaterial (AtlasRegion originalRegion, List<ReplacementMaterial> replacementMaterials) {
+			var newRegion = originalRegion.Clone();
+			Material material = null;
+			foreach (var replacement in replacementMaterials) {
+				if (replacement.pageName == originalRegion.page.name) {
+					material = replacement.material;
+					break;
+				}
+			}
+
+			AtlasPage originalPage = originalRegion.page;
+			var newPage = originalPage.Clone();
+			newPage.rendererObject = material;
+			newRegion.page = newPage;
+			return newRegion;
+		}
+	}
+}

+ 11 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterials.cs.meta

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

+ 9 - 2
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs

@@ -49,6 +49,9 @@ namespace Spine.Unity {
 		#endif
 		public TextAsset skeletonJSON;
 
+		public bool isUpgradingBlendModeMaterials = false;
+		public BlendModeMaterials blendModeMaterials = new BlendModeMaterials();
+
 		[Tooltip("Use SkeletonDataModifierAssets to apply changes to the SkeletonData after being loaded, such as apply blend mode Materials to Attachments under slots with special blend modes.")]
 		public List<SkeletonDataModifierAsset> skeletonDataModifiers = new List<SkeletonDataModifierAsset>();
 
@@ -187,10 +190,14 @@ namespace Spine.Unity {
 				return null;
 
 			if (skeletonDataModifiers != null) {
-				foreach (var m in skeletonDataModifiers) {
-					if (m != null) m.Apply(loadedSkeletonData);
+				foreach (var modifier in skeletonDataModifiers) {
+					if (modifier != null && !(isUpgradingBlendModeMaterials && modifier is BlendModeMaterialsAsset)) {
+						modifier.Apply(loadedSkeletonData);
+					}
 				}
 			}
+			if (!isUpgradingBlendModeMaterials)
+				blendModeMaterials.ApplyMaterials(loadedSkeletonData);
 
 			this.InitializeWithData(loadedSkeletonData);