From 5beb3c935261f2a020138eeab3d592ce7b60d945 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Thu, 12 Feb 2026 19:51:39 +0100 Subject: [PATCH] [unity] Added new GetRepackedSkin and GetRepackedAttachments variants which maintain blend modes. Closes #1945. --- CHANGELOG.md | 1 + .../spine-unity/Utility/AtlasUtilities.cs | 666 +++++++++++++----- spine-unity/Assets/Spine/package.json | 2 +- 3 files changed, 497 insertions(+), 172 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5db58ac8..7a7e775b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -370,6 +370,7 @@ - Spine UI Toolkit UPM package now supports PMA atlas textures. At the `SpineVisualElement` expand `Blend Mode Materials` and hit `Detect Materials` to automatically assign the proper PMA or straight alpha material at `Normal Material`. Unity minimum version increased to 6000.3 which added support for UI Toolkit materials. - Spine UI Toolkit UPM package now supports all Spine blend modes via blend mode materials and multiple materials per skeleton. Enable `Multiple Materials` (enabled by default), expand `Blend Mode Materials` and hit `Detect Materials` to automatically assign the correct PMA or straight alpha blend mode materials. - Every Spine URP shader now has an `Outline` option to switch to the respective Outline shader variant. Uses multi-pass support of newer URP versions. Requires spine-unity core package version 4.3.44 or newer due to required modifications in custom Sprite Shader GUI. + - Added new variants of `GetRepackedSkin` and `GetRepackedAttachments` supporting blend modes. These new variants take a packing configuration input struct `RepackAttachmentsSettings` which provides optional `additiveMaterialSource`, `multiplyMaterialSource` and `screenMaterialSource` properties, enabling blend mode repacking when any is non-null. Create your `RepackAttachmentsSettings` from default settings via `RepackAttachmentsSettings.Default` and then customize settings as needed. - **Deprecated** diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs index 851162216..3e2ee015d 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs @@ -236,9 +236,375 @@ namespace Spine.Unity.AttachmentTools { static List[] texturesToPackAtParam = new List[1]; static List inoutAttachments = new List(); + /// + /// Repack output struct for and . + /// Returned Material and Texture behave like new Texture2D(), thus you need to + /// call Destroy() to free resources. + /// + public struct RepackAttachmentsOutput { + /// The newly generated Material for the normal blend mode, holding the repacked texture + /// as main texture. + /// Materials and textures returned behave like new Texture2D() and need to be destroyed. + public Material outputMaterial; + /// The newly generated main repacked texture, assigned at outputMaterial.mainTexture. + /// Materials and textures returned behave like new Texture2D() and need to be destroyed. + public Texture2D outputTexture; + /// When non-null, this array will be filled with the resulting repacked texture for every property, + /// just as the main repacked texture is assigned to outputTexture. This serves to avoid unnecessary + /// allocations. + /// Materials and textures returned behave like new Texture2D() and need to be destroyed. + public Texture2D[] additionalOutputTextures; + /// The optional generated Material for additive blend mode, holding the repacked texture + /// as main texture. + /// Materials and textures returned behave like new Texture2D() and need to be destroyed. + public Material outputAdditiveMaterial; + /// The optional generated Material for multiply blend mode, holding the repacked texture + /// as main texture. + /// Materials and textures returned behave like new Texture2D() and need to be destroyed. + public Material outputMultiplyMaterial; + /// The optional generated Material for screen blend mode, holding the repacked texture + /// as main texture. + /// Materials and textures returned behave like new Texture2D() and need to be destroyed. + public Material outputScreenMaterial; + } + + /// + /// Repack configuration settings for and . + /// + public struct RepackAttachmentsSettings { + public static readonly string DefaultTextureName = "Repacked Attachments"; + + /// + /// Name used for newly created textures, materials and atlas pages. + /// + public string newAssetName; + + /// + /// Optional shader to be used, overrides the shader of materialPropertySource.shader. + /// + public Shader shader; + + /// + /// Normal blend mode Material reference. The generated normal blend mode Material copies these settings. + /// + public Material materialPropertySource; + /// + /// Additive blend mode Material reference. Optional, assign when repacking any additive blend mode + /// attachments. The generated additive blend mode Material copies these settings. + /// If all three blend mode materials additiveMaterialSource, multiplyMaterialSource and + /// screenMaterialSource are null, blend mode attachments are treated as normal blend mode and + /// repacked using a single material and atlas page. + /// + public Material additiveMaterialSource; + /// + /// Multiply blend mode Material reference. Optional, assign when repacking any multiply blend mode + /// attachments. The generated multiply blend mode Material copies these settings. + /// If all three blend mode materials additiveMaterialSource, multiplyMaterialSource and + /// screenMaterialSource are null, blend mode attachments are treated as normal blend mode and + /// repacked using a single material and atlas page. + /// + public Material multiplyMaterialSource; + /// + /// Screen blend mode Material reference. Optional, assign when repacking any screen blend mode attachments. + /// The generated screen blend mode Material copies these settings. + /// If all three blend mode materials additiveMaterialSource, multiplyMaterialSource and + /// screenMaterialSource are null, blend mode attachments are treated as normal blend mode and + /// repacked using a single material and atlas page. + /// + public Material screenMaterialSource; + + /// Max atlas size of a repacked texture. Packed attachments are scaled down to fit into a single + /// texture, not using multiple repacked texture pages. + public int maxAtlasSize; + /// Padding between packed texture regions in pixels. + public int padding; + /// Texture format of the main repacked texture. + public TextureFormat textureFormat; + /// + /// Whether mip-maps shall be generated for newly created repacked textures. + /// + public bool mipmaps; + /// When set to true, is called after + /// repacking to clear the texture cache. See remarks in and + /// for additional info. + public bool clearCache; + /// + /// When true, original non-texture-region attachments (e.g. bounding box or point attachments) are + /// attached to the new skin as-is. + /// + public bool useOriginalNonrenderables; + + /// Optional additional textures (such as normal maps) to copy while repacking. + /// To copy e.g. the main texture and normal maps, set it to + /// new int[] { Shader.PropertyToID("_BumpMap") }. + /// + public int[] additionalTexturePropertyIDsToCopy; + /// When non-null, this array will be used as TextureFormat at the Texture at the respective property. + /// When set to null or when its array size is smaller, textureFormat is used where there + /// exists no corresponding array item. + public TextureFormat[] additionalTextureFormats; + /// When non-null, this array will be used to determine whether linear or sRGB color space is + /// used at the Texture at the respective property. + /// When set to null, linear color space is assumed at every additional Texture element. + /// When e.g. packing the main texture and normal maps, assign new bool[] { true } at this parameters, + /// because normal maps use linear color space. + public bool[] additionalTextureIsLinear; + + /// Default settings providing reasonable parameters, modify according to your needs. + public static RepackAttachmentsSettings Default = new RepackAttachmentsSettings { + materialPropertySource = null, + additiveMaterialSource = null, + multiplyMaterialSource = null, + screenMaterialSource = null, + + newAssetName = "Repacked Attachments", + + maxAtlasSize = 1024, + padding = 2, + textureFormat = SpineTextureFormat, + mipmaps = UseMipMaps, + clearCache = false, + + useOriginalNonrenderables = true, + additionalTexturePropertyIDsToCopy = null, + additionalTextureFormats = null, + additionalTextureIsLinear = null + }; + } + + protected struct BlendModeAtlasPages { + public AtlasPage normalPage; + public AtlasPage additivePage; + public AtlasPage multiplyPage; + public AtlasPage screenPage; + } + /// /// Fills the outputAttachments list with new attachment objects based on the attachments in sourceAttachments, /// but mapped to a new single texture using the same material. + /// + /// This variant of GetRepackedAttachments supports repacking blend mode attachments. + /// To enable blend mode repacking, assign a reference material at either + /// , + /// or + /// of parameter . + /// Otherwise any blend mode attachments are treated as normal blend mode and repacked using a single material + /// and atlas page. + /// + /// Returned Material and Texture behave like new Texture2D(), thus you need to + /// call Destroy() to free resources. + /// This method caches necessary Texture copies for later re-use, which might steadily increase the texture + /// memory footprint when used excessively. + /// Set to true at the argument + /// or call to clear this texture cache. + /// You may want to call Resources.UnloadUnusedAssets() after that. + /// + /// The list of attachments to be repacked. + /// The List(Attachment) to populate with the newly created Attachment objects. + /// May be equal to sourceAttachments for in-place operation. + /// Repack configuration settings, see . + /// Repack output struct holding generated material and texture references for + /// potential later cleanup. + public static void GetRepackedAttachments (List sourceAttachments, List outputAttachments, + RepackAttachmentsSettings settings, ref RepackAttachmentsOutput output) { + + if (sourceAttachments == null) throw new System.ArgumentNullException("sourceAttachments"); + if (outputAttachments == null) throw new System.ArgumentNullException("outputAttachments"); + + if (settings.shader == null) + settings.shader = settings.materialPropertySource == null ? + Shader.Find("Spine/Skeleton") : settings.materialPropertySource.shader; + + output.outputTexture = null; + if (settings.additionalTexturePropertyIDsToCopy != null && settings.additionalTextureIsLinear == null) { + settings.additionalTextureIsLinear = new bool[settings.additionalTexturePropertyIDsToCopy.Length]; + for (int i = 0; i < settings.additionalTextureIsLinear.Length; ++i) { + settings.additionalTextureIsLinear[i] = true; + } + } + + // Use these to detect and use shared regions. + existingRegions.Clear(); + regionIndices.Clear(); + + // Collect all textures from original attachments. + int numTextureParamsToRepack = 1 + (settings.additionalTexturePropertyIDsToCopy == null ? + 0 : settings.additionalTexturePropertyIDsToCopy.Length); + + if (texturesToPackAtParam.Length < numTextureParamsToRepack) + Array.Resize(ref texturesToPackAtParam, numTextureParamsToRepack); + for (int i = 0; i < numTextureParamsToRepack; ++i) { + if (texturesToPackAtParam[i] != null) + texturesToPackAtParam[i].Clear(); + else + texturesToPackAtParam[i] = new List(); + } + originalRegions.Clear(); + + if (!object.ReferenceEquals(sourceAttachments, outputAttachments)) { + outputAttachments.Clear(); + outputAttachments.AddRange(sourceAttachments); + } + + int newRegionIndex = 0; + for (int attachmentIndex = 0, n = sourceAttachments.Count; attachmentIndex < n; attachmentIndex++) { + Attachment originalAttachment = sourceAttachments[attachmentIndex]; + + if (originalAttachment is IHasTextureRegion) { + MeshAttachment originalMeshAttachment = originalAttachment as MeshAttachment; + IHasTextureRegion originalTextureAttachment = (IHasTextureRegion)originalAttachment; + + Attachment newAttachment = (originalTextureAttachment.Sequence != null) ? originalAttachment : + (originalMeshAttachment != null) ? originalMeshAttachment.NewLinkedMesh() : + originalAttachment.Copy(); + IHasTextureRegion newTextureAttachment = (IHasTextureRegion)newAttachment; + AtlasRegion region = newTextureAttachment.Region as AtlasRegion; + if (region == null && originalTextureAttachment.Sequence != null) + region = (AtlasRegion)originalTextureAttachment.Sequence.Regions[0]; + + int existingIndex; + if (existingRegions.TryGetValue(region, out existingIndex)) { + regionIndices.Add(existingIndex); + } else { + existingRegions.Add(region, newRegionIndex); + Sequence originalSequence = originalTextureAttachment.Sequence; + if (originalSequence != null) { + newTextureAttachment.Sequence = new Sequence(originalSequence); + for (int i = 0, regionCount = originalSequence.Regions.Length; i < regionCount; ++i) { + AtlasRegion sequenceRegion = (AtlasRegion)originalSequence.Regions[i]; + AddRegionTexturesToPack(numTextureParamsToRepack, sequenceRegion, + settings.textureFormat, settings.mipmaps, settings.additionalTextureFormats, + settings.additionalTexturePropertyIDsToCopy, settings.additionalTextureIsLinear); + originalRegions.Add(sequenceRegion); + regionIndices.Add(newRegionIndex); + newRegionIndex++; + } + } else { + AddRegionTexturesToPack(numTextureParamsToRepack, region, + settings.textureFormat, settings.mipmaps, settings.additionalTextureFormats, + settings.additionalTexturePropertyIDsToCopy, settings.additionalTextureIsLinear); + originalRegions.Add(region); + regionIndices.Add(newRegionIndex); + newRegionIndex++; + } + } + outputAttachments[attachmentIndex] = newAttachment; + } else { + outputAttachments[attachmentIndex] = settings.useOriginalNonrenderables ? + originalAttachment : originalAttachment.Copy(); + regionIndices.Add(NonrenderingRegion); // Output attachments pairs with regionIndices list 1:1. Pad with a sentinel if the attachment doesn't have a region. + } + } + + // Rehydrate the repacked textures as a Material, Spine atlas and Spine.AtlasAttachments + Material newMaterial = new Material(settings.shader); + if (settings.materialPropertySource != null) { + newMaterial.CopyPropertiesFromMaterial(settings.materialPropertySource); + newMaterial.shaderKeywords = settings.materialPropertySource.shaderKeywords; + } + newMaterial.name = settings.newAssetName; + + Rect[] rects = null; + for (int i = 0; i < numTextureParamsToRepack; ++i) { + // Fill a new texture with the collected attachment textures. + TextureFormat format = (i > 0 && + settings.additionalTextureFormats != null && + i - 1 < settings.additionalTextureFormats.Length) ? + settings.additionalTextureFormats[i - 1] : settings.textureFormat; + bool linear = (i > 0) ? settings.additionalTextureIsLinear[i - 1] : false; + Texture2D newTexture = new Texture2D(settings.maxAtlasSize, settings.maxAtlasSize, + format, settings.mipmaps, linear); + newTexture.mipMapBias = AtlasUtilities.DefaultMipmapBias; + + List texturesToPack = texturesToPackAtParam[i]; + if (texturesToPack.Count > 0) { + Texture2D sourceTexture = texturesToPack[0]; + newTexture.CopyTextureAttributesFrom(sourceTexture); + } + newTexture.name = settings.newAssetName; + Rect[] rectsForTexParam = newTexture.PackTextures(texturesToPack.ToArray(), + settings.padding, settings.maxAtlasSize); + if (i == 0) { + rects = rectsForTexParam; + newMaterial.mainTexture = newTexture; + output.outputTexture = newTexture; + } else { + newMaterial.SetTexture(settings.additionalTexturePropertyIDsToCopy[i - 1], newTexture); + if (output.additionalOutputTextures != null && output.additionalOutputTextures.Length > i - 1) + output.additionalOutputTextures[i - 1] = newTexture; + } + } + + AtlasPage page = newMaterial.ToSpineAtlasPage(); + page.name = settings.newAssetName; + + BlendModeAtlasPages blendModePages = new BlendModeAtlasPages(); + blendModePages.normalPage = page; + + repackedRegions.Clear(); + for (int i = 0, n = originalRegions.Count; i < n; i++) { + AtlasRegion oldRegion = originalRegions[i]; + AtlasRegion newRegion = UVRectToAtlasRegion(rects[i], oldRegion, page); + repackedRegions.Add(newRegion); + } + + Material additiveMaterialSource = settings.additiveMaterialSource; + Material multiplyMaterialSource = settings.multiplyMaterialSource; + Material screenMaterialSource = settings.screenMaterialSource; + bool enableBlendModes = + (additiveMaterialSource != null) || + (multiplyMaterialSource != null) || + (screenMaterialSource != null); + Shader normalShader = settings.shader; + + // Map the cloned attachments to the repacked atlas. + for (int attachmentIndex = 0, repackedIndex = 0, n = outputAttachments.Count; + attachmentIndex < n; + ++attachmentIndex, ++repackedIndex) { + + Attachment attachment = outputAttachments[attachmentIndex]; + IHasTextureRegion textureAttachment = attachment as IHasTextureRegion; + if (textureAttachment != null) { + if (textureAttachment.Sequence != null) { + TextureRegion[] regions = textureAttachment.Sequence.Regions; + for (int r = 0, regionCount = regions.Length; r < regionCount; ++r) { + TextureRegion originalRegion = regions[r]; + TextureRegion repackedRegion = repackedRegions[regionIndices[repackedIndex++]]; + AssignBlendMode(ref repackedRegion, originalRegion, normalShader, ref blendModePages, + additiveMaterialSource, multiplyMaterialSource, screenMaterialSource); + regions[r] = repackedRegion; + } + textureAttachment.Region = regions[0]; + --repackedIndex; + } else { + TextureRegion originalRegion = textureAttachment.Region; + TextureRegion repackedRegion = repackedRegions[regionIndices[repackedIndex]]; + if (enableBlendModes) + AssignBlendMode(ref repackedRegion, originalRegion, normalShader, ref blendModePages, + additiveMaterialSource, multiplyMaterialSource, screenMaterialSource); + textureAttachment.Region = repackedRegion; + } + textureAttachment.UpdateRegion(); + } + } + + // Clean up. + if (settings.clearCache) + AtlasUtilities.ClearCache(); + + output.outputMaterial = newMaterial; + + output.outputAdditiveMaterial = blendModePages.additivePage != null ? (Material)blendModePages.additivePage.rendererObject : null; + output.outputMultiplyMaterial = blendModePages.multiplyPage != null ? (Material)blendModePages.multiplyPage.rendererObject : null; + output.outputScreenMaterial = blendModePages.screenPage != null ? (Material)blendModePages.screenPage.rendererObject : null; + } + + /// + /// Fills the outputAttachments list with new attachment objects based on the attachments in sourceAttachments, + /// but mapped to a new single texture using the same material. All blend modes are treated as normal blend mode. + /// Use if blend modes shall be retained. + /// /// Returned Material and Texture behave like new Texture2D(), thus you need to call Destroy() /// to free resources. /// This method caches necessary Texture copies for later re-use, which might steadily increase the texture memory @@ -285,7 +651,10 @@ namespace Spine.Unity.AttachmentTools { /// /// Fills the outputAttachments list with new attachment objects based on the attachments in sourceAttachments, - /// but mapped to a new single texture using the same material. + /// but mapped to a new single texture using the same material. All blend modes are treated as normal blend mode. + /// Use if blend modes shall be retained. + /// /// Returned Material and Texture behave like new Texture2D(), thus you need to call Destroy() /// to free resources. /// The list of attachments to be repacked. @@ -316,157 +685,60 @@ namespace Spine.Unity.AttachmentTools { int[] additionalTexturePropertyIDsToCopy = null, Texture2D[] additionalOutputTextures = null, TextureFormat[] additionalTextureFormats = null, bool[] additionalTextureIsLinear = null) { - if (sourceAttachments == null) throw new System.ArgumentNullException("sourceAttachments"); - if (outputAttachments == null) throw new System.ArgumentNullException("outputAttachments"); - outputTexture = null; - if (additionalTexturePropertyIDsToCopy != null && additionalTextureIsLinear == null) { - additionalTextureIsLinear = new bool[additionalTexturePropertyIDsToCopy.Length]; - for (int i = 0; i < additionalTextureIsLinear.Length; ++i) { - additionalTextureIsLinear[i] = true; + RepackAttachmentsSettings settings = new RepackAttachmentsSettings { + shader = shader, + maxAtlasSize = maxAtlasSize, + padding = padding, + textureFormat = textureFormat, + mipmaps = mipmaps, + newAssetName = newAssetName, + materialPropertySource = materialPropertySource, + clearCache = clearCache, + useOriginalNonrenderables = useOriginalNonrenderables, + additionalTexturePropertyIDsToCopy = additionalTexturePropertyIDsToCopy, + additionalTextureFormats = additionalTextureFormats, + additionalTextureIsLinear = additionalTextureIsLinear + }; + RepackAttachmentsOutput output = new RepackAttachmentsOutput(); + output.additionalOutputTextures = additionalOutputTextures; + GetRepackedAttachments(sourceAttachments, outputAttachments, settings, ref output); + + outputMaterial = output.outputMaterial; + outputTexture = output.outputTexture; + } + + private static void AssignBlendMode (ref TextureRegion repackedRegion, TextureRegion originalRegion, + Shader normalShader, ref BlendModeAtlasPages blendModePages, + Material additiveMaterialSource, Material multiplyMaterialSource, Material screenMaterialSource) { + + Material material = ((AtlasRegion)originalRegion).page.rendererObject as Material; + if (material == null) return; + + if (material.shader != normalShader) { + if (material.shader.name.Contains("Multiply")) { + ((AtlasRegion)repackedRegion).page = GetBlendModePage(ref blendModePages.multiplyPage, + blendModePages.normalPage, multiplyMaterialSource, "-Multiply"); + } else if (material.shader.name.Contains("Screen")) { + ((AtlasRegion)repackedRegion).page = GetBlendModePage(ref blendModePages.screenPage, + blendModePages.normalPage, screenMaterialSource, "-Screen"); + } else if (material.shader.name.Contains("Additive")) { + ((AtlasRegion)repackedRegion).page = GetBlendModePage(ref blendModePages.additivePage, + blendModePages.normalPage, additiveMaterialSource, "-Additive"); } } + } - // Use these to detect and use shared regions. - existingRegions.Clear(); - regionIndices.Clear(); + private static AtlasPage GetBlendModePage (ref AtlasPage targetPage, AtlasPage normalReferencePage, + Material materialSource, string nameSuffix) { - // Collect all textures from original attachments. - int numTextureParamsToRepack = 1 + (additionalTexturePropertyIDsToCopy == null ? 0 : additionalTexturePropertyIDsToCopy.Length); - additionalOutputTextures = (additionalTexturePropertyIDsToCopy == null ? null : new Texture2D[additionalTexturePropertyIDsToCopy.Length]); - if (texturesToPackAtParam.Length < numTextureParamsToRepack) - Array.Resize(ref texturesToPackAtParam, numTextureParamsToRepack); - for (int i = 0; i < numTextureParamsToRepack; ++i) { - if (texturesToPackAtParam[i] != null) - texturesToPackAtParam[i].Clear(); - else - texturesToPackAtParam[i] = new List(); + if (targetPage == null) { + targetPage = normalReferencePage.Clone(); + Material material = new Material(materialSource); + material.mainTexture = ((Material)normalReferencePage.rendererObject).mainTexture; + material.name = material.mainTexture.name + nameSuffix; + targetPage.rendererObject = material; } - originalRegions.Clear(); - - if (!object.ReferenceEquals(sourceAttachments, outputAttachments)) { - outputAttachments.Clear(); - outputAttachments.AddRange(sourceAttachments); - } - - int newRegionIndex = 0; - for (int attachmentIndex = 0, n = sourceAttachments.Count; attachmentIndex < n; attachmentIndex++) { - Attachment originalAttachment = sourceAttachments[attachmentIndex]; - - if (originalAttachment is IHasTextureRegion) { - MeshAttachment originalMeshAttachment = originalAttachment as MeshAttachment; - IHasTextureRegion originalTextureAttachment = (IHasTextureRegion)originalAttachment; - - Attachment newAttachment = (originalTextureAttachment.Sequence != null) ? originalAttachment : - (originalMeshAttachment != null) ? originalMeshAttachment.NewLinkedMesh() : - originalAttachment.Copy(); - IHasTextureRegion newTextureAttachment = (IHasTextureRegion)newAttachment; - AtlasRegion region = newTextureAttachment.Region as AtlasRegion; - if (region == null && originalTextureAttachment.Sequence != null) - region = (AtlasRegion)originalTextureAttachment.Sequence.Regions[0]; - - int existingIndex; - if (existingRegions.TryGetValue(region, out existingIndex)) { - regionIndices.Add(existingIndex); - } else { - existingRegions.Add(region, newRegionIndex); - Sequence originalSequence = originalTextureAttachment.Sequence; - if (originalSequence != null) { - newTextureAttachment.Sequence = new Sequence(originalSequence); - for (int i = 0, regionCount = originalSequence.Regions.Length; i < regionCount; ++i) { - AtlasRegion sequenceRegion = (AtlasRegion)originalSequence.Regions[i]; - AddRegionTexturesToPack(numTextureParamsToRepack, sequenceRegion, textureFormat, mipmaps, - additionalTextureFormats, additionalTexturePropertyIDsToCopy, additionalTextureIsLinear); - originalRegions.Add(sequenceRegion); - regionIndices.Add(newRegionIndex); - newRegionIndex++; - } - } else { - AddRegionTexturesToPack(numTextureParamsToRepack, region, textureFormat, mipmaps, - additionalTextureFormats, additionalTexturePropertyIDsToCopy, additionalTextureIsLinear); - originalRegions.Add(region); - regionIndices.Add(newRegionIndex); - newRegionIndex++; - } - } - outputAttachments[attachmentIndex] = newAttachment; - } else { - outputAttachments[attachmentIndex] = useOriginalNonrenderables ? originalAttachment : originalAttachment.Copy(); - regionIndices.Add(NonrenderingRegion); // Output attachments pairs with regionIndices list 1:1. Pad with a sentinel if the attachment doesn't have a region. - } - } - - // Rehydrate the repacked textures as a Material, Spine atlas and Spine.AtlasAttachments - Material newMaterial = new Material(shader); - if (materialPropertySource != null) { - newMaterial.CopyPropertiesFromMaterial(materialPropertySource); - newMaterial.shaderKeywords = materialPropertySource.shaderKeywords; - } - newMaterial.name = newAssetName; - - Rect[] rects = null; - for (int i = 0; i < numTextureParamsToRepack; ++i) { - // Fill a new texture with the collected attachment textures. - Texture2D newTexture = new Texture2D(maxAtlasSize, maxAtlasSize, - (i > 0 && additionalTextureFormats != null && i - 1 < additionalTextureFormats.Length) ? - additionalTextureFormats[i - 1] : textureFormat, - mipmaps, - (i > 0) ? additionalTextureIsLinear[i - 1] : false); - newTexture.mipMapBias = AtlasUtilities.DefaultMipmapBias; - - List texturesToPack = texturesToPackAtParam[i]; - if (texturesToPack.Count > 0) { - Texture2D sourceTexture = texturesToPack[0]; - newTexture.CopyTextureAttributesFrom(sourceTexture); - } - newTexture.name = newAssetName; - Rect[] rectsForTexParam = newTexture.PackTextures(texturesToPack.ToArray(), padding, maxAtlasSize); - if (i == 0) { - rects = rectsForTexParam; - newMaterial.mainTexture = newTexture; - outputTexture = newTexture; - } else { - newMaterial.SetTexture(additionalTexturePropertyIDsToCopy[i - 1], newTexture); - additionalOutputTextures[i - 1] = newTexture; - } - } - - AtlasPage page = newMaterial.ToSpineAtlasPage(); - page.name = newAssetName; - - repackedRegions.Clear(); - for (int i = 0, n = originalRegions.Count; i < n; i++) { - AtlasRegion oldRegion = originalRegions[i]; - AtlasRegion newRegion = UVRectToAtlasRegion(rects[i], oldRegion, page); - repackedRegions.Add(newRegion); - } - - // Map the cloned attachments to the repacked atlas. - for (int attachmentIndex = 0, repackedIndex = 0, n = outputAttachments.Count; - attachmentIndex < n; - ++attachmentIndex, ++repackedIndex) { - - Attachment attachment = outputAttachments[attachmentIndex]; - IHasTextureRegion textureAttachment = attachment as IHasTextureRegion; - if (textureAttachment != null) { - if (textureAttachment.Sequence != null) { - TextureRegion[] regions = textureAttachment.Sequence.Regions; - textureAttachment.Region = repackedRegions[regionIndices[repackedIndex]]; - for (int r = 0, regionCount = regions.Length; r < regionCount; ++r) { - regions[r] = repackedRegions[regionIndices[repackedIndex++]]; - } - --repackedIndex; - } else { - textureAttachment.Region = repackedRegions[regionIndices[repackedIndex]]; - } - textureAttachment.UpdateRegion(); - } - } - - // Clean up. - if (clearCache) - AtlasUtilities.ClearCache(); - - outputMaterial = newMaterial; + return targetPage; } private static void AddRegionTexturesToPack (int numTextureParamsToRepack, AtlasRegion region, @@ -485,7 +757,59 @@ namespace Spine.Unity.AttachmentTools { /// /// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas - /// comprised of all the regions from the original skin. + /// comprised of all the regions from the original skin. Supports blend modes if configured accordingly. + /// + /// + /// GetRepackedSkin is an expensive operation, preferably call it at level load time. + /// No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments + /// using them. Returned Material and Texture behave like new Texture2D(), thus you + /// need to call Destroy() to free resources. + /// This method caches necessary Texture copies for later re-use, which might steadily increase the texture memory + /// footprint when used excessively. + /// Set to true at the argument + /// or call to clear this texture cache. + /// You may want to call Resources.UnloadUnusedAssets() after that. + /// + /// This variant of GetRepackedSkin supports repacking blend mode attachments. + /// To enable blend mode repacking, assign a reference material at either + /// , + /// or + /// of parameter . + /// Otherwise any blend mode attachments are treated as normal blend mode and repacked using a single material + /// and atlas page. + /// + /// Repack configuration settings, see . + /// Repack output struct holding generated material and texture references for + /// potential later cleanup. + public static Skin GetRepackedSkin (this Skin o, string newName, + RepackAttachmentsSettings settings, ref RepackAttachmentsOutput output) { + + if (o == null) throw new System.NullReferenceException("Skin was null"); + ICollection skinAttachments = o.Attachments; + Skin newSkin = new Skin(newName); + + newSkin.Bones.AddRange(o.Bones); + newSkin.Constraints.AddRange(o.Constraints); + + inoutAttachments.Clear(); + foreach (Skin.SkinEntry entry in skinAttachments) { + inoutAttachments.Add(entry.Attachment); + } + GetRepackedAttachments(inoutAttachments, inoutAttachments, settings, ref output); + int i = 0; + foreach (Skin.SkinEntry originalSkinEntry in skinAttachments) { + Attachment newAttachment = inoutAttachments[i++]; + newSkin.SetAttachment(originalSkinEntry.SlotIndex, originalSkinEntry.Name, newAttachment); + } + return newSkin; + } + + /// + /// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas + /// comprised of all the regions from the original skin. All blend modes are treated as normal blend mode. + /// Use if + /// blend modes shall be retained. + /// /// GetRepackedSkin is an expensive operation, preferably call it at level load time. /// No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them. /// Returned Material and Texture behave like new Texture2D(), thus you need to call Destroy() @@ -526,36 +850,36 @@ namespace Spine.Unity.AttachmentTools { /// /// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas - /// comprised of all the regions from the original skin. - /// See documentation of for details. + /// comprised of all the regions from the original skin. All blend modes are treated as normal blend mode. + /// + /// See documentation of for details. public static Skin GetRepackedSkin (this Skin o, string newName, Shader shader, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, Material materialPropertySource = null, bool clearCache = false, bool useOriginalNonrenderables = true, int[] additionalTexturePropertyIDsToCopy = null, Texture2D[] additionalOutputTextures = null, TextureFormat[] additionalTextureFormats = null, bool[] additionalTextureIsLinear = null) { - outputTexture = null; + RepackAttachmentsSettings settings = new RepackAttachmentsSettings { + shader = shader, + maxAtlasSize = maxAtlasSize, + padding = padding, + textureFormat = textureFormat, + mipmaps = mipmaps, + newAssetName = newName, + materialPropertySource = materialPropertySource, + clearCache = clearCache, + useOriginalNonrenderables = useOriginalNonrenderables, + additionalTexturePropertyIDsToCopy = additionalTexturePropertyIDsToCopy, + additionalTextureFormats = additionalTextureFormats, + additionalTextureIsLinear = additionalTextureIsLinear + }; + RepackAttachmentsOutput output = new RepackAttachmentsOutput(); + output.additionalOutputTextures = additionalOutputTextures; - if (o == null) throw new System.NullReferenceException("Skin was null"); - ICollection skinAttachments = o.Attachments; - Skin newSkin = new Skin(newName); - - newSkin.Bones.AddRange(o.Bones); - newSkin.Constraints.AddRange(o.Constraints); - - inoutAttachments.Clear(); - foreach (Skin.SkinEntry entry in skinAttachments) { - inoutAttachments.Add(entry.Attachment); - } - GetRepackedAttachments(inoutAttachments, inoutAttachments, materialPropertySource, out outputMaterial, out outputTexture, - maxAtlasSize, padding, textureFormat, mipmaps, newName, clearCache, useOriginalNonrenderables, - additionalTexturePropertyIDsToCopy, additionalOutputTextures, additionalTextureFormats, additionalTextureIsLinear); - int i = 0; - foreach (Skin.SkinEntry originalSkinEntry in skinAttachments) { - Attachment newAttachment = inoutAttachments[i++]; - newSkin.SetAttachment(originalSkinEntry.SlotIndex, originalSkinEntry.Name, newAttachment); - } - return newSkin; + Skin repackedSkin = o.GetRepackedSkin(newName, settings, ref output); + outputMaterial = output.outputMaterial; + outputTexture = output.outputTexture; + return repackedSkin; } public static Sprite ToSprite (this AtlasRegion ar, float pixelsPerUnit = 100) { diff --git a/spine-unity/Assets/Spine/package.json b/spine-unity/Assets/Spine/package.json index 138a42414..7ace57b2a 100644 --- a/spine-unity/Assets/Spine/package.json +++ b/spine-unity/Assets/Spine/package.json @@ -2,7 +2,7 @@ "name": "com.esotericsoftware.spine.spine-unity", "displayName": "spine-unity Runtime", "description": "This plugin provides the spine-unity runtime core and examples. Spine Examples can be installed via the Samples tab.", - "version": "4.3.44", + "version": "4.3.45", "unity": "2018.3", "author": { "name": "Esoteric Software",