Search Unity

Combining Metallic & Smoothness textures into a single texture automatically

Discussion in 'General Graphics' started by Gillissie, Jul 9, 2020.

  1. Gillissie

    Gillissie

    Joined:
    May 16, 2011
    Posts:
    305
    I've been very frustrated at the requirement of having the "smoothness" be the alpha channel of the Metallic texture. Sure, I get that it's more compact for the shader to read all the data it needs, but it's a workflow nightmare when you need to make changes to the metallic or smoothness texture.

    I don't know about everyone else, but I like to work with PSD's a lot, because it let's me keep things on different layers, with layer effects that are easily adjustable, and I can keep a layer of the UV Map in there to turn on when I need to see it, then turn off before saving.

    So, I finally broke down and wrote a texture asset importer that will combine individual Metallic and Smoothness textures into a 32-bit texture that the shader likes. Now you can make changes to either of the source textures, and they will automatically re-combine into the new PNG texture that's linked to the material.

    Now I can use PSD files for both the metallic and smoothness textures, and let the script combine them into a PNG for material use. Feel free to use this and share it. Please comment if you also find this helpful.

    Usage:
    Save this script in an "Editor" folder.
    Save a texture file with the suffix " Metallic" (note the space), and a texture file with the suffix " Smoothness" (note the space) in the same folder in Unity.
    If both textures are found, then a combined PNG texture will be created using the same base filename, but with the suffix " Metallic-Smoothness".

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using System.IO;
    4.  
    5. /*
    6. This class hooks into Unity's asset import process to combine "Metallic" and "Smoothness" textures
    7. into a single "Metallic-Smoothness" texture to be used in the "Metallic" texture slot of the standard shader.
    8. Editing the "smoothness" part of the combined texture is very difficult to do since it lives in the alpha channel.
    9. This lets you keep the smoothness texture separate, for ease of editing.
    10. For example, you may want to save it as a PSD file with a UV Map layer that you turn on when working on it,
    11. then turn back off when saving, or have multiple layers of elements that are more flexible to adjust dynamically.
    12.  
    13. Written by Todd Gillissie of Gilligames.
    14. Feel free to use and distribute freely.
    15. */
    16.  
    17. public class ImportMetallicSmoothness : AssetPostprocessor
    18. {
    19.     void OnPreprocessTexture()
    20.     {
    21.         if (!isMetallicOrSmoothness())
    22.         {
    23.             return;
    24.         }
    25.  
    26.         // Sets some required import values for reading the texture's pixels for combining.
    27.         TextureImporter textureImporter = (TextureImporter)assetImporter;
    28.  
    29.         textureImporter.isReadable = true;
    30.         textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
    31.         textureImporter.mipmapEnabled = false;
    32.     }
    33.  
    34.     // We need to do the actual combining of textures in the Postprocessor, since the original texture needs to be finished processing first.
    35.     void OnPostprocessTexture(Texture2D texture)
    36.     {
    37.         if (!isMetallicOrSmoothness())
    38.         {
    39.             return;
    40.         }
    41.  
    42.         string filename = Path.GetFileNameWithoutExtension(assetPath);
    43.         string combinedPath = "";
    44.  
    45.         Texture2D metallic = null;
    46.         Texture2D smoothness = null;
    47.  
    48.         if (filename.EndsWith(" Metallic"))
    49.         {
    50.             metallic = texture;
    51.  
    52.             string smoothnessPath = convertMetallicSmoothnessPath("Metallic", "Smoothness", out combinedPath);
    53.  
    54.             if (File.Exists(smoothnessPath))
    55.             {
    56.                 smoothness = AssetDatabase.LoadAssetAtPath<Texture2D>(smoothnessPath);
    57.             }
    58.         }
    59.         else if (filename.EndsWith(" Smoothness"))
    60.         {
    61.             smoothness = texture;
    62.  
    63.             string metallicPath = convertMetallicSmoothnessPath("Smoothness", "Metallic", out combinedPath);
    64.  
    65.             if (File.Exists(metallicPath))
    66.             {
    67.                 metallic = AssetDatabase.LoadAssetAtPath<Texture2D>(metallicPath);
    68.             }
    69.         }
    70.  
    71.         if (metallic == null)
    72.         {
    73.             Debug.LogWarningFormat("Associated Metallic texture not found for: {0}", filename);
    74.             return;
    75.         }
    76.  
    77.         if (smoothness == null)
    78.         {
    79.             Debug.LogWarningFormat("Associated Smoothness texture not found for: {0}", filename);
    80.             return;
    81.         }
    82.  
    83.         if (metallic.width != smoothness.width || metallic.height != smoothness.height)
    84.         {
    85.             Debug.LogWarningFormat("Metallic and Smoothness textures must be the same size in order to combine: {0}", assetPath);
    86.             return;
    87.         }
    88.  
    89.         var metallicPixels = metallic.GetPixels32();
    90.         var smoothnessPixels = smoothness.GetPixels32();
    91.  
    92.         Texture2D combined = new Texture2D(metallic.width, metallic.height, TextureFormat.ARGB32, false);
    93.  
    94.         // Use the red channel info from smoothness for the alpha channel of the combined texture.
    95.         // Since the smoothness should be grayscale, we just use the red channel info.
    96.         for (int i = 0; i < metallicPixels.Length; i++)
    97.         {
    98.             metallicPixels[i].a = smoothnessPixels[i].r;
    99.         }
    100.  
    101.         combined.SetPixels32(metallicPixels);
    102.  
    103.         // Save the combined data.
    104.         byte[] png = combined.EncodeToPNG();
    105.         File.WriteAllBytes(combinedPath, png);
    106.  
    107.         AssetDatabase.ImportAsset(combinedPath);
    108.     }
    109.  
    110.     ////////////////////////////////////////////////////////////////////////////////////////////////
    111.     // Helper functions.
    112.     ////////////////////////////////////////////////////////////////////////////////////////////////
    113.  
    114.     // Returns true if the texture being processed ends with " Metallic" or " Smoothness",
    115.     // since we only want to work with those.
    116.     private bool isMetallicOrSmoothness()
    117.     {
    118.         string filename = Path.GetFileNameWithoutExtension(assetPath);
    119.  
    120.         return filename.EndsWith(" Metallic") || filename.EndsWith(" Smoothness");
    121.     }
    122.  
    123.     private string convertMetallicSmoothnessPath(string from, string to, out string combinedPath)
    124.     {
    125.         string filename = Path.GetFileNameWithoutExtension(assetPath);
    126.         string extension = Path.GetExtension(assetPath);
    127.         string pathWithoutFilename = Path.GetDirectoryName(assetPath);
    128.         string baseFilename = filename.Substring(0, filename.Length - string.Format(" {0}", from).Length);
    129.  
    130.         string newPath = string.Format("{0}/{1} {2}{3}", pathWithoutFilename, baseFilename, to, extension);
    131.  
    132.         combinedPath = string.Format("{0}/{1} Metallic-Smoothness.png", pathWithoutFilename, baseFilename);
    133.  
    134.         return newPath;
    135.     }
    136. }
     
    paladim, sirleto, kaymay1037 and 6 others like this.