Search Unity

Question Trouble generating compressed texture2DArray with mipmaps

Discussion in 'Scripting' started by LukasKiefer, May 28, 2020.

  1. LukasKiefer

    LukasKiefer

    Joined:
    Jul 25, 2017
    Posts:
    9
    Hi there.

    I am trying to generate Texture2DArrays in edit mode with an editor script.

    I have an issue where i cannot generate a Texture2Darray from comporessed Texures with mipmaps.

    Creating an array with compressed Textures works if mipmaps are disabled.
    Careating an array with mipmaps enabled works if the Textures are uncompressed.

    Trying to use compressed AND mipmaps results in this error:
    Code (CSharp):
    1. Rebuilding mipmaps of compressed 2DArray textures is not supported
    2. UnityEngine.Texture2DArray:Apply()
    My Methods for creating the array:
    Code (CSharp):
    1. using System.Linq;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. public static class Texture2DArrayExtensions
    6. {
    7.     public static Texture2DArray CreateTexure2DArray(TextureFormat format, params Texture2D[] tex)
    8.     {
    9.         bool usingMips = tex[0].mipmapCount > 1;
    10.  
    11.         var texArray = new Texture2DArray(
    12.             tex[0].width,
    13.             tex[0].height,
    14.             tex.Length,
    15.             format,
    16.             usingMips,
    17.             false
    18.             );
    19.  
    20.         texArray.anisoLevel = tex[0].anisoLevel;
    21.         texArray.filterMode = tex[0].filterMode;
    22.         texArray.wrapMode = tex[0].wrapMode;
    23.  
    24.         // Go over all the textures and add to array
    25.         for (int i = 0; i < tex.Length; i++)
    26.         {
    27.             if (tex[i] != null)
    28.                 texArray.CopyTextureToTextureArray(tex[i], i);
    29.         }
    30.  
    31.         Debug.Log("Array is using Mips: " + usingMips);
    32.  
    33.         // appaerntly apply also creates mipmaps?
    34.         texArray.Apply();
    35.  
    36.         // this doesn't work on Texture2DArrays
    37.         //EditorUtility.CompressTexture(textureArray, TextureFormat.DXT5, TextureCompressionQuality.Best);
    38.  
    39.         return texArray;
    40.     }
    41.  
    42.     public static void CopyTextureToTextureArray(this Texture2DArray texArray, Texture2D sourceTex, int index)
    43.     {
    44.         for (int mip = 0; mip < sourceTex.mipmapCount; mip++)
    45.             Graphics.CopyTexture(sourceTex, 0, mip, texArray, index, mip);
    46.     }
    47.  
    48.     public static Texture2D CreateTempTextureInFormat(this Texture2D source, TextureFormat format, bool mips)
    49.     {
    50.         Texture2D dest = new Texture2D(source.width, source.height, format, mips);
    51.         dest.hideFlags = HideFlags.HideAndDontSave;
    52.  
    53.         for (int mip = 0; mip < source.mipmapCount; mip++)
    54.         {
    55.             Graphics.CopyTexture(source, 0, mip, dest, 0, mip);
    56.             if (!mips) break;
    57.         }
    58.  
    59.         // apply creates mips?
    60.         dest.Apply();
    61.  
    62.         EditorUtility.CompressTexture(dest, format, TextureCompressionQuality.Best);
    63.         return dest;
    64.     }
    65.  
    66.     public static Texture2D[] GetTempTextures(TextureFormat format, bool mips, params Texture2D[] source)
    67.     {
    68.         return source
    69.             .Select(q => q.CreateTempTextureInFormat(format, mips))
    70.             .ToArray();
    71.     }
    72. }
    73.  
    Triggering the Generation in ScriptableObject:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Sirenix.OdinInspector;
    5. using UnityEditor;
    6. using UnityEngine.Serialization;
    7. using System.Linq;
    8. using System.IO;
    9. using UnityEditor.Build;
    10.  
    11. [CreateAssetMenu(fileName = "new Texture2DArray Profile", menuName = "Texture2DArray Profile")]
    12. [HideMonoScript]
    13. public class Texture2DArrayProfile : ScriptableObject//, IPreprocessBuild
    14. {
    15.     [SerializeField] private AviableFormats format = AviableFormats.DXT5;
    16.     [SerializeField] private bool useMipMaps = true;
    17.  
    18.     [SerializeField] private Material targetMaterial;
    19.     [SerializeField] private string targetSlot = "_AlbedoMaps";
    20.  
    21.     [SerializeField] private List<Texture2D> textures = new List<Texture2D>();
    22.  
    23.     string targetPath
    24.     {
    25.         get
    26.         {
    27.             var pathToMe = AssetDatabase.GetAssetPath(this);
    28.             return Path.GetDirectoryName(pathToMe) + "\\" + Path.GetFileNameWithoutExtension(pathToMe) + "_GEN.asset";
    29.         }
    30.     }
    31.  
    32.     [ContextMenu("Generate now")]
    33.     void GenerateNow()
    34.     {
    35.         Debug.Log("Generating " + this.name);
    36.  
    37.         var tempTextures = Texture2DArrayExtensions.GetTempTextures((TextureFormat)format, useMipMaps, textures.ToArray());
    38.         var Array = Texture2DArrayExtensions.CreateTexure2DArray((TextureFormat)format, tempTextures);
    39.  
    40.         foreach (var item in tempTextures)
    41.             DestroyImmediate(item);
    42.  
    43.         AssetDatabase.CreateAsset(Array, targetPath);
    44.         Debug.Log("Saved asset to " + targetPath);
    45.  
    46.         // load again to use asset from disk
    47.         var loaded = AssetDatabase.LoadAssetAtPath<Texture2DArray>(targetPath);
    48.         targetMaterial.SetTexture(targetSlot, loaded);
    49.     }
    50.  
    51.     //public void OnPreprocessBuild(BuildTarget target, string path)
    52.     //{
    53.     //    Debug.Log("PREPROCESSING TEXTUREARRAY: " + name);
    54.     //    GenerateNow();
    55.     //}
    56.  
    57.     public enum AviableFormats
    58.     {
    59.         ARGB32 = TextureFormat.ARGB32,
    60.         DXT1 = TextureFormat.DXT1,
    61.         DXT1Crunched = TextureFormat.DXT1Crunched,
    62.         DXT5 = TextureFormat.DXT5,
    63.         DXT5Crunched = TextureFormat.DXT5Crunched,
    64.         PVRTC_RGB2 = TextureFormat.PVRTC_RGB2,
    65.         PVRTC_RGBA2 = TextureFormat.PVRTC_RGBA2,
    66.     }
    67. }
    68.  
    My results:
    • ARGB32 + mipmaps -> works
    • DXT1 (original Textures are DXT1) + mipmaps ->
      "Rebuilding mipmaps of compressed 2DArray textures is not supported"
      but array is created
    • DXT1 (original Textures are DXT1), no mipmaps -> works
    • DXT5 (original Textures are DXT5), no mipmaps -> works
    • DXT5 (original Textures are DXT5) + mipmaps ->
      "Rebuilding mipmaps of compressed 2DArray textures is not supported"
      but array is created

    I create the temp Texures for format conversion and to make sure that formats of Texture2Ds and Texture2Darray match.

    It seems like CopyTexture works only when source and dest Texures are of the same format, is this correct?
    If so, how can i create a temporary texture in another format?
    Using setPixels does not work on DXT1 or DXT5.

    Writing all this i think it comes down to a few questions:
    1. How can i create a temporary Texture in a set format (e.g. DXT1 or 5, PVRTC, etc) with optional mipmaps as a copy of an existing texture asset?
      I think mipmaps could also be just regenerated instead of copied over.
    2. How can i use these to create a Texture2DArray that has the same format and mipmaps settings?
      e.g. a Texture2DArray in DXT5 with mipmaps
     
  2. LukasKiefer

    LukasKiefer

    Joined:
    Jul 25, 2017
    Posts:
    9
    Okay so turns out this was simpler than i thought.

    Calling Apply() on a Texture2D that was modified with Graphics.Copy does not go well together.

    I decided to modify the Texture import settings of each Texture in my list and then generate the array form it.

    my methods:
    Code (CSharp):
    1. using System.Linq;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. public static class Texture2DArrayExtensions
    6. {
    7.     public static Texture2DArray CreateTexure2DArray(params Texture2D[] tex)
    8.     {
    9.         bool mips = tex[0].mipmapCount > 1;
    10.  
    11.         var texArray = new Texture2DArray(
    12.             tex[0].width,
    13.             tex[0].height,
    14.             tex.Length,
    15.             tex[0].format,
    16.             mips,
    17.             false
    18.             );
    19.  
    20.         texArray.anisoLevel = tex[0].anisoLevel;
    21.         texArray.filterMode = tex[0].filterMode;
    22.         texArray.wrapMode = tex[0].wrapMode;
    23.  
    24.         // Go over all the textures and add to array
    25.         for (int texIndex = 0; texIndex < tex.Length; texIndex++)
    26.         {
    27.             if (tex[texIndex] != null)
    28.             {
    29.                 for (int mip = 0; mip < tex[texIndex].mipmapCount; mip++)
    30.                     Graphics.CopyTexture(tex[texIndex], 0, mip, texArray, texIndex, mip);
    31.             }
    32.         }
    33.  
    34.         return texArray;
    35.     }
    36.  
    37.     public static void SetTexureProps(this Texture2D source, int maxSize, TextureImporterCompression compression, bool mipmaps)
    38.     {
    39.         var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(source)) as TextureImporter;
    40.  
    41.         importer.isReadable = false;
    42.         importer.maxTextureSize = maxSize;
    43.         importer.textureCompression = compression;
    44.         importer.mipmapEnabled = mipmaps;
    45.  
    46.  
    47.         // disable platform specific overrides
    48.  
    49.         var platforms = new string[]
    50.         {
    51.         "Standalone", "Web", "iPhone", "Android", "WebGL", "Windows Store Apps", "PS4", "XboxOne", "Nintendo 3DS" ,"tvOS"
    52.         };
    53.  
    54.         foreach (var platform in platforms)
    55.         {
    56.             var settings = importer.GetPlatformTextureSettings(platform);
    57.             //settings.textureCompression = compression;
    58.             //Debug.Log(settings.overridden);
    59.             settings.overridden = false;
    60.             importer.SetPlatformTextureSettings(settings);
    61.         }
    62.  
    63.  
    64.         importer.SaveAndReimport();
    65.     }
    66. }
    67.  
    And a ScriptableObject to setup and generate the array.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Sirenix.OdinInspector;
    5. using UnityEditor;
    6. using UnityEngine.Serialization;
    7. using System.Linq;
    8. using System.IO;
    9. using UnityEditor.Build;
    10.  
    11. [CreateAssetMenu(fileName = "new Texture2DArray Profile", menuName = "Texture2DArray Profile")]
    12. [HideMonoScript]
    13. public class Texture2DArrayProfile : ScriptableObject//, IPreprocessBuild
    14. {
    15.     //[SerializeField] private TextureImporterFormat format;
    16.     [SerializeField] private bool useMipMaps = true;
    17.     [SerializeField] private PrettyTextureImporterCompression compression;
    18.     [SerializeField] private MaxSize maxSize = MaxSize._2048;
    19.  
    20.     [SerializeField] private Material targetMaterial;
    21.     [SerializeField] private string targetSlot = "_AlbedoMaps";
    22.  
    23.     [SerializeField] private List<Texture2D> textures = new List<Texture2D>();
    24.  
    25.     string targetPath
    26.     {
    27.         get
    28.         {
    29.             var pathToMe = AssetDatabase.GetAssetPath(this);
    30.             return Path.GetDirectoryName(pathToMe) + "\\" + Path.GetFileNameWithoutExtension(pathToMe) + "_GEN.asset";
    31.         }
    32.     }
    33.  
    34.     [Button, ContextMenu("Generate now")]
    35.     void GenerateNow()
    36.     {
    37.         string progressHeader = "TextureArray Generator " + name;
    38.  
    39.         EditorUtility.DisplayProgressBar(progressHeader, "", 0f);
    40.  
    41.         for (int i = 0; i < textures.Count; i++)
    42.         {
    43.             EditorUtility.DisplayProgressBar(progressHeader, "Setting Texture Import Settings for " + textures[i].name, ((float)(i + 1) / textures.Count));
    44.  
    45.             textures[i].SetTexureProps((int)maxSize, (TextureImporterCompression)compression, useMipMaps);
    46.         }
    47.  
    48.         AssetDatabase.SaveAssets();
    49.  
    50.         EditorUtility.DisplayProgressBar(progressHeader, "Generating TextureArray", 0f);
    51.         var Array = Texture2DArrayExtensions.CreateTexure2DArray(textures.ToArray());
    52.  
    53.         EditorUtility.DisplayProgressBar(progressHeader, "Saving TextureArray", 0f);
    54.         //AssetDatabase.DeleteAsset(targetPath);
    55.         AssetDatabase.CreateAsset(Array, targetPath);
    56.         AssetDatabase.SaveAssets();
    57.         Debug.Log("Saved asset to " + targetPath);
    58.  
    59.         // load again to use asset from disk
    60.         EditorUtility.DisplayProgressBar(progressHeader, "Assigning TextureArray to Material", 0f);
    61.         var loaded = AssetDatabase.LoadAssetAtPath<Texture2DArray>(targetPath);
    62.         targetMaterial.SetTexture(targetSlot, loaded);
    63.  
    64.  
    65.         EditorUtility.ClearProgressBar();
    66.     }
    67.  
    68.     //public void OnPreprocessBuild(BuildTarget target, string path)
    69.     //{
    70.     //    Debug.Log("PREPROCESSING TEXTUREARRAY: " + name);
    71.     //    GenerateNow();
    72.     //}
    73.  
    74.     public enum MaxSize
    75.     {
    76.         _32 = 32,
    77.         _64 = 64,
    78.         _128 = 128,
    79.         _256 = 256,
    80.         _512 = 512,
    81.         _1024 = 1024,
    82.         _2048 = 2048,
    83.         _4096 = 4096,
    84.         _8192 = 8192,
    85.     }
    86.  
    87.     public enum PrettyTextureImporterCompression
    88.     {
    89.         Standard = TextureImporterCompression.Compressed,
    90.         LowQualityHighPerformance = TextureImporterCompression.CompressedLQ,
    91.         HighQualityLowPerformance = TextureImporterCompression.CompressedHQ,
    92.         Uncompressed = TextureImporterCompression.Uncompressed,
    93.     }
    94. }
    95.  
    Now i just hook it into pre-build callback and my artists can handle all this by themselves :)
     
    Kevin-VFX, botafii and sfike like this.