Texture Array Export on 2020.2+

* Resolves #425
* Resolves #426
This commit is contained in:
Jeremy Pritts 2023-06-08 10:01:25 -04:00
parent 829559072a
commit 3006a70d5c
8 changed files with 334 additions and 32 deletions

View File

@ -254,6 +254,15 @@ namespace AssetRipper.Export.UnityProjects
projectExporter.OverrideExporter<ISpriteAtlas>(spriteExporter);
}
//Texture Array exporters
if (Settings.Version.IsGreaterEqual(2020, 2))
{
TextureArrayAssetExporter textureArrayExporter = new(Settings);
projectExporter.OverrideExporter<ICubemapArray>(textureArrayExporter);
projectExporter.OverrideExporter<ITexture2DArray>(textureArrayExporter);
projectExporter.OverrideExporter<ITexture3D>(textureArrayExporter);
}
//Shader exporters
projectExporter.OverrideExporter<IShader>(Settings.ShaderExportMode switch
{

View File

@ -1,11 +1,16 @@
using AssetRipper.Assets.Export;
using AssetRipper.Assets;
using AssetRipper.Assets.Export;
using AssetRipper.Import.Logging;
using AssetRipper.SourceGenerated.Classes.ClassID_1006;
using AssetRipper.SourceGenerated.Classes.ClassID_1055;
using AssetRipper.SourceGenerated.Classes.ClassID_117;
using AssetRipper.SourceGenerated.Classes.ClassID_187;
using AssetRipper.SourceGenerated.Classes.ClassID_188;
using AssetRipper.SourceGenerated.Classes.ClassID_28;
using AssetRipper.SourceGenerated.Classes.ClassID_89;
using AssetRipper.SourceGenerated.Enums;
using AssetRipper.SourceGenerated.Extensions;
using AssetRipper.SourceGenerated.Subclasses.GLTextureSettings;
using AssetRipper.SourceGenerated.Subclasses.TextureImporterPlatformSettings;
using System.Numerics;
@ -13,40 +18,39 @@ namespace AssetRipper.Export.UnityProjects.Textures
{
public static class ImporterFactory
{
public static ITextureImporter GenerateTextureImporter(IExportContainer container, ITexture2D origin)
public static ITextureImporter GenerateTextureImporter(IExportContainer container, IUnityObjectBase origin)
{
TextureImporterData data = new TextureImporterData(origin);
ITextureImporter instance = TextureImporterFactory.CreateAsset(container.ExportVersion, container.File);
instance.MipMaps_C1006.EnableMipMap = (origin.Has_MipCount_C28() && origin.MipCount_C28 > 1 || origin.Has_MipMap_C28() && origin.MipMap_C28) ? 1 : 0;
instance.MipMaps_C1006.SRGBTexture = origin.ColorSpace_C28 == (int)ColorSpace.Linear ? 1 : 0;
instance.MipMaps_C1006.EnableMipMap = data.EnableMipMap ? 1 : 0;
instance.MipMaps_C1006.SRGBTexture = data.SRGBTexture ? 1 : 0;
instance.MipMaps_C1006.AlphaTestReferenceValue = 0.5f;
instance.MipMaps_C1006.MipMapFadeDistanceStart = 1;
instance.MipMaps_C1006.MipMapFadeDistanceEnd = 3;
instance.BumpMap_C1006.HeightScale = .25f;
instance.GenerateCubemap_C1006 = (int)TextureImporterGenerateCubemap.AutoCubemap;
instance.StreamingMipmaps_C1006 = origin.StreamingMipmaps_C28 ? 1 : 0;
instance.StreamingMipmapsPriority_C1006 = origin.StreamingMipmapsPriority_C28;
instance.IsReadable_C1006 = origin.IsReadable_C28 ? 1 : 0;
instance.Format_C1006 = origin.Format_C28;
instance.MaxTextureSize_C1006 = CalculateMaxTextureSize(origin.Width_C28, origin.Height_C28);
instance.TextureSettings_C1006.CopyValues(origin.TextureSettings_C28);
instance.NPOTScale_C1006 = (int)TextureImporterNPOTScale.ToNearest; // Default texture importer settings uses this value, and cubemaps appear to not work when it's None
instance.GenerateCubemap_C1006E = TextureImporterGenerateCubemap.AutoCubemap;
instance.StreamingMipmaps_C1006 = data.StreamingMipmaps ? 1 : 0;
instance.StreamingMipmapsPriority_C1006 = data.StreamingMipmapsPriority;
instance.IsReadable_C1006 = data.IsReadable ? 1 : 0;
instance.Format_C1006 = (int)data.Format;
instance.MaxTextureSize_C1006 = data.MaxTextureSize;
instance.TextureSettings_C1006.CopyValues(data.TextureSettings);
instance.NPOTScale_C1006E = TextureImporterNPOTScale.ToNearest; // Default texture importer settings uses this value, and cubemaps appear to not work when it's None
instance.CompressionQuality_C1006 = 50;
instance.SetSwizzle(TextureImporterSwizzle.R, TextureImporterSwizzle.G, TextureImporterSwizzle.B, TextureImporterSwizzle.A);
instance.SpriteMode_C1006 = (int)SpriteImportMode.Single;
instance.SpriteMode_C1006E = SpriteImportMode.Single;
instance.SpriteExtrude_C1006 = 1;
instance.SpriteMeshType_C1006 = (int)SpriteMeshType.Tight;
instance.SpriteMeshType_C1006E = SpriteMeshType.Tight;
instance.SpritePivot_C1006?.SetValues(0.5f, 0.5f);
instance.SpritePixelsToUnits_C1006 = 100.0f;
instance.SpriteGenerateFallbackPhysicsShape_C1006 = 1;
instance.AlphaUsage_C1006 = (int)TextureImporterAlphaSource.FromInput;
instance.AlphaUsage_C1006E = TextureImporterAlphaSource.FromInput;
instance.AlphaIsTransparency_C1006 = 1;
instance.SpriteTessellationDetail_C1006 = -1;
instance.TextureType_C1006 = GetTextureTypeFromLightmapFormat(origin);
instance.TextureShape_C1006 = origin is ICubemap
? (int)TextureImporterShape.TextureCube
: (int)TextureImporterShape.Texture2D;
instance.TextureType_C1006E = data.TextureType;
instance.TextureShape_C1006E = data.TextureShape;
ITextureImporterPlatformSettings platformSettings = instance.PlatformSettings_C1006.AddNew();
platformSettings.BuildTarget.String = "DefaultTexturePlatform";
@ -67,18 +71,100 @@ namespace AssetRipper.Export.UnityProjects.Textures
}
return instance;
}
static int CalculateMaxTextureSize(int width, int height)
{
uint maxSideLength = (uint)Math.Max(width, height);
return Math.Max(2048, (int)BitOperations.RoundUpToPowerOf2(maxSideLength));
}
private static int CalculateMaxTextureSize(int width, int height)
{
uint maxSideLength = (uint)Math.Max(width, height);
return Math.Max(2048, (int)BitOperations.RoundUpToPowerOf2(maxSideLength));
}
static int GetTextureTypeFromLightmapFormat(ITexture2D origin)
private readonly ref struct TextureImporterData
{
public bool EnableMipMap { get; }
public bool SRGBTexture { get; }
public bool StreamingMipmaps { get; }
public int StreamingMipmapsPriority { get; }
public bool IsReadable { get; }
public TextureFormat Format { get; }
public int MaxTextureSize { get; }
public IGLTextureSettings? TextureSettings { get; }
public TextureImporterType TextureType { get; }
public TextureImporterShape TextureShape { get; }
public TextureImporterData(IUnityObjectBase asset)
{
return ((TextureUsageMode)origin.LightmapFormat_C28).IsNormalmap()
? (int)TextureImporterType.NormalMap
: (int)TextureImporterType.Default;
switch (asset)
{
case ITexture2D texture2D:
{
EnableMipMap = texture2D.Has_MipCount_C28() && texture2D.MipCount_C28 > 1 || texture2D.Has_MipMap_C28() && texture2D.MipMap_C28;
SRGBTexture = texture2D.ColorSpace_C28 == (int)ColorSpace.Linear;
StreamingMipmaps = texture2D.StreamingMipmaps_C28;
StreamingMipmapsPriority = texture2D.StreamingMipmapsPriority_C28;
IsReadable = texture2D.IsReadable_C28;
Format = texture2D.Format_C28E;
MaxTextureSize = CalculateMaxTextureSize(texture2D.Width_C28, texture2D.Height_C28);
TextureSettings = texture2D.TextureSettings_C28;
TextureType = ((TextureUsageMode)texture2D.LightmapFormat_C28).IsNormalmap()
? TextureImporterType.NormalMap
: TextureImporterType.Default;
TextureShape = texture2D is ICubemap
? TextureImporterShape.TextureCube
: TextureImporterShape.Texture2D;
}
break;
case ITexture2DArray texture2DArray:
{
EnableMipMap = texture2DArray.MipCount_C187 > 1;
SRGBTexture = texture2DArray.ColorSpace_C187E == ColorSpace.Linear;
StreamingMipmaps = false;
StreamingMipmapsPriority = default;
IsReadable = texture2DArray.IsReadable_C187;
Format = texture2DArray.Format_C187E;
MaxTextureSize = CalculateMaxTextureSize(texture2DArray.Width_C187, texture2DArray.Height_C187);
TextureSettings = texture2DArray.TextureSettings_C187;
TextureType = texture2DArray.Has_UsageMode_C187() && ((TextureUsageMode)texture2DArray.UsageMode_C187).IsNormalmap()
? TextureImporterType.NormalMap
: TextureImporterType.Default;
TextureShape = TextureImporterShape.Texture2DArray;
}
break;
case ICubemapArray cubemapArray:
{
EnableMipMap = cubemapArray.MipCount_C188 > 1;
SRGBTexture = cubemapArray.ColorSpace_C188 == (int)ColorSpace.Linear;
StreamingMipmaps = false;
StreamingMipmapsPriority = default;
IsReadable = cubemapArray.IsReadable_C188;
Format = cubemapArray.Format_C188E;
MaxTextureSize = CalculateMaxTextureSize(cubemapArray.Width_C188, cubemapArray.Width_C188);
TextureSettings = cubemapArray.TextureSettings_C188;
TextureType = cubemapArray.Has_UsageMode_C188() && ((TextureUsageMode)cubemapArray.UsageMode_C188).IsNormalmap()
? TextureImporterType.NormalMap
: TextureImporterType.Default;
TextureShape = TextureImporterShape.Texture2DArray;//Maybe this should be TextureCube
}
break;
case ITexture3D texture3D:
{
EnableMipMap = texture3D.Has_MipCount_C117() && texture3D.MipCount_C117 > 1 || texture3D.Has_MipMap_C117() && texture3D.MipMap_C117;
SRGBTexture = texture3D.ColorSpace_C117 == (int)ColorSpace.Linear;
StreamingMipmaps = false;
StreamingMipmapsPriority = default;
IsReadable = texture3D.IsReadable_C117;
Format = texture3D.GetTextureFormat();
MaxTextureSize = CalculateMaxTextureSize(texture3D.Width_C117, texture3D.Height_C117);
TextureSettings = texture3D.TextureSettings_C117;
TextureType = texture3D.GetLightmapFormat().IsNormalmap()
? TextureImporterType.NormalMap
: TextureImporterType.Default;
TextureShape = TextureImporterShape.Texture3D;
}
break;
default:
throw new ArgumentException($"Asset type not supported: {asset.GetType().Name}", nameof(asset));
}
}
}

View File

@ -0,0 +1,23 @@
using AssetRipper.Assets;
using AssetRipper.Assets.Export;
using AssetRipper.Export.UnityProjects.Configuration;
using AssetRipper.Export.UnityProjects.Project.Collections;
namespace AssetRipper.Export.UnityProjects.Textures;
public sealed class TextureArrayAssetExportCollection : AssetExportCollection
{
public TextureArrayAssetExportCollection(TextureArrayAssetExporter assetExporter, IUnityObjectBase asset) : base(assetExporter, asset)
{
}
protected override string GetExportExtension(IUnityObjectBase asset)
{
return ((TextureArrayAssetExporter)AssetExporter).ImageExportFormat.GetFileExtension();
}
protected override IUnityObjectBase CreateImporter(IExportContainer container)
{
return ImporterFactory.GenerateTextureImporter(container, Asset);
}
}

View File

@ -0,0 +1,90 @@
using AssetRipper.Assets;
using AssetRipper.Assets.Collections;
using AssetRipper.Assets.Export;
using AssetRipper.Export.UnityProjects.Configuration;
using AssetRipper.Export.UnityProjects.Project.Exporters;
using AssetRipper.Export.UnityProjects.Utils;
using AssetRipper.Import.Logging;
using AssetRipper.SourceGenerated.Classes.ClassID_117;
using AssetRipper.SourceGenerated.Classes.ClassID_187;
using AssetRipper.SourceGenerated.Classes.ClassID_188;
using AssetRipper.SourceGenerated.Classes.ClassID_28;
using AssetRipper.SourceGenerated.Extensions;
using AssetRipper.SourceGenerated.Subclasses.StreamingInfo;
namespace AssetRipper.Export.UnityProjects.Textures;
public sealed class TextureArrayAssetExporter : BinaryAssetExporter
{
public ImageExportFormat ImageExportFormat { get; private set; }
public TextureArrayAssetExporter(LibraryConfiguration configuration)
{
ImageExportFormat = configuration.ImageExportFormat;
}
public override bool TryCreateCollection(IUnityObjectBase asset, TemporaryAssetCollection temporaryFile, [NotNullWhen(true)] out IExportCollection? exportCollection)
{
exportCollection = asset switch
{
ITexture2D texture when texture.CheckAssetIntegrity() => new TextureArrayAssetExportCollection(this, texture),
_ => null,
};
return exportCollection is not null;
}
public override bool Export(IExportContainer container, IUnityObjectBase asset, string path)
{
DirectBitmap? bitmap;
switch (asset)
{
case ICubemapArray cubemapArray:
{
if (!cubemapArray.CheckAssetIntegrity())
{
WarnResourceFileNotFound(cubemapArray.NameString, cubemapArray.StreamData_C188);
return false;
}
bitmap = TextureConverter.ConvertToBitmap(cubemapArray);
}
break;
case ITexture2DArray texture2DArray:
{
if (!texture2DArray.CheckAssetIntegrity())
{
WarnResourceFileNotFound(texture2DArray.NameString, texture2DArray.StreamData_C187);
return false;
}
bitmap = TextureConverter.ConvertToBitmap(texture2DArray);
}
break;
case ITexture3D texture3D:
{
if (!texture3D.CheckAssetIntegrity())
{
WarnResourceFileNotFound(texture3D.NameString, texture3D.StreamData_C117);
return false;
}
bitmap = TextureConverter.ConvertToBitmap(texture3D);
}
break;
default:
{
Logger.Log(LogType.Error, LogCategory.Export, $"Texture array '{asset}' has unsupported type '{asset.GetType().Name}'");
}
return false;
}
if (bitmap is null)
{
Logger.Log(LogType.Warning, LogCategory.Export, $"Unable to convert '{asset}' to bitmap");
return false;
}
return bitmap.Save(path, ImageExportFormat);
static void WarnResourceFileNotFound(string assetName, IStreamingInfo? streamingInfo)
{
Logger.Log(LogType.Warning, LogCategory.Export, $"Can't export '{assetName}' because resources file '{streamingInfo?.Path}' hasn't been found");
}
}
}

View File

@ -2,6 +2,7 @@ using AssetRipper.Export.UnityProjects.Utils;
using AssetRipper.Import.Logging;
using AssetRipper.SourceGenerated.Classes.ClassID_117;
using AssetRipper.SourceGenerated.Classes.ClassID_187;
using AssetRipper.SourceGenerated.Classes.ClassID_188;
using AssetRipper.SourceGenerated.Classes.ClassID_28;
using AssetRipper.SourceGenerated.Classes.ClassID_89;
using AssetRipper.SourceGenerated.Enums;
@ -80,6 +81,33 @@ namespace AssetRipper.Export.UnityProjects.Textures
return bitmap;
}
public static DirectBitmap? ConvertToBitmap(ICubemapArray texture)
{
byte[] buffer = texture.GetImageData();
if (buffer.Length == 0)
{
return null;
}
DirectBitmap? bitmap = ConvertToBitmap(
texture.Format_C188E,
texture.Width_C188,
texture.Width_C188,//Not sure if this is correct
texture.CubemapCount_C188 * 6,//Not sure if this is correct
(int)texture.DataSize_C188,
texture.Collection.Version,
buffer);
if (bitmap == null)
{
return null;
}
bitmap.FlipY();
return bitmap;
}
public static DirectBitmap? ConvertToBitmap(ITexture2D texture)
{
byte[] buffer = texture.GetImageData();

View File

@ -78,7 +78,7 @@ namespace AssetRipper.Export.UnityProjects.Textures
return exportID;
}
private void AddSprites(ITextureImporter importer, Dictionary<ISprite, ISpriteAtlas?>? textureSpriteInformation)
private void AddSprites(ITextureImporter importer, IReadOnlyDictionary<ISprite, ISpriteAtlas?>? textureSpriteInformation)
{
if (textureSpriteInformation == null || textureSpriteInformation.Count == 0)
{
@ -146,7 +146,7 @@ namespace AssetRipper.Export.UnityProjects.Textures
}
}
private static void AddSpriteSheet(ITextureImporter importer, Dictionary<ISprite, ISpriteAtlas?> textureSpriteInformation)
private static void AddSpriteSheet(ITextureImporter importer, IReadOnlyDictionary<ISprite, ISpriteAtlas?> textureSpriteInformation)
{
if (!importer.Has_SpriteSheet_C1006())
{
@ -173,7 +173,7 @@ namespace AssetRipper.Export.UnityProjects.Textures
}
}
private void AddIDToName(ITextureImporter importer, Dictionary<ISprite, ISpriteAtlas?> textureSpriteInformation)
private void AddIDToName(ITextureImporter importer, IReadOnlyDictionary<ISprite, ISpriteAtlas?> textureSpriteInformation)
{
if (importer.SpriteMode_C1006E == SpriteImportMode.Multiple)
{

View File

@ -0,0 +1,38 @@
using AssetRipper.SourceGenerated.Classes.ClassID_188;
namespace AssetRipper.SourceGenerated.Extensions;
public static class CubemapArrayExtensions
{
public static byte[] GetImageData(this ICubemapArray texture)
{
if (texture.ImageData_C188.Length > 0)
{
return texture.ImageData_C188;
}
else if (texture.Has_StreamData_C188() && texture.StreamData_C188.IsSet())
{
return texture.StreamData_C188.GetContent(texture.Collection);
}
else
{
return Array.Empty<byte>();
}
}
public static bool CheckAssetIntegrity(this ICubemapArray texture)
{
if (texture.ImageData_C188.Length > 0)
{
return true;
}
else if (texture.Has_StreamData_C188())
{
return texture.StreamData_C188.CheckIntegrity(texture.Collection);
}
else
{
return false;
}
}
}

View File

@ -21,6 +21,22 @@ namespace AssetRipper.SourceGenerated.Extensions
}
}
public static bool CheckAssetIntegrity(this ITexture3D texture)
{
if (texture.ImageData_C117.Length > 0)
{
return true;
}
else if (texture.Has_StreamData_C117())
{
return texture.StreamData_C117.CheckIntegrity(texture.Collection);
}
else
{
return false;
}
}
public static TextureFormat GetTextureFormat(this ITexture3D texture)
{
if (texture.Has_Format_C117_Int32())
@ -33,6 +49,18 @@ namespace AssetRipper.SourceGenerated.Extensions
}
}
public static TextureUsageMode GetLightmapFormat(this ITexture3D texture)
{
if (texture.Has_LightmapFormat_C117())
{
return texture.LightmapFormat_C117E;
}
else
{
return (TextureUsageMode)texture.UsageMode_C117;
}
}
public static int GetCompleteImageSize(this ITexture3D texture)
{
if (texture.Has_DataSize_C117())
@ -56,7 +84,7 @@ namespace AssetRipper.SourceGenerated.Extensions
if (texture.MipMap_C117)
{
int maxSide = Math.Max(texture.Width_C117, texture.Height_C117);
return Convert.ToInt32(Math.Log(maxSide) / Math.Log(2));
return Convert.ToInt32(Math.Log2(maxSide));
}
else
{