[unity] Added new GetRepackedSkin and GetRepackedAttachments variants which maintain blend modes. Closes #1945.

This commit is contained in:
Harald Csaszar 2026-02-12 19:51:39 +01:00
parent ab79c143c3
commit 5beb3c9352
3 changed files with 497 additions and 172 deletions

View File

@ -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 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. - 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. - 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** - **Deprecated**

View File

@ -236,9 +236,375 @@ namespace Spine.Unity.AttachmentTools {
static List<Texture2D>[] texturesToPackAtParam = new List<Texture2D>[1]; static List<Texture2D>[] texturesToPackAtParam = new List<Texture2D>[1];
static List<Attachment> inoutAttachments = new List<Attachment>(); static List<Attachment> inoutAttachments = new List<Attachment>();
/// <summary>
/// Repack output struct for <see cref="GetRepackedAttachments"/> and <see cref="GetRepackedSkin"/>.
/// Returned <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you need to
/// call <c>Destroy()</c> to free resources.
/// </summary>
public struct RepackAttachmentsOutput {
/// <summary>The newly generated Material for the normal blend mode, holding the repacked texture
/// as main texture.
/// Materials and textures returned behave like <c>new Texture2D()</c> and need to be destroyed.</summary>
public Material outputMaterial;
/// <summary>The newly generated main repacked texture, assigned at <c>outputMaterial.mainTexture</c>.
/// Materials and textures returned behave like <c>new Texture2D()</c> and need to be destroyed.</summary>
public Texture2D outputTexture;
/// <summary>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 <c>outputTexture</c>. This serves to avoid unnecessary
/// allocations.
/// Materials and textures returned behave like <c>new Texture2D()</c> and need to be destroyed.</summary>
public Texture2D[] additionalOutputTextures;
/// <summary>The optional generated Material for additive blend mode, holding the repacked texture
/// as main texture.
/// Materials and textures returned behave like <c>new Texture2D()</c> and need to be destroyed.</summary>
public Material outputAdditiveMaterial;
/// <summary>The optional generated Material for multiply blend mode, holding the repacked texture
/// as main texture.
/// Materials and textures returned behave like <c>new Texture2D()</c> and need to be destroyed.</summary>
public Material outputMultiplyMaterial;
/// <summary>The optional generated Material for screen blend mode, holding the repacked texture
/// as main texture.
/// Materials and textures returned behave like <c>new Texture2D()</c> and need to be destroyed.</summary>
public Material outputScreenMaterial;
}
/// <summary>
/// Repack configuration settings for <see cref="GetRepackedAttachments"/> and <see cref="GetRepackedSkin"/>.
/// </summary>
public struct RepackAttachmentsSettings {
public static readonly string DefaultTextureName = "Repacked Attachments";
/// <summary>
/// Name used for newly created textures, materials and atlas pages.
/// </summary>
public string newAssetName;
/// <summary>
/// Optional shader to be used, overrides the shader of <c>materialPropertySource.shader</c>.
/// </summary>
public Shader shader;
/// <summary>
/// Normal blend mode Material reference. The generated normal blend mode Material copies these settings.
/// </summary>
public Material materialPropertySource;
/// <summary>
/// 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 <c>additiveMaterialSource</c>, <c>multiplyMaterialSource</c> and
/// <c>screenMaterialSource</c> are null, blend mode attachments are treated as normal blend mode and
/// repacked using a single material and atlas page.
/// </summary>
public Material additiveMaterialSource;
/// <summary>
/// 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 <c>additiveMaterialSource</c>, <c>multiplyMaterialSource</c> and
/// <c>screenMaterialSource</c> are null, blend mode attachments are treated as normal blend mode and
/// repacked using a single material and atlas page.
/// </summary>
public Material multiplyMaterialSource;
/// <summary>
/// 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 <c>additiveMaterialSource</c>, <c>multiplyMaterialSource</c> and
/// <c>screenMaterialSource</c> are null, blend mode attachments are treated as normal blend mode and
/// repacked using a single material and atlas page.
/// </summary>
public Material screenMaterialSource;
/// <summary>Max atlas size of a repacked texture. Packed attachments are scaled down to fit into a single
/// texture, not using multiple repacked texture pages.</summary>
public int maxAtlasSize;
/// <summary>Padding between packed texture regions in pixels.</summary>
public int padding;
/// <summary>Texture format of the main repacked texture.</summary>
public TextureFormat textureFormat;
/// <summary>
/// Whether mip-maps shall be generated for newly created repacked textures.
/// </summary>
public bool mipmaps;
/// <summary>When set to <c>true</c>, <see cref="AtlasUtilities.ClearCache()"/> is called after
/// repacking to clear the texture cache. See remarks in <see cref="GetRepackedAttachments"/> and
/// <see cref="GetRepackedSkin"/> for additional info.</summary>
public bool clearCache;
/// <summary>
/// When <c>true</c>, original non-texture-region attachments (e.g. bounding box or point attachments) are
/// attached to the new skin as-is.
/// </summary>
public bool useOriginalNonrenderables;
/// <summary>Optional additional textures (such as normal maps) to copy while repacking.
/// To copy e.g. the main texture and normal maps, set it to
/// <c>new int[] { Shader.PropertyToID("_BumpMap") }</c>.
/// </summary>
public int[] additionalTexturePropertyIDsToCopy;
/// <summary>When non-null, this array will be used as <c>TextureFormat</c> at the Texture at the respective property.
/// When set to <c>null</c> or when its array size is smaller, <c>textureFormat</c> is used where there
/// exists no corresponding array item.</summary>
public TextureFormat[] additionalTextureFormats;
/// <summary>When non-null, this array will be used to determine whether <c>linear</c> or <c>sRGB</c> color space is
/// used at the Texture at the respective property.
/// When set to <c>null</c>, <c>linear</c> color space is assumed at every additional Texture element.
/// When e.g. packing the main texture and normal maps, assign <c>new bool[] { true }</c> at this parameters,
/// because normal maps use linear color space.</summary>
public bool[] additionalTextureIsLinear;
/// <summary>Default settings providing reasonable parameters, modify according to your needs.</summary>
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;
}
/// <summary> /// <summary>
/// Fills the outputAttachments list with new attachment objects based on the attachments in sourceAttachments, /// 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.</summary> /// but mapped to a new single texture using the same material.</summary>
/// <remarks>
/// This variant of <c>GetRepackedAttachments</c> supports repacking blend mode attachments.
/// To enable blend mode repacking, assign a reference material at either
/// <see cref="RepackAttachmentsSettings.additiveMaterialSource"/>,
/// <see cref="RepackAttachmentsSettings.multiplyMaterialSource"/> or
/// <see cref="RepackAttachmentsSettings.screenMaterialSource"/> of parameter <paramref name="settings"/>.
/// Otherwise any blend mode attachments are treated as normal blend mode and repacked using a single material
/// and atlas page.
/// </remarks>
/// <remarks>Returned <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you need to
/// call <c>Destroy()</c> 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 <see cref="RepackAttachmentsSettings.clearCache"/> to <c>true</c> at the argument
/// <paramref name="settings"/> or call <see cref="AtlasUtilities.ClearCache()"/> to clear this texture cache.
/// You may want to call <c>Resources.UnloadUnusedAssets()</c> after that.
/// </remarks>
/// <param name="sourceAttachments">The list of attachments to be repacked.</param>
/// <param name = "outputAttachments">The List(Attachment) to populate with the newly created Attachment objects.
/// May be equal to <c>sourceAttachments</c> for in-place operation.</param>
/// <param name="settings">Repack configuration settings, see <see cref="RepackAttachmentsSettings"/>.</param>
/// <param name="output">Repack output struct holding generated material and texture references for
/// potential later cleanup.</param>
public static void GetRepackedAttachments (List<Attachment> sourceAttachments, List<Attachment> 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<Texture2D>();
}
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<Texture2D> 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;
}
/// <summary>
/// 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 <see cref="GetRepackedAttachments(List{Attachment}, List{Attachment}, RepackAttachmentsSettings,
/// ref RepackAttachmentsOutput)"/> if blend modes shall be retained.
/// </summary>
/// <remarks>Returned <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you need to call <c>Destroy()</c> /// <remarks>Returned <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you need to call <c>Destroy()</c>
/// to free resources. /// to free resources.
/// This method caches necessary Texture copies for later re-use, which might steadily increase the texture memory /// 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 {
/// <summary> /// <summary>
/// Fills the outputAttachments list with new attachment objects based on the attachments in sourceAttachments, /// 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.</summary> /// but mapped to a new single texture using the same material. All blend modes are treated as normal blend mode.
/// Use <see cref="GetRepackedAttachments(List{Attachment}, List{Attachment}, RepackAttachmentsSettings,
/// ref RepackAttachmentsOutput)"/> if blend modes shall be retained.
/// </summary>
/// <remarks>Returned <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you need to call <c>Destroy()</c> /// <remarks>Returned <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you need to call <c>Destroy()</c>
/// to free resources.</remarks> /// to free resources.</remarks>
/// <param name="sourceAttachments">The list of attachments to be repacked.</param> /// <param name="sourceAttachments">The list of attachments to be repacked.</param>
@ -316,157 +685,60 @@ namespace Spine.Unity.AttachmentTools {
int[] additionalTexturePropertyIDsToCopy = null, Texture2D[] additionalOutputTextures = null, int[] additionalTexturePropertyIDsToCopy = null, Texture2D[] additionalOutputTextures = null,
TextureFormat[] additionalTextureFormats = null, bool[] additionalTextureIsLinear = null) { TextureFormat[] additionalTextureFormats = null, bool[] additionalTextureIsLinear = null) {
if (sourceAttachments == null) throw new System.ArgumentNullException("sourceAttachments"); RepackAttachmentsSettings settings = new RepackAttachmentsSettings {
if (outputAttachments == null) throw new System.ArgumentNullException("outputAttachments"); shader = shader,
outputTexture = null; maxAtlasSize = maxAtlasSize,
if (additionalTexturePropertyIDsToCopy != null && additionalTextureIsLinear == null) { padding = padding,
additionalTextureIsLinear = new bool[additionalTexturePropertyIDsToCopy.Length]; textureFormat = textureFormat,
for (int i = 0; i < additionalTextureIsLinear.Length; ++i) { mipmaps = mipmaps,
additionalTextureIsLinear[i] = true; 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. private static AtlasPage GetBlendModePage (ref AtlasPage targetPage, AtlasPage normalReferencePage,
existingRegions.Clear(); Material materialSource, string nameSuffix) {
regionIndices.Clear();
// Collect all textures from original attachments. if (targetPage == null) {
int numTextureParamsToRepack = 1 + (additionalTexturePropertyIDsToCopy == null ? 0 : additionalTexturePropertyIDsToCopy.Length); targetPage = normalReferencePage.Clone();
additionalOutputTextures = (additionalTexturePropertyIDsToCopy == null ? null : new Texture2D[additionalTexturePropertyIDsToCopy.Length]); Material material = new Material(materialSource);
if (texturesToPackAtParam.Length < numTextureParamsToRepack) material.mainTexture = ((Material)normalReferencePage.rendererObject).mainTexture;
Array.Resize(ref texturesToPackAtParam, numTextureParamsToRepack); material.name = material.mainTexture.name + nameSuffix;
for (int i = 0; i < numTextureParamsToRepack; ++i) { targetPage.rendererObject = material;
if (texturesToPackAtParam[i] != null)
texturesToPackAtParam[i].Clear();
else
texturesToPackAtParam[i] = new List<Texture2D>();
} }
originalRegions.Clear(); return targetPage;
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<Texture2D> 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;
} }
private static void AddRegionTexturesToPack (int numTextureParamsToRepack, AtlasRegion region, private static void AddRegionTexturesToPack (int numTextureParamsToRepack, AtlasRegion region,
@ -485,7 +757,59 @@ namespace Spine.Unity.AttachmentTools {
/// <summary> /// <summary>
/// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas /// 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.</summary> /// comprised of all the regions from the original skin. Supports blend modes if configured accordingly.
/// </summary>
/// <remarks>
/// <para>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 <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you
/// need to call <c>Destroy()</c> 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 <see cref="RepackAttachmentsSettings.clearCache"/> to <c>true</c> at the argument
/// <paramref name="settings"/> or call <see cref="AtlasUtilities.ClearCache()"/> to clear this texture cache.
/// You may want to call <c>Resources.UnloadUnusedAssets()</c> after that.
/// </para><para>
/// This variant of <c>GetRepackedSkin</c> supports repacking blend mode attachments.
/// To enable blend mode repacking, assign a reference material at either
/// <see cref="RepackAttachmentsSettings.additiveMaterialSource"/>,
/// <see cref="RepackAttachmentsSettings.multiplyMaterialSource"/> or
/// <see cref="RepackAttachmentsSettings.screenMaterialSource"/> of parameter <paramref name="settings"/>.
/// Otherwise any blend mode attachments are treated as normal blend mode and repacked using a single material
/// and atlas page.</para>
/// </remarks>
/// <param name="settings">Repack configuration settings, see <see cref="RepackAttachmentsSettings"/>.</param>
/// <param name="output">Repack output struct holding generated material and texture references for
/// potential later cleanup.</param>
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<Skin.SkinEntry> 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;
}
/// <summary>
/// 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 <see cref="GetRepackedSkin(Skin, string, RepackAttachmentsSettings, ref RepackAttachmentsOutput)"/> if
/// blend modes shall be retained.
/// </summary>
/// <remarks>GetRepackedSkin is an expensive operation, preferably call it at level load time. /// <remarks>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. /// No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.
/// Returned <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you need to call <c>Destroy()</c> /// Returned <c>Material</c> and <c>Texture</c> behave like <c>new Texture2D()</c>, thus you need to call <c>Destroy()</c>
@ -526,36 +850,36 @@ namespace Spine.Unity.AttachmentTools {
/// <summary> /// <summary>
/// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas /// 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.</summary> /// comprised of all the regions from the original skin. All blend modes are treated as normal blend mode.
/// See documentation of <see cref="GetRepackedSkin"/> for details. /// </summary>
/// <remarks>See documentation of <see cref="GetRepackedSkin"/> for details.</remarks>
public static Skin GetRepackedSkin (this Skin o, string newName, Shader shader, out Material outputMaterial, out Texture2D outputTexture, 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, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps,
Material materialPropertySource = null, bool clearCache = false, bool useOriginalNonrenderables = true, Material materialPropertySource = null, bool clearCache = false, bool useOriginalNonrenderables = true,
int[] additionalTexturePropertyIDsToCopy = null, Texture2D[] additionalOutputTextures = null, int[] additionalTexturePropertyIDsToCopy = null, Texture2D[] additionalOutputTextures = null,
TextureFormat[] additionalTextureFormats = null, bool[] additionalTextureIsLinear = 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"); Skin repackedSkin = o.GetRepackedSkin(newName, settings, ref output);
ICollection<Skin.SkinEntry> skinAttachments = o.Attachments; outputMaterial = output.outputMaterial;
Skin newSkin = new Skin(newName); outputTexture = output.outputTexture;
return repackedSkin;
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;
} }
public static Sprite ToSprite (this AtlasRegion ar, float pixelsPerUnit = 100) { public static Sprite ToSprite (this AtlasRegion ar, float pixelsPerUnit = 100) {

View File

@ -2,7 +2,7 @@
"name": "com.esotericsoftware.spine.spine-unity", "name": "com.esotericsoftware.spine.spine-unity",
"displayName": "spine-unity Runtime", "displayName": "spine-unity Runtime",
"description": "This plugin provides the spine-unity runtime core and examples. Spine Examples can be installed via the Samples tab.", "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", "unity": "2018.3",
"author": { "author": {
"name": "Esoteric Software", "name": "Esoteric Software",