Search Unity

Substance ProceduralMaterial to Material? Possible?

Discussion in 'Scripting' started by Krileon, Sep 16, 2013.

  1. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Is it possible to extract the ProceduralMaterial from a substance then turn it into a normal Material and save it to assets? I know how to get all the materials in a substance, etc.. I also know how to save assets, but I wonder if there is a simple approach to extracting the ProceduralMaterials and turning them into regular Materials. My current approach looks to be to parse all the ProceduralMaterials, save their textures, create a new Material with the same shader as ProceduralMaterial then assign the extracted textures, offsets, and shader parameters (maybe easier to just copy the shader and point to the newly saved textures).

    My idea behind this is to use B2M to generate materials, but don't use it long term. As in it won't be packaged with the game. This allows for the best of both worlds without any performance losses of using substances (using substances in Editor makes me want to pull my eyes out with a plastic spoon, slow as can be).
     
  2. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    I was able to create proper paths and create a material with the substance materials shader. The problem I'm having is extracting the textures. I've tried just instantiating the texture and saving as a new asset, but it ends up being a empty texture. Anyone have any pointers on how to extract the textures from a substance? Below is my current progress.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public class ExtractSubstance : EditorWindow {
    8.  
    9.     private SubstanceArchive substance;
    10.     private string folder;
    11.  
    12.     [MenuItem ("Window/Extract Substance")]
    13.     static void Init() {
    14.         EditorWindow.GetWindow( typeof( ExtractSubstance ) );
    15.     }
    16.  
    17.     void OnGUI() {
    18.         substance = EditorGUILayout.ObjectField( "Substance", substance, typeof( SubstanceArchive ), false ) as SubstanceArchive;
    19.         folder = EditorGUILayout.TextField( "Folder", folder );
    20.  
    21.         if ( GUILayout.Button( "Split" ) ) {
    22.             string fromPath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    23.             SubstanceImporter fromImporter = AssetImporter.GetAtPath( fromPath ) as SubstanceImporter;
    24.             int fromMaterialCount = fromImporter.GetMaterialCount();
    25.             ProceduralMaterial[] fromMaterials = fromImporter.GetMaterials();
    26.  
    27.             fromPath = fromPath.Replace( "/" + substance.name + ".sbsar", "" );
    28.  
    29.             string toFolder = folder;
    30.  
    31.             if ( ! ( toFolder.Length > 0 ) ) {
    32.                 toFolder = substance.name;
    33.             }
    34.            
    35.             AssetDatabase.CreateFolder( fromPath, toFolder );
    36.  
    37.             if ( fromMaterialCount > 0 ) foreach ( ProceduralMaterial fromMaterial in fromMaterials ) {
    38.                 AssetDatabase.CreateFolder( fromPath + "/" + toFolder, fromMaterial.name );
    39.  
    40.                 string toPathBase = fromPath + "/" + toFolder + "/" + fromMaterial.name + "/";
    41.                 Material newMaterial = new Material( fromMaterial.shader );
    42.  
    43.                 AssetDatabase.CreateAsset( newMaterial, AssetDatabase.GenerateUniqueAssetPath( toPathBase + fromMaterial.name + ".mat" ) );
    44.  
    45.                 Texture[] materialTextures = fromMaterial.GetGeneratedTextures();
    46.  
    47.                 if ( materialTextures.Length > 0 ) foreach ( Texture materialTexture in materialTextures ) {
    48.                     Texture newTexture = Instantiate( materialTexture ) as Texture;
    49.                     AssetDatabase.CreateAsset( newTexture, AssetDatabase.GenerateUniqueAssetPath( toPathBase + materialTexture.name + ".asset" ) );
    50.                 }
    51.             }
    52.         }
    53.     }
    54. }
    55.  
    It'd be a life saver if I could just do ExportBitmaps from script.
     
    Last edited: Sep 16, 2013
  3. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    949
    I haven't been able to get this working myself by code (even when attempting to use the GetPixels32 method of ProceduralTexture).

    However, you might be interested to know that you can extract textures by selecting "Export Bitmaps" from the gear menu in the inspector. Naturally you would need to manually associate the exported textures with your material.

    See: http://forum.unity3d.com/threads/196289-Substance-Export-Bitmaps
     
  4. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    What I'm seeing is even though I'm Instantiating to Texture and even though the array returns an array of Textures, they're actually ProceduralTextures. Even casting it to Texture doesn't seam to work.

    I know I can export manually, but I have 122 materials in my substances in total. Doing that 122 times then having to manually setup the materials for the textures 122 times would really suck.

    Hopefully can get this figured out. If I can will share it here. The above is still my current progress unfortunately. I hope someone from the Unity Team can chime in though.
     
    Last edited: Sep 17, 2013
  5. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Ok, progress! I was able to extract the pixels. Set your substance compression to RAW. I would do this automatically in script, but I can't seam to find a way to do that. That's all you need to do. My script will make the material readable so the pixels can be read.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public class ExtractSubstance : EditorWindow {
    8.  
    9.     private SubstanceArchive substance;
    10.     private string folder;
    11.  
    12.     [MenuItem ("Window/Extract Substance")]
    13.     static void Init() {
    14.         EditorWindow.GetWindow( typeof( ExtractSubstance ) );
    15.     }
    16.  
    17.     void OnGUI() {
    18.         substance = EditorGUILayout.ObjectField( "Substance", substance, typeof( SubstanceArchive ), false ) as SubstanceArchive;
    19.         folder = EditorGUILayout.TextField( "Folder", folder );
    20.  
    21.         if ( GUILayout.Button( "Split" ) ) {
    22.             string fromPath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    23.             SubstanceImporter fromImporter = AssetImporter.GetAtPath( fromPath ) as SubstanceImporter;
    24.             int fromMaterialCount = fromImporter.GetMaterialCount();
    25.             ProceduralMaterial[] fromMaterials = fromImporter.GetMaterials();
    26.            
    27.             fromPath = fromPath.Replace( "/" + substance.name + ".sbsar", "" );
    28.  
    29.             string toFolder = folder;
    30.  
    31.             if ( ! ( toFolder.Length > 0 ) ) {
    32.                 toFolder = substance.name;
    33.             }
    34.  
    35.             AssetDatabase.CreateFolder( fromPath, toFolder );
    36.  
    37.             if ( fromMaterialCount > 0 ) foreach ( ProceduralMaterial fromMaterial in fromMaterials ) {
    38.                 fromMaterial.isReadable = true;
    39.  
    40.                 AssetDatabase.CreateFolder( fromPath + "/" + toFolder, fromMaterial.name );
    41.  
    42.                 string toPathBase = fromPath + "/" + toFolder + "/" + fromMaterial.name + "/";
    43.                 Material newMaterial = new Material( fromMaterial.shader );
    44.  
    45.                 newMaterial.CopyPropertiesFromMaterial( fromMaterial );
    46.  
    47.                 AssetDatabase.CreateAsset( newMaterial, AssetDatabase.GenerateUniqueAssetPath( toPathBase + fromMaterial.name + ".mat" ) );
    48.  
    49.                 Texture[] materialTextures = fromMaterial.GetGeneratedTextures();
    50.  
    51.                 if ( materialTextures.Length > 0 ) foreach ( ProceduralTexture materialTexture in materialTextures ) {
    52.                     Texture2D newTexture = new Texture2D( materialTexture.width, materialTexture.height );
    53.  
    54.                     newTexture.anisoLevel = materialTexture.anisoLevel;
    55.                     newTexture.filterMode = materialTexture.filterMode;
    56.                     newTexture.mipMapBias = materialTexture.mipMapBias;
    57.                     newTexture.wrapMode = materialTexture.wrapMode;
    58.  
    59.                     Color32[] pixels = materialTexture.GetPixels32( 0, 0, materialTexture.width, materialTexture.height );
    60.  
    61.                     newTexture.SetPixels32( pixels );
    62.                     //newTexture.Compress( true );
    63.                    
    64.                     AssetDatabase.CreateAsset( newTexture, AssetDatabase.GenerateUniqueAssetPath( toPathBase + materialTexture.name + ".asset" ) );
    65.                 }
    66.             }
    67.  
    68.             AssetDatabase.Refresh();
    69.         }
    70.     }
    71. }
    72.  
    Tested the above with a few textures and seams to work fine. I have compression off for now, but ideally you'd want it on as the textures come out pretty large. My only issue is they're stored as .asset and I want them stored as .png. So trying to figure out how to store them to PNG assets.

    I'm also only using the 2 parameter Texture2D as I'm not sure if the default RGBA32 will be fine or not. On all the textures I've tried it seams ok. So didn't have reason to change at the moment.
     
    Last edited: Sep 17, 2013
  6. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Ok, progress again. I got it to encode the textures to PNG properly.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.IO;
    7.  
    8. public class ExtractSubstance : EditorWindow {
    9.  
    10.     private SubstanceArchive substance;
    11.     private string folder;
    12.  
    13.     [MenuItem ("Window/Extract Substance")]
    14.     static void Init() {
    15.         EditorWindow.GetWindow( typeof( ExtractSubstance ) );
    16.     }
    17.  
    18.     void OnGUI() {
    19.         substance = EditorGUILayout.ObjectField( "Substance", substance, typeof( SubstanceArchive ), false ) as SubstanceArchive;
    20.         folder = EditorGUILayout.TextField( "Folder", folder );
    21.  
    22.         if ( GUILayout.Button( "Split" ) ) {
    23.             string fromPath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    24.             SubstanceImporter fromImporter = AssetImporter.GetAtPath( fromPath ) as SubstanceImporter;
    25.             int fromMaterialCount = fromImporter.GetMaterialCount();
    26.             ProceduralMaterial[] fromMaterials = fromImporter.GetMaterials();
    27.            
    28.             fromPath = fromPath.Replace( "/" + substance.name + ".sbsar", "" );
    29.  
    30.             string toFolder = folder;
    31.  
    32.             if ( ! ( toFolder.Length > 0 ) ) {
    33.                 toFolder = substance.name;
    34.             }
    35.  
    36.             AssetDatabase.CreateFolder( fromPath, toFolder );
    37.  
    38.             if ( fromMaterialCount > 0 ) foreach ( ProceduralMaterial fromMaterial in fromMaterials ) {
    39.                 fromMaterial.isReadable = true;
    40.  
    41.                 AssetDatabase.CreateFolder( fromPath + "/" + toFolder, fromMaterial.name );
    42.  
    43.                 string toPathBase = fromPath + "/" + toFolder + "/" + fromMaterial.name + "/";
    44.                 Material newMaterial = new Material( fromMaterial.shader );
    45.  
    46.                 newMaterial.CopyPropertiesFromMaterial( fromMaterial );
    47.  
    48.                 AssetDatabase.CreateAsset( newMaterial, AssetDatabase.GenerateUniqueAssetPath( toPathBase + fromMaterial.name + ".mat" ) );
    49.  
    50.                 //int propertyCount = ShaderUtil.GetPropertyCount( newMaterial.shader );
    51.                 Texture[] materialTextures = fromMaterial.GetGeneratedTextures();
    52.  
    53.                 if ( materialTextures.Length > 0 ) foreach ( ProceduralTexture materialTexture in materialTextures ) {
    54.                     Texture2D newTexture = new Texture2D( materialTexture.width, materialTexture.height );
    55.  
    56.                     newTexture.anisoLevel = materialTexture.anisoLevel;
    57.                     newTexture.filterMode = materialTexture.filterMode;
    58.                     newTexture.mipMapBias = materialTexture.mipMapBias;
    59.                     newTexture.wrapMode = materialTexture.wrapMode;
    60.  
    61.                     Color32[] pixels = materialTexture.GetPixels32( 0, 0, materialTexture.width, materialTexture.height );
    62.  
    63.                     newTexture.SetPixels32( pixels );
    64.                     //newTexture.Compress( true );
    65.  
    66.                     File.WriteAllBytes( AssetDatabase.GenerateUniqueAssetPath( toPathBase + materialTexture.name + ".png" ), newTexture.EncodeToPNG() );
    67.                 }
    68.             }
    69.  
    70.             AssetDatabase.Refresh();
    71.         }
    72.     }
    73. }
    74.  
    Seams pretty close to completed. I just need to assign the newly generated textures to the generated material.

    I've also tested this on a substance with 100 materials. Performance seams ok and memory seams to be clearing fine.
     
    Last edited: Sep 17, 2013
  7. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Ok, with the above the exported textures are a 1 to 1 match to using ExportBitmap. The only problem so far is Normals don't come out correct, so working on fixing that.
     
  8. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Alright, even more progress. The textures output by B2M don't have an alpha channel. So I had to force the colors alpha to 255. I also forced the blue channel for the normal to 255 so it'll be completely blue like using ExportBitmaps.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.IO;
    7.  
    8. public class ExtractSubstance : EditorWindow {
    9.  
    10.     private SubstanceArchive substance;
    11.     private string folder;
    12.  
    13.     [MenuItem ("Window/Extract Substance")]
    14.     static void Init() {
    15.         EditorWindow.GetWindow( typeof( ExtractSubstance ) );
    16.     }
    17.  
    18.     void OnGUI() {
    19.         substance = EditorGUILayout.ObjectField( "Substance", substance, typeof( SubstanceArchive ), false ) as SubstanceArchive;
    20.         folder = EditorGUILayout.TextField( "Folder", folder );
    21.  
    22.         if ( GUILayout.Button( "Split" ) ) {
    23.             string fromPath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    24.             SubstanceImporter fromImporter = AssetImporter.GetAtPath( fromPath ) as SubstanceImporter;
    25.             int fromMaterialCount = fromImporter.GetMaterialCount();
    26.             ProceduralMaterial[] fromMaterials = fromImporter.GetMaterials();
    27.            
    28.             fromPath = fromPath.Replace( "/" + substance.name + ".sbsar", "" );
    29.  
    30.             string toFolder = folder;
    31.  
    32.             if ( ! ( toFolder.Length > 0 ) ) {
    33.                 toFolder = substance.name;
    34.             }
    35.  
    36.             AssetDatabase.CreateFolder( fromPath, toFolder );
    37.  
    38.             if ( fromMaterialCount > 0 ) foreach ( ProceduralMaterial fromMaterial in fromMaterials ) {
    39.                 fromMaterial.isReadable = true;
    40.  
    41.                 AssetDatabase.CreateFolder( fromPath + "/" + toFolder, fromMaterial.name );
    42.  
    43.                 string toPathBase = fromPath + "/" + toFolder + "/" + fromMaterial.name + "/";
    44.                 Material newMaterial = new Material( fromMaterial.shader );
    45.  
    46.                 newMaterial.CopyPropertiesFromMaterial( fromMaterial );
    47.  
    48.                 AssetDatabase.CreateAsset( newMaterial, AssetDatabase.GenerateUniqueAssetPath( toPathBase + fromMaterial.name + ".mat" ) );
    49.  
    50.                 //int propertyCount = ShaderUtil.GetPropertyCount( newMaterial.shader );
    51.                 Texture[] materialTextures = fromMaterial.GetGeneratedTextures();
    52.  
    53.                 if ( materialTextures.Length > 0 ) foreach ( ProceduralTexture materialTexture in materialTextures ) {
    54.                     Texture2D newTexture = new Texture2D( materialTexture.width, materialTexture.height );
    55.  
    56.                     newTexture.anisoLevel = materialTexture.anisoLevel;
    57.                     newTexture.filterMode = materialTexture.filterMode;
    58.                     newTexture.mipMapBias = materialTexture.mipMapBias;
    59.                     newTexture.wrapMode = materialTexture.wrapMode;
    60.  
    61.                     Color32[] pixels = materialTexture.GetPixels32( 0, 0, materialTexture.width, materialTexture.height );
    62.  
    63.                     for ( int i = 0; i < pixels.Length; i++ ) {
    64.                         pixels[i].a = 255;
    65.  
    66.                         if ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Normal ) {
    67.                             pixels[i].b = 255;
    68.                         }
    69.                     }
    70.  
    71.                     newTexture.SetPixels32( pixels );
    72.  
    73.                     File.WriteAllBytes( AssetDatabase.GenerateUniqueAssetPath( toPathBase + materialTexture.name + ".png" ), newTexture.EncodeToPNG() );
    74.  
    75.                     AssetDatabase.Refresh();
    76.                 }
    77.  
    78.                 AssetDatabase.Refresh();
    79.             }
    80.  
    81.             AssetDatabase.Refresh();
    82.         }
    83.     }
    84. }
    85.  
    I'm still having problems with the Normal not having a proper green channel. I'm guessing a negative channel or green and red channels need to be inverted. You'll notice the differences if you do ExportBitmap then compare it to the extracted Normal. Anyone who has experience with normals have any pointers?
     
  9. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Still not having any luck generating the normal map from the exported bump map. Anyone have any ideas how to do that? I can get the blue coloring and such working, but it's not quite right still. Generating from grayscale doesn't work either as it basically scrambles it with far too much noise.
     
  10. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    I'm not finding any further info on getting these normals working right. They look "ok", but they're not the same as the normals exported by ExportBitmap or the normals in the substances material.

    Can someone from Unity please chime in? Would really appreciate the help... Maybe can explain how to run ExportBitmaps from script? or the method it uses to extract the normal?
     
    Last edited: Sep 17, 2013
  11. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Think I maybe getting somewhere. It looks like generating normal from heightmap works really well and is accurate as long as the noise is correctly set (lower than default). So what I've done is I've set it to force the material to generate all outputs, extract, then revert it back to what it was before (current output is cached). This way when extracting you get to extract all outputs.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.IO;
    7.  
    8. public class ExtractSubstance : EditorWindow {
    9.  
    10.     private SubstanceArchive substance;
    11.  
    12.     [MenuItem ("Window/Extract Substance")]
    13.     static void Init() {
    14.         EditorWindow.GetWindow( typeof( ExtractSubstance ) );
    15.     }
    16.  
    17.     void OnGUI() {
    18.         substance = EditorGUILayout.ObjectField( "Substance", substance, typeof( SubstanceArchive ), false ) as SubstanceArchive;
    19.  
    20.         if ( GUILayout.Button( "Extract" ) ) {
    21.             string fromPath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    22.             SubstanceImporter fromImporter = AssetImporter.GetAtPath( fromPath ) as SubstanceImporter;
    23.             int fromMaterialCount = fromImporter.GetMaterialCount();
    24.             ProceduralMaterial[] fromMaterials = fromImporter.GetMaterials();
    25.            
    26.             fromPath = fromPath.Replace( "/" + substance.name + ".sbsar", "" );
    27.  
    28.             if ( ! Directory.Exists( fromPath + "/" + substance.name ) ) {
    29.                 AssetDatabase.CreateFolder( fromPath, substance.name );
    30.             }
    31.  
    32.             if ( fromMaterialCount > 0 ) foreach ( ProceduralMaterial fromMaterial in fromMaterials ) {
    33.                 fromMaterial.isReadable = true;
    34.  
    35.                 bool generateOutputCache = fromImporter.GetGenerateAllOutputs( fromMaterial );
    36.  
    37.                 fromImporter.SetGenerateAllOutputs( fromMaterial, true );
    38.  
    39.                 if ( ! Directory.Exists( fromPath + "/" + substance.name + "/" + fromMaterial.name ) ) {
    40.                     AssetDatabase.CreateFolder( fromPath + "/" + substance.name, fromMaterial.name );
    41.                 }
    42.  
    43.                 string toPathBase = fromPath + "/" + substance.name + "/" + fromMaterial.name + "/";
    44.                 Material newMaterial = new Material( fromMaterial.shader );
    45.  
    46.                 newMaterial.CopyPropertiesFromMaterial( fromMaterial );
    47.  
    48.                 AssetDatabase.CreateAsset( newMaterial, toPathBase + fromMaterial.name + ".mat" );
    49.  
    50.                 //int propertyCount = ShaderUtil.GetPropertyCount( newMaterial.shader );
    51.                 Texture[] materialTextures = fromMaterial.GetGeneratedTextures();
    52.  
    53.                 if ( materialTextures.Length > 0 ) foreach ( ProceduralTexture materialTexture in materialTextures ) {
    54.                     fromMaterial.isReadable = true;
    55.  
    56.                     Texture2D newTexture = new Texture2D( materialTexture.width, materialTexture.height );
    57.  
    58.                     newTexture.anisoLevel = materialTexture.anisoLevel;
    59.                     newTexture.filterMode = materialTexture.filterMode;
    60.                     newTexture.mipMapBias = materialTexture.mipMapBias;
    61.                     newTexture.wrapMode = materialTexture.wrapMode;
    62.  
    63.                     Color32[] pixels = materialTexture.GetPixels32( 0, 0, materialTexture.width, materialTexture.height );
    64.  
    65.                     if ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Normal ) for ( int i = 0; i < pixels.Length; i++ ) {
    66.                         pixels[i].a = 255;
    67.                     }
    68.  
    69.                     newTexture.SetPixels32( pixels );
    70.  
    71.                     File.WriteAllBytes( toPathBase + materialTexture.name + ".png", newTexture.EncodeToPNG() );
    72.  
    73.                     AssetDatabase.Refresh();
    74.                 }
    75.  
    76.                 fromMaterial.isReadable = false;
    77.  
    78.                 if ( ! generateOutputCache ) {
    79.                     fromImporter.SetGenerateAllOutputs( fromMaterial, false );
    80.                 }
    81.  
    82.                 AssetDatabase.Refresh();
    83.             }
    84.  
    85.             AssetDatabase.Refresh();
    86.         }
    87.     }
    88. }
    89.  
    I would say the next step is to update the material with the output textures by property name (notice the commented counter, we need to get all the property names of the shader) and use heightmap for normal instead of normalmap.

    You also probably don't want to do this with a computer with less than 2GB ram as it is pretty heavy. With that said it refreshes enough to clear the memory that it maybe ok on slower computers. I've tested it successfully on a substance with 100 materials.

    I suggest using on reasonably sized substances. If you need to split use my split substance script to make them smaller. I may add a 2 step process to this. 1 step prepares the substances and materials then the second does the extract.
     
    Last edited: Sep 19, 2013
  12. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Ok, now it'll actually set the new materials properties to the new textures.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.IO;
    7.  
    8. public class ExtractSubstance : EditorWindow {
    9.  
    10.     private SubstanceArchive substance;
    11.     private bool generateAllOuputs;
    12.  
    13.     [MenuItem ("Window/Extract Substance")]
    14.     static void Init() {
    15.         EditorWindow.GetWindow( typeof( ExtractSubstance ) );
    16.     }
    17.  
    18.     void OnGUI() {
    19.         substance = EditorGUILayout.ObjectField( "Substance", substance, typeof( SubstanceArchive ), false ) as SubstanceArchive;
    20.         generateAllOuputs = EditorGUILayout.Toggle( "Generate All Outputs", generateAllOuputs );
    21.  
    22.         if ( GUILayout.Button( "Extract" ) ) {
    23.             string fromPath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    24.             SubstanceImporter fromImporter = AssetImporter.GetAtPath( fromPath ) as SubstanceImporter;
    25.             int fromMaterialCount = fromImporter.GetMaterialCount();
    26.             ProceduralMaterial[] fromMaterials = fromImporter.GetMaterials();
    27.  
    28.             fromPath = fromPath.Replace( "/" + substance.name + ".sbsar", "" );
    29.  
    30.             if ( ! Directory.Exists( fromPath + "/" + substance.name ) ) {
    31.                 AssetDatabase.CreateFolder( fromPath, substance.name );
    32.             }
    33.  
    34.             if ( fromMaterialCount > 0 ) foreach ( ProceduralMaterial fromMaterial in fromMaterials ) {
    35.                 bool generateAllOuputsCache = fromImporter.GetGenerateAllOutputs( fromMaterial );
    36.  
    37.                 fromMaterial.isReadable = true;
    38.  
    39.                 if ( generateAllOuputs ) {
    40.                     fromImporter.SetGenerateAllOutputs( fromMaterial, true );
    41.                 }
    42.  
    43.                 if ( ! Directory.Exists( fromPath + "/" + substance.name + "/" + fromMaterial.name ) ) {
    44.                     AssetDatabase.CreateFolder( fromPath + "/" + substance.name, fromMaterial.name );
    45.                 }
    46.  
    47.                 string toPathBase = fromPath + "/" + substance.name + "/" + fromMaterial.name + "/";
    48.                 Material newMaterial = new Material( fromMaterial.shader );
    49.  
    50.                 newMaterial.CopyPropertiesFromMaterial( fromMaterial );
    51.  
    52.                 AssetDatabase.CreateAsset( newMaterial, toPathBase + fromMaterial.name + ".mat" );
    53.  
    54.                 int propertyCount = ShaderUtil.GetPropertyCount( newMaterial.shader );
    55.                 Texture[] materialTextures = fromMaterial.GetGeneratedTextures();
    56.  
    57.                 if ( materialTextures.Length > 0 ) foreach ( ProceduralTexture materialTexture in materialTextures ) {
    58.                     fromMaterial.isReadable = true;
    59.  
    60.                     Texture2D newTexture = new Texture2D( materialTexture.width, materialTexture.height );
    61.  
    62.                     newTexture.anisoLevel = materialTexture.anisoLevel;
    63.                     newTexture.filterMode = materialTexture.filterMode;
    64.                     newTexture.mipMapBias = materialTexture.mipMapBias;
    65.                     newTexture.wrapMode = materialTexture.wrapMode;
    66.  
    67.                     Color32[] pixels = materialTexture.GetPixels32( 0, 0, materialTexture.width, materialTexture.height );
    68.  
    69.                     if ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Normal ) for ( int i = 0; i < pixels.Length; i++ ) {
    70.                         pixels[i].a = 255;
    71.                     }
    72.  
    73.                     newTexture.SetPixels32( pixels );
    74.  
    75.                     string newTexturePath = toPathBase + materialTexture.name + ".png";
    76.  
    77.                     File.WriteAllBytes( newTexturePath, newTexture.EncodeToPNG() );
    78.  
    79.                     AssetDatabase.Refresh();
    80.  
    81.                     Texture newTextureAsset = Resources.LoadAssetAtPath( newTexturePath, typeof( Texture ) ) as Texture;
    82.  
    83.                     if ( propertyCount > 0 ) for ( int i = 0; i < propertyCount; i++ ) {
    84.                         if ( ShaderUtil.GetPropertyType( newMaterial.shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv ) {
    85.                             string propertyName = ShaderUtil.GetPropertyName( newMaterial.shader, i );
    86.  
    87.                             if ( newMaterial.GetTexture( propertyName ).name == newTextureAsset.name  ) {
    88.                                 newMaterial.SetTexture( propertyName, newTextureAsset );
    89.                             }
    90.                         }
    91.                     }
    92.                 }
    93.  
    94.                 if ( generateAllOuputs  ( ! generateAllOuputsCache ) ) {
    95.                     fromImporter.SetGenerateAllOutputs( fromMaterial, false );
    96.                 }
    97.  
    98.                 fromMaterial.isReadable = true;
    99.  
    100.                 AssetDatabase.Refresh();
    101.             }
    102.  
    103.             AssetDatabase.Refresh();
    104.         }
    105.     }
    106. }
    107.  
    Generate all outputs is now optional and will still reverse it. This is to prevent changes from being permanently made to a substance.
     
  13. EricBatut

    EricBatut

    Joined:
    Feb 14, 2012
    Posts:
    8
    Hi Krileon

    Your Substance work is certainly impressive :)

    To return to your original issue, it seems like you would like to bake all substances to bitmaps when you build your project, while still enjoying the Substance workflow in the Editor. I hope I understood this correctly, if not please correct me.

    There is a very easy way of achieving this: in the ProceduralMaterialInspector, near the texture size comboboxes, there is also a "Loading Behavior" combobox. What it does is detailed here: http://docs.unity3d.com/Documentation/ScriptReference/ProceduralLoadingBehavior.html . Setting the Loading Behavior of your Substances to "Bake and Discard" should do exactly this: at build time, bitmaps will be automatically generated from the Substances and inserted in your build. You project will keep the ProceduralMaterials, but when loaded they will just fetch the corresponding bitmaps so that you don't pay the generation time.

    Is this what you were trying to do?

    Best Regards,
    Eric
     
    Kurius likes this.
  14. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    No, I want to bake to bitmaps in the editor including the material, shader, and the shader properties. I then will dispose of the substance as I have no further use for it.

    Unfortunately I can not seam to 1:1 match the "Export Bitmaps" usage, but I really wish I could. I'm finding no documentation on how to use "Export Bitmaps" from script or mimic how it properly extracts normals (my biggest issue has been normals.. all other textures come out fine).

    I however am nearly done with my script and will be able to successfully extract the textures, the material, shader properties, and map the new textures to it. I'll be fixing the issue of normals not extracting properly by using Heightmap as the Normal map and setting it to generate from grayscale.
     
  15. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Ok, good news everyone. Believe I'm finally successful. The only manual work you need to do is to set the Compression for the substance material to RAW as I have yet to figure out how to automate this. The below will force all materials to generate all ouputs (it has to be done twice for some reason, don't ask why, becauseI don't know). Next it creates a material with the same shader and copies over its properties. Now it grabs all the textures and generates new ones from the pixel colors. Now this works fine for everything except the Normal, which doesn't come out as blue an ready to go. So what I've done is converted the Height to a Normal from Grayscale and calculated the height based off the normal strength property (if it exists). This gives you a (in my opinion) 1:1 result.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.IO;
    7.  
    8. public class ExtractSubstance : EditorWindow {
    9.  
    10.     private SubstanceArchive substance;
    11.  
    12.     [MenuItem ("Window/Extract Substance")]
    13.     static void Init() {
    14.         EditorWindow.GetWindow( typeof( ExtractSubstance ) );
    15.     }
    16.  
    17.     void OnGUI() {
    18.         substance = EditorGUILayout.ObjectField( "Substance", substance, typeof( SubstanceArchive ), false ) as SubstanceArchive;
    19.  
    20.         if ( GUILayout.Button( "Extract" ) ) {
    21.             string fromPath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    22.             SubstanceImporter fromImporter = AssetImporter.GetAtPath( fromPath ) as SubstanceImporter;
    23.             int fromMaterialCount = fromImporter.GetMaterialCount();
    24.             ProceduralMaterial[] fromMaterials = fromImporter.GetMaterials();
    25.  
    26.             fromPath = fromPath.Replace( "/" + substance.name + ".sbsar", "" );
    27.  
    28.             if ( ! Directory.Exists( fromPath + "/" + substance.name ) ) {
    29.                 AssetDatabase.CreateFolder( fromPath, substance.name );
    30.  
    31.                 AssetDatabase.Refresh();
    32.             }
    33.  
    34.             int index = 0;
    35.             bool[] revert = new bool[fromMaterialCount];
    36.  
    37.             if ( fromMaterialCount > 0 ) foreach ( ProceduralMaterial fromMaterial in fromMaterials ) {
    38.                 revert[index] = fromImporter.GetGenerateAllOutputs( fromMaterial );
    39.  
    40.                 fromImporter.SetGenerateAllOutputs( fromMaterial, true );
    41.  
    42.                 index++;
    43.             }
    44.  
    45.             index = 0;
    46.  
    47.             if ( fromMaterialCount > 0 ) foreach ( ProceduralMaterial fromMaterial in fromMaterials ) {
    48.                 fromMaterial.isReadable = true;
    49.  
    50.                 fromImporter.SetGenerateAllOutputs( fromMaterial, true );
    51.  
    52.                 if ( ! Directory.Exists( fromPath + "/" + substance.name + "/" + fromMaterial.name ) ) {
    53.                     AssetDatabase.CreateFolder( fromPath + "/" + substance.name, fromMaterial.name );
    54.  
    55.                     AssetDatabase.Refresh();
    56.                 }
    57.  
    58.                 string toPathBase = fromPath + "/" + substance.name + "/" + fromMaterial.name + "/";
    59.                 Material newMaterial = new Material( fromMaterial.shader );
    60.  
    61.                 newMaterial.CopyPropertiesFromMaterial( fromMaterial );
    62.  
    63.                 AssetDatabase.CreateAsset( newMaterial, toPathBase + fromMaterial.name + ".mat" );
    64.  
    65.                 AssetDatabase.Refresh();
    66.  
    67.                 int propertyCount = ShaderUtil.GetPropertyCount( newMaterial.shader );
    68.                 Texture[] materialTextures = fromMaterial.GetGeneratedTextures();
    69.  
    70.                 if ( materialTextures.Length > 0 ) foreach ( ProceduralTexture materialTexture in materialTextures ) {
    71.                     fromMaterial.isReadable = true;
    72.  
    73.                     Texture2D newTexture = new Texture2D( materialTexture.width, materialTexture.height );
    74.  
    75.                     newTexture.anisoLevel = materialTexture.anisoLevel;
    76.                     newTexture.filterMode = materialTexture.filterMode;
    77.                     newTexture.mipMapBias = materialTexture.mipMapBias;
    78.                     newTexture.wrapMode = materialTexture.wrapMode;
    79.  
    80.                     Color32[] pixels = materialTexture.GetPixels32( 0, 0, materialTexture.width, materialTexture.height );
    81.  
    82.                     if ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Normal ) for ( int i = 0; i < pixels.Length; i++ ) {
    83.                         pixels[i].a = 255;
    84.                     }
    85.  
    86.                     newTexture.SetPixels32( pixels );
    87.  
    88.                     string newTexturePath = toPathBase + materialTexture.name + ".png";
    89.  
    90.                     File.WriteAllBytes( newTexturePath, newTexture.EncodeToPNG() );
    91.  
    92.                     AssetDatabase.Refresh();
    93.  
    94.                     Texture newTextureAsset = Resources.LoadAssetAtPath( newTexturePath, typeof( Texture ) ) as Texture;
    95.  
    96.                     if ( propertyCount > 0 ) for ( int i = 0; i < propertyCount; i++ ) {
    97.                         if ( ShaderUtil.GetPropertyType( newMaterial.shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv ) {
    98.                             string propertyName = ShaderUtil.GetPropertyName( newMaterial.shader, i );
    99.  
    100.                             if ( ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Height )  ( propertyName == "_BumpMap" ) ) {
    101.                                 newMaterial.SetTexture( propertyName, newTextureAsset );
    102.                             } else if ( newMaterial.GetTexture( propertyName ).name == newTextureAsset.name  ) {
    103.                                 newMaterial.SetTexture( propertyName, newTextureAsset );
    104.                             }
    105.                         }
    106.                     }
    107.  
    108.                     float normalStrength = 0.15f;
    109.  
    110.                     if ( fromMaterial.HasProceduralProperty( "Normal_Strength" ) ) {
    111.                         normalStrength = fromMaterial.GetProceduralFloat( "Normal_Strength" ) * 0.015f;
    112.                     }
    113.  
    114.                     if ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Height ) {
    115.                         TextureImporter textureImporter = AssetImporter.GetAtPath( newTexturePath ) as TextureImporter;
    116.  
    117.                         textureImporter.textureType = TextureImporterType.Bump;
    118.                         textureImporter.convertToNormalmap = true;
    119.                         textureImporter.heightmapScale = normalStrength;
    120.  
    121.                         AssetDatabase.ImportAsset( newTexturePath );
    122.                     }
    123.                 }
    124.  
    125.                 if ( ! revert[index] ) {
    126.                     fromImporter.SetGenerateAllOutputs( fromMaterial, false );
    127.                 }
    128.  
    129.                 AssetDatabase.Refresh();
    130.  
    131.                 index++;
    132.             }
    133.  
    134.             AssetDatabase.Refresh();
    135.         }
    136.     }
    137. }
    138.  
    If I make any more progress I'll be sure to post it, but I believe this finally fits my needs.
     
  16. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Ok, I've upgraded it so it can accept an array of substances so you can run it on a group of substances if you want. I've also upgraded it to do a asset import for specific files instead of trying to do an entire asset database import. So this should speed it up a little bit. I've also upgraded it to auto force the material to RAW output so there is no longer any manual steps to prepare the substance for extraction. The only issue so far is reverting the RAW. I have it reverting, but it doesn't disable Standalone override (not sure how yet and I can't seam to change Default platform settings).

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.IO;
    7.  
    8. public class ExtractSubstance : EditorWindow {
    9.  
    10.     private int size;
    11.     private SubstanceArchive[] substances;
    12.  
    13.     [MenuItem ("Window/Extract Substance")]
    14.     static void Init() {
    15.         EditorWindow.GetWindow( typeof( ExtractSubstance ) );
    16.     }
    17.  
    18.     void OnGUI() {
    19.         if ( size == 0 ) {
    20.             size = 1;
    21.         }
    22.  
    23.         size = EditorGUILayout.IntField( "Substances", size );
    24.  
    25.         if ( substances == null ) {
    26.             substances = new SubstanceArchive[size];
    27.         }
    28.  
    29.         if ( substances.Length != size ) {
    30.             SubstanceArchive[] cache = substances;
    31.  
    32.             substances = new SubstanceArchive[size];
    33.  
    34.             for ( int i = 0; i < ( cache.Length > size ? size : cache.Length ); i++ ) {
    35.                 substances[i] = cache[i];
    36.             }
    37.         }
    38.  
    39.         for ( int i = 0; i < size; i++ ) {
    40.             substances[i] = EditorGUILayout.ObjectField( "Substance " + ( i + 1 ), substances[i], typeof( SubstanceArchive ), false ) as SubstanceArchive;
    41.         }
    42.  
    43.         if ( GUILayout.Button( "Extract" ) ) {
    44.             for ( int subs = 0; subs < size; subs++ ) {
    45.                 SubstanceArchive substance = substances[subs];
    46.  
    47.                 if ( substance == null ) {
    48.                     continue;
    49.                 }
    50.  
    51.                 string substancePath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    52.                 SubstanceImporter substanceImporter = AssetImporter.GetAtPath( substancePath ) as SubstanceImporter;
    53.                 int substanceMaterialCount = substanceImporter.GetMaterialCount();
    54.                 ProceduralMaterial[] substanceMaterials = substanceImporter.GetMaterials();
    55.  
    56.                 if ( substanceMaterialCount <= 0 ) {
    57.                     continue;
    58.                 }
    59.  
    60.                 string basePath = substancePath.Replace( "/" + substance.name + ".sbsar", "" );
    61.  
    62.                 if ( ! Directory.Exists( basePath + "/" + substance.name ) ) {
    63.                     AssetDatabase.CreateFolder( basePath, substance.name );
    64.  
    65.                     AssetDatabase.ImportAsset( basePath + "/" + substance.name );
    66.                 }
    67.  
    68.                 int index = 0;
    69.                 bool[] revertOutput = new bool[substanceMaterialCount];
    70.                 int[] revertPlatform = new int[substanceMaterialCount];
    71.  
    72.                 // Why do we have to prepare the materials twice? For some reason it just refuses to "take" with doing it ounce!
    73.                 foreach ( ProceduralMaterial substanceMaterial in substanceMaterials ) {
    74.                     substanceMaterial.isReadable = true;
    75.  
    76.                     revertOutput[index] = substanceImporter.GetGenerateAllOutputs( substanceMaterial );
    77.  
    78.                     int maxTextureWidth;
    79.                     int maxTextureHeight;
    80.                     int textureFormat;
    81.                     int loadBehavior;
    82.  
    83.                     substanceImporter.GetPlatformTextureSettings( substanceMaterial.name, "Standalone", out maxTextureWidth, out maxTextureHeight, out textureFormat, out loadBehavior );
    84.  
    85.                     revertPlatform[index] = textureFormat;
    86.  
    87.                     substanceImporter.SetPlatformTextureSettings( substanceMaterial.name, "Standalone", maxTextureWidth, maxTextureHeight, 1, loadBehavior );
    88.  
    89.                     substanceImporter.SetGenerateAllOutputs( substanceMaterial, true );
    90.  
    91.                     index++;
    92.                 }
    93.  
    94.                 AssetDatabase.ImportAsset( substancePath );
    95.  
    96.                 index = 0;
    97.  
    98.                 foreach ( ProceduralMaterial substanceMaterial in substanceMaterials ) {
    99.                     // Start of duplicate material prepare
    100.                     substanceMaterial.isReadable = true;
    101.  
    102.                     int maxTextureWidth;
    103.                     int maxTextureHeight;
    104.                     int textureFormat;
    105.                     int loadBehavior;
    106.  
    107.                     substanceImporter.GetPlatformTextureSettings( substanceMaterial.name, "Standalone", out maxTextureWidth, out maxTextureHeight, out textureFormat, out loadBehavior );
    108.  
    109.                     substanceImporter.SetPlatformTextureSettings( substanceMaterial.name, "Standalone", maxTextureWidth, maxTextureHeight, 1, loadBehavior );
    110.  
    111.                     substanceImporter.SetGenerateAllOutputs( substanceMaterial, true );
    112.                     // End of duplicate material prepare
    113.  
    114.                     if ( ! Directory.Exists( basePath + "/" + substance.name + "/" + substanceMaterial.name ) ) {
    115.                         AssetDatabase.CreateFolder( basePath + "/" + substance.name, substanceMaterial.name );
    116.  
    117.                         AssetDatabase.ImportAsset( basePath + "/" + substance.name + "/" + substanceMaterial.name );
    118.                     }
    119.  
    120.                     string toPathBase = basePath + "/" + substance.name + "/" + substanceMaterial.name + "/";
    121.                     Material newMaterial = new Material( substanceMaterial.shader );
    122.  
    123.                     newMaterial.CopyPropertiesFromMaterial( substanceMaterial );
    124.  
    125.                     AssetDatabase.CreateAsset( newMaterial, toPathBase + substanceMaterial.name + ".mat" );
    126.  
    127.                     AssetDatabase.ImportAsset( toPathBase + substanceMaterial.name + ".mat" );
    128.  
    129.                     int propertyCount = ShaderUtil.GetPropertyCount( newMaterial.shader );
    130.                     Texture[] materialTextures = substanceMaterial.GetGeneratedTextures();
    131.  
    132.                     if ( ( materialTextures.Length <= 0 ) || ( propertyCount <= 0 ) ) {
    133.                         continue;
    134.                     }
    135.  
    136.                     foreach ( ProceduralTexture materialTexture in materialTextures ) {
    137.                         Texture2D newTexture = new Texture2D( materialTexture.width, materialTexture.height );
    138.  
    139.                         newTexture.anisoLevel = materialTexture.anisoLevel;
    140.                         newTexture.filterMode = materialTexture.filterMode;
    141.                         newTexture.mipMapBias = materialTexture.mipMapBias;
    142.                         newTexture.wrapMode = materialTexture.wrapMode;
    143.  
    144.                         Color32[] pixels = materialTexture.GetPixels32( 0, 0, materialTexture.width, materialTexture.height );
    145.  
    146.                         if ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Normal ) for ( int i = 0; i < pixels.Length; i++ ) {
    147.                             pixels[i].a = 255;
    148.                         }
    149.  
    150.                         newTexture.SetPixels32( pixels );
    151.  
    152.                         string newTexturePath = toPathBase + materialTexture.name + ".png";
    153.  
    154.                         File.WriteAllBytes( newTexturePath, newTexture.EncodeToPNG() );
    155.  
    156.                         AssetDatabase.ImportAsset( newTexturePath );
    157.  
    158.                         Texture newTextureAsset = Resources.LoadAssetAtPath( newTexturePath, typeof( Texture ) ) as Texture;
    159.  
    160.                         for ( int i = 0; i < propertyCount; i++ ) {
    161.                             if ( ShaderUtil.GetPropertyType( newMaterial.shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv ) {
    162.                                 string propertyName = ShaderUtil.GetPropertyName( newMaterial.shader, i );
    163.  
    164.                                 if ( ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Height )  ( propertyName == "_BumpMap" ) ) {
    165.                                     newMaterial.SetTexture( propertyName, newTextureAsset );
    166.                                 } else if ( newMaterial.GetTexture( propertyName ).name == newTextureAsset.name  ) {
    167.                                     newMaterial.SetTexture( propertyName, newTextureAsset );
    168.                                 }
    169.                             }
    170.                         }
    171.  
    172.                         float normalStrength = 0.15f;
    173.  
    174.                         if ( substanceMaterial.HasProceduralProperty( "Normal_Strength" ) ) {
    175.                             normalStrength = substanceMaterial.GetProceduralFloat( "Normal_Strength" ) * 0.015f;
    176.                         }
    177.  
    178.                         if ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Height ) {
    179.                             TextureImporter textureImporter = AssetImporter.GetAtPath( newTexturePath ) as TextureImporter;
    180.  
    181.                             textureImporter.textureType = TextureImporterType.Bump;
    182.                             textureImporter.convertToNormalmap = true;
    183.                             textureImporter.heightmapScale = normalStrength;
    184.  
    185.                             AssetDatabase.ImportAsset( newTexturePath );
    186.                         }
    187.                     }
    188.  
    189.                     if ( ! revertOutput[index] ) {
    190.                         substanceImporter.SetGenerateAllOutputs( substanceMaterial, false );
    191.                     }
    192.  
    193.                     if ( revertPlatform[index] != textureFormat ) {
    194.                         substanceImporter.SetPlatformTextureSettings( substanceMaterial.name, "Standalone", maxTextureWidth, maxTextureHeight, revertPlatform[index], loadBehavior );
    195.                     }
    196.  
    197.                     index++;
    198.                 }
    199.  
    200.                 AssetDatabase.ImportAsset( substancePath );
    201.             }
    202.         }
    203.     }
    204. }
    205.  
    I also went ahead and made a comment where it does the duplicate material prepare. I have yet to figure out why I can to set the materials properties twice for it to work. It's really weird and very frustrating, but I haven't figured it out yet. I even re-import the substance after making the changes and it just doesn't care. I don't think asset importer will work on procedural materials and textures either, but maybe can give that a try. Certainly open to suggestions if anyone wants to contribute!

    /rant on
    Also a stab to Unity. Shame on you! Your documentation SUCKS. Your reference documentation is -out-of-date- for crying out loud. PLEASE fix this nonsense and PLEASE provide example usages for ALL functions, thank you. I would also like to request a license support forum. As a license holder it's pretty saddening that not a single person from Unity has tried to help me. It is your product and no one knows it best except you Unity. So please help the people who bought a license for a very large sum of money. Asset developers selling $20 assets provide FAR superior support than Unity it self and it's just disrespectful to those who have invested in you.
    /rant off
     
    Last edited: Sep 21, 2013
  17. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    ExportBitmaps is marked Internal in SubstanceImporter. Any idea how I could possible invoke it? This would eliminate all my troubles with extracting the textures. Why is this Internal to begin with? Heck why is ClearPlatformTextureSettings also marked Internal. Doesn't make any sense.

    Below is the decompiled source for the SubstanceImporter.

    Code (csharp):
    1.  
    2. using System;
    3. using System.Runtime.CompilerServices;
    4. using UnityEngine;
    5. namespace UnityEditor
    6. {
    7.     public sealed class SubstanceImporter : AssetImporter
    8.     {
    9.         [WrapperlessIcall ]
    10.         [MethodImpl (MethodImplOptions.InternalCall)]
    11.         public extern string[] GetPrototypeNames ();
    12.         [WrapperlessIcall ]
    13.         [MethodImpl (MethodImplOptions.InternalCall)]
    14.         public extern int GetMaterialCount ();
    15.         [WrapperlessIcall ]
    16.         [MethodImpl (MethodImplOptions.InternalCall)]
    17.         public extern ProceduralMaterial[] GetMaterials ();
    18.         [WrapperlessIcall ]
    19.         [MethodImpl (MethodImplOptions.InternalCall)]
    20.         public extern string CloneMaterial (ProceduralMaterial material);
    21.         [WrapperlessIcall ]
    22.         [MethodImpl (MethodImplOptions.InternalCall)]
    23.         public extern string InstantiateMaterial (string prototypeName);
    24.         [WrapperlessIcall ]
    25.         [MethodImpl (MethodImplOptions.InternalCall)]
    26.         public extern void DestroyMaterial (ProceduralMaterial material);
    27.         [WrapperlessIcall ]
    28.         [MethodImpl (MethodImplOptions.InternalCall)]
    29.         public extern void ResetMaterial (ProceduralMaterial material);
    30.         [WrapperlessIcall ]
    31.         [MethodImpl (MethodImplOptions.InternalCall)]
    32.         public extern bool RenameMaterial (ProceduralMaterial material, string name);
    33.         [WrapperlessIcall ]
    34.         [MethodImpl (MethodImplOptions.InternalCall)]
    35.         public extern void OnShaderModified (ProceduralMaterial material);
    36.         public Vector2 GetMaterialOffset (ProceduralMaterial material)
    37.         {
    38.             return this.GetMaterialInformation (material).offset;
    39.         }
    40.         public void SetMaterialOffset (ProceduralMaterial material, Vector2 offset)
    41.         {
    42.             ProceduralMaterialInformation materialInformation = this.GetMaterialInformation (material);
    43.             materialInformation.offset = offset;
    44.             this.SetMaterialInformation (material, materialInformation);
    45.         }
    46.         public Vector2 GetMaterialScale (ProceduralMaterial material)
    47.         {
    48.             return this.GetMaterialInformation (material).scale;
    49.         }
    50.         public void SetMaterialScale (ProceduralMaterial material, Vector2 scale)
    51.         {
    52.             ProceduralMaterialInformation materialInformation = this.GetMaterialInformation (material);
    53.             materialInformation.scale = scale;
    54.             this.SetMaterialInformation (material, materialInformation);
    55.         }
    56.         public bool GetGenerateAllOutputs (ProceduralMaterial material)
    57.         {
    58.             return this.GetMaterialInformation (material).generateAllOutputs;
    59.         }
    60.         public void SetGenerateAllOutputs (ProceduralMaterial material, bool generated)
    61.         {
    62.             ProceduralMaterialInformation materialInformation = this.GetMaterialInformation (material);
    63.             materialInformation.generateAllOutputs = generated;
    64.             this.SetMaterialInformation (material, materialInformation);
    65.         }
    66.         public int GetAnimationUpdateRate (ProceduralMaterial material)
    67.         {
    68.             return this.GetMaterialInformation (material).animationUpdateRate;
    69.         }
    70.         public void SetAnimationUpdateRate (ProceduralMaterial material, int animation_update_rate)
    71.         {
    72.             ProceduralMaterialInformation materialInformation = this.GetMaterialInformation (material);
    73.             materialInformation.animationUpdateRate = animation_update_rate;
    74.             this.SetMaterialInformation (material, materialInformation);
    75.         }
    76.         [WrapperlessIcall ]
    77.         [MethodImpl (MethodImplOptions.InternalCall)]
    78.         public extern ProceduralOutputType GetTextureAlphaSource (ProceduralMaterial material, string textureName);
    79.         [WrapperlessIcall ]
    80.         [MethodImpl (MethodImplOptions.InternalCall)]
    81.         public extern void SetTextureAlphaSource (ProceduralMaterial material, string textureName, ProceduralOutputType alphaSource);
    82.         [WrapperlessIcall ]
    83.         [MethodImpl (MethodImplOptions.InternalCall)]
    84.         public extern bool GetPlatformTextureSettings (string materialName, string platform, out int maxTextureWidth, out int maxTextureHeight, out int textureFormat, out int loadBehavior);
    85.         [WrapperlessIcall ]
    86.         [MethodImpl (MethodImplOptions.InternalCall)]
    87.         public extern void SetPlatformTextureSettings (string materialName, string platform, int maxTextureWidth, int maxTextureHeight, int textureFormat, int loadBehavior);
    88.         [WrapperlessIcall ]
    89.         [MethodImpl (MethodImplOptions.InternalCall)]
    90.         private extern ProceduralMaterialInformation GetMaterialInformation (ProceduralMaterial material);
    91.         [WrapperlessIcall ]
    92.         [MethodImpl (MethodImplOptions.InternalCall)]
    93.         private extern void SetMaterialInformation (ProceduralMaterial material, ProceduralMaterialInformation information);
    94.         [WrapperlessIcall ]
    95.         [MethodImpl (MethodImplOptions.InternalCall)]
    96.         internal static extern string ProceduralOutputTypeToUnityShaderPropertyName (ProceduralOutputType substanceType);
    97.         internal static bool IsProceduralTextureSlot (Material material, Texture tex, string name)
    98.         {
    99.             return material is ProceduralMaterial  tex is ProceduralTexture  SubstanceImporter.ProceduralOutputTypeToUnityShaderPropertyName ((tex as ProceduralTexture).GetProceduralOutputType ()) == name  SubstanceImporter.IsSubstanceParented (tex as ProceduralTexture, material as ProceduralMaterial);
    100.         }
    101.         [WrapperlessIcall ]
    102.         [MethodImpl (MethodImplOptions.InternalCall)]
    103.         internal extern void ClearPlatformTextureSettings (string materialName, string platform);
    104.         [WrapperlessIcall ]
    105.         [MethodImpl (MethodImplOptions.InternalCall)]
    106.         internal extern void OnTextureInformationsChanged (ProceduralTexture texture);
    107.         [WrapperlessIcall ]
    108.         [MethodImpl (MethodImplOptions.InternalCall)]
    109.         internal extern void ExportBitmaps (ProceduralMaterial material);
    110.         [WrapperlessIcall ]
    111.         [MethodImpl (MethodImplOptions.InternalCall)]
    112.         internal static extern bool IsSubstanceParented (ProceduralTexture texture, ProceduralMaterial material);
    113.     }
    114. }
    115.  
     
    Last edited: Sep 21, 2013
  18. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    949
    I have not tested the following, but you could invoke "ExportBitmaps" using reflection something like this:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. using System.Reflection;
    6.  
    7. public static YourSubstanceUtility {
    8.  
    9.     public static void InvokeExportBitmaps(SubstanceImporter importer, ProceduralMaterial material) {
    10.         var substanceImporterType = typeof(SubstanceImporter);
    11.        
    12.         var miExportBitmaps = substanceImporterType.GetMethod("ExportBitmaps", BindingFlags.Instance | BindingFlags.NonPublic);
    13.         if (miExportBitmaps == null)
    14.             throw new System.NullReferenceException("Unable to locate 'SubstanceImporter.ExportBitmaps'");
    15.            
    16.         miExportBitmaps.Invoke(importer, new object[] { material });
    17.     }
    18.  
    19. }
    20.  
     
  19. sjm-tech

    sjm-tech

    Joined:
    Sep 23, 2010
    Posts:
    695
    Awesome! I will follow the develop...nice work!
     
  20. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Brilliant! That works! Now need to somehow get rid of the popup folder selection window and force it to a directory and it'd be complete!
     
  21. sjm-tech

    sjm-tech

    Joined:
    Sep 23, 2010
    Posts:
    695
    I found that the "_normal" generated texture is set as type "texture" and "_height" generated texture is set as type "Normal map".
    Also I found that the "_normal" generated by script is different by the manual exported(Export bitmap).
    Tested with parallax specular shader.

    Edit:
    Sorry it is a known point.
     
    Last edited: Sep 21, 2013
  22. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Right, the normals won't 1:1 export. As far as I can tell there's no way to perfectly mimic the ExportBitmaps usage. I'm working on a new script that uses ExportBitmaps, but it'll require manual action for every single material, which I guess is ok as it'll at least be 100% accurate.
     
  23. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Success! The below is a 100% guaranteed 1:1 match to using ExportBitmaps. I truly mean 100%, because it uses ExportBitmaps.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using System.IO;
    7. using System.Reflection;
    8.  
    9. public class MassExportBitmaps : EditorWindow {
    10.  
    11.     private int size;
    12.     private SubstanceArchive[] substances;
    13.  
    14.     [MenuItem ("Window/Mass Export Bitmaps")]
    15.     static void Init() {
    16.         EditorWindow.GetWindow( typeof( MassExportBitmaps ) );
    17.     }
    18.  
    19.     void OnGUI() {
    20.         if ( size == 0 ) {
    21.             size = 1;
    22.         }
    23.  
    24.         size = EditorGUILayout.IntField( "Substances", size );
    25.  
    26.         if ( substances == null ) {
    27.             substances = new SubstanceArchive[size];
    28.         }
    29.  
    30.         if ( substances.Length != size ) {
    31.             SubstanceArchive[] cache = substances;
    32.  
    33.             substances = new SubstanceArchive[size];
    34.  
    35.             for ( int i = 0; i < ( cache.Length > size ? size : cache.Length ); i++ ) {
    36.                 substances[i] = cache[i];
    37.             }
    38.         }
    39.  
    40.         for ( int i = 0; i < size; i++ ) {
    41.             substances[i] = EditorGUILayout.ObjectField( "Substance " + ( i + 1 ), substances[i], typeof( SubstanceArchive ), false ) as SubstanceArchive;
    42.         }
    43.  
    44.         if ( GUILayout.Button( "Extract" ) ) {
    45.             for ( int subs = 0; subs < size; subs++ ) {
    46.                 SubstanceArchive substance = substances[subs];
    47.  
    48.                 if ( substance == null ) {
    49.                     continue;
    50.                 }
    51.  
    52.                 string substancePath = AssetDatabase.GetAssetPath( substance.GetInstanceID() );
    53.                 SubstanceImporter substanceImporter = AssetImporter.GetAtPath( substancePath ) as SubstanceImporter;
    54.                 int substanceMaterialCount = substanceImporter.GetMaterialCount();
    55.                 ProceduralMaterial[] substanceMaterials = substanceImporter.GetMaterials();
    56.  
    57.                 if ( substanceMaterialCount <= 0 ) {
    58.                     continue;
    59.                 }
    60.  
    61.                 string basePath = substancePath.Replace( "/" + substance.name + ".sbsar", "" );
    62.  
    63.                 if ( ! Directory.Exists( basePath + "/" + substance.name ) ) {
    64.                     AssetDatabase.CreateFolder( basePath, substance.name );
    65.  
    66.                     AssetDatabase.ImportAsset( basePath + "/" + substance.name );
    67.                 }
    68.  
    69.                 if ( ! Directory.Exists( "EXPORT_HERE" ) ) {
    70.                     Directory.CreateDirectory( "EXPORT_HERE" );
    71.                 }
    72.  
    73.                 System.Type substanceImporterType = typeof( SubstanceImporter );
    74.                 MethodInfo exportBitmaps = substanceImporterType.GetMethod( "ExportBitmaps", BindingFlags.Instance | BindingFlags.NonPublic );
    75.  
    76.                 foreach ( ProceduralMaterial substanceMaterial in substanceMaterials ) {
    77.                     bool generateAllOutputs = substanceImporter.GetGenerateAllOutputs( substanceMaterial );
    78.  
    79.                     if ( ! Directory.Exists( basePath + "/" + substance.name + "/" + substanceMaterial.name ) ) {
    80.                         AssetDatabase.CreateFolder( basePath + "/" + substance.name, substanceMaterial.name );
    81.  
    82.                         AssetDatabase.ImportAsset( basePath + "/" + substance.name + "/" + substanceMaterial.name );
    83.                     }
    84.  
    85.                     string materialPath = basePath + "/" + substance.name + "/" + substanceMaterial.name + "/";
    86.                     Material newMaterial = new Material( substanceMaterial.shader );
    87.  
    88.                     newMaterial.CopyPropertiesFromMaterial( substanceMaterial );
    89.  
    90.                     AssetDatabase.CreateAsset( newMaterial, materialPath + substanceMaterial.name + ".mat" );
    91.  
    92.                     AssetDatabase.ImportAsset( materialPath + substanceMaterial.name + ".mat" );
    93.  
    94.                     substanceImporter.SetGenerateAllOutputs( substanceMaterial, true );
    95.  
    96.                     exportBitmaps.Invoke( substanceImporter, new object[] { substanceMaterial } );
    97.  
    98.                     if ( ! generateAllOutputs ) {
    99.                         substanceImporter.SetGenerateAllOutputs( substanceMaterial, false );
    100.                     }
    101.  
    102.                     string[] exportedTextures = Directory.GetFiles( "EXPORT_HERE" );
    103.  
    104.                     if ( exportedTextures.Length > 0 ) foreach ( string exportedTexture in exportedTextures ) {
    105.                         File.Move( exportedTexture, materialPath + exportedTexture.Replace( "EXPORT_HERE", "" ) );
    106.                     }
    107.  
    108.                     AssetDatabase.Refresh();
    109.  
    110.                     int propertyCount = ShaderUtil.GetPropertyCount( newMaterial.shader );
    111.                     Texture[] materialTextures = substanceMaterial.GetGeneratedTextures();
    112.  
    113.                     if ( ( materialTextures.Length <= 0 ) || ( propertyCount <= 0 ) ) {
    114.                         continue;
    115.                     }
    116.  
    117.                     foreach ( ProceduralTexture materialTexture in materialTextures ) {
    118.                         string newTexturePath = materialPath + materialTexture.name + ".tga";
    119.  
    120.                         Texture newTextureAsset = Resources.LoadAssetAtPath( newTexturePath, typeof( Texture ) ) as Texture;
    121.  
    122.                         for ( int i = 0; i < propertyCount; i++ ) {
    123.                             if ( ShaderUtil.GetPropertyType( newMaterial.shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv ) {
    124.                                 string propertyName = ShaderUtil.GetPropertyName( newMaterial.shader, i );
    125.  
    126.                                 if ( newMaterial.GetTexture( propertyName ).name == newTextureAsset.name  ) {
    127.                                     newMaterial.SetTexture( propertyName, newTextureAsset );
    128.                                 }
    129.                             }
    130.                         }
    131.  
    132.                         if ( materialTexture.GetProceduralOutputType() == ProceduralOutputType.Normal ) {
    133.                             TextureImporter textureImporter = AssetImporter.GetAtPath( newTexturePath ) as TextureImporter;
    134.  
    135.                             textureImporter.textureType = TextureImporterType.Bump;
    136.  
    137.                             AssetDatabase.ImportAsset( newTexturePath );
    138.                         }
    139.                     }
    140.                 }
    141.  
    142.                 if ( Directory.Exists( "EXPORT_HERE" ) ) {
    143.                     Directory.Delete( "EXPORT_HERE" );
    144.                 }
    145.             }
    146.         }
    147.     }
    148. }
    149.  
    Now, read this carefully as you'll need to know it has a manual step. Do the follow.

    1. Window > Mass Export Bitmaps
    2. Select your substance
    3. Click Extract
    4. In the pop up folder window select "EXPORT_HERE"
    5. Click "Select Folder"
    6. DONE!

    It will create the material, copy the shader properties, export the bitmaps, MOVE the bitmaps, assign the bitmaps to the material, and ensure normal is set to be a normal. It will also dispose of the "EXPORT_HERE" folder when it's done. So there is no clean up or extra work needed when done.

    Now you can use my previous script or you can use this one. I don't particularly care. The old one has no manual steps. The new one has a manual step per material, but it's 100% accurate. If I can find a way to suppress the folder dialog I'll implement it, but for now this works perfectly.
     
    Last edited: Sep 21, 2013
    kilik128 likes this.
  24. sjm-tech

    sjm-tech

    Joined:
    Sep 23, 2010
    Posts:
    695
    Your new script gives me this error.
    Edit: Works! I Did miss to select precisely the "export here" folder.

    Thanks!
    Max
     
    Last edited: Sep 21, 2013
  25. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Ok, my latest script does everything I need. I've successfully extracted all 33 of my substances for a total of 122 materials. All have been remapped and all are working and looking great. So I won't be improving on the script any further. Anyone is welcome to attempt to improve it as they please, but for me I am done for now until I need it to do something further.
     
  26. Zaddo67

    Zaddo67

    Joined:
    Aug 14, 2012
    Posts:
    489
    @Krileon. Thank you for script. This works brilliantly!!! Just what I needed.
     
  27. arturmandas

    arturmandas

    Joined:
    Sep 29, 2012
    Posts:
    197
    I couldn't get it to work - it creates a folder for export outside Assets. I'm confused - do I only need 1 script, the latest revision of MassBitmapExporter? Also, does the exported material looks like the source substance, or is it just an extraction of maps? Best
     
  28. 3Duaun

    3Duaun

    Joined:
    Dec 29, 2009
    Posts:
    600
    Is there an updated version of this script working with Unity 5.4x+
     
  29. kilik128

    kilik128

    Joined:
    Jul 15, 2013
    Posts:
    876
    got
    NullReferenceException: Object reference not set to an instance of an object
    MassExportBitmaps.OnGUI () (at Assets/Road/MassExportBitmaps.cs:248)
    System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:222)

    any help please
     
  30. Klamore74

    Klamore74

    Joined:
    Jun 17, 2013
    Posts:
    30
    I just fix a compatibility issue with sign of the export method and a specific shader problem.

    Thanks to @Krileon for their help to the community (I'm too very annoyed about editor hang with substance materials)


    Here the code working with 2017.3:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.IO;
    6. using System.Reflection;
    7.  
    8. public class MassExportSubstance : EditorWindow
    9. {
    10.  
    11.     private int size;
    12.     private SubstanceArchive[] substances;
    13.  
    14.     [MenuItem("Window/Mass Export Bitmaps")]
    15.     static void Init()
    16.     {
    17.         EditorWindow.GetWindow(typeof(MassExportSubstance));
    18.     }
    19.  
    20.     void OnGUI()
    21.     {
    22.         if (size == 0)
    23.         {
    24.             size = 1;
    25.         }
    26.  
    27.         size = EditorGUILayout.IntField("Substances", size);
    28.  
    29.         if (substances == null)
    30.         {
    31.             substances = new SubstanceArchive[size];
    32.         }
    33.  
    34.         if (substances.Length != size)
    35.         {
    36.             SubstanceArchive[] cache = substances;
    37.  
    38.             substances = new SubstanceArchive[size];
    39.  
    40.             for (int i = 0; i < (cache.Length > size ? size : cache.Length); i++)
    41.             {
    42.                 substances[i] = cache[i];
    43.             }
    44.         }
    45.  
    46.         for (int i = 0; i < size; i++)
    47.         {
    48.             substances[i] = EditorGUILayout.ObjectField("Substance " + (i + 1), substances[i], typeof(SubstanceArchive), false) as SubstanceArchive;
    49.         }
    50.  
    51.         if (GUILayout.Button("Extract"))
    52.         {
    53.             for (int subs = 0; subs < size; subs++)
    54.             {
    55.                 SubstanceArchive substance = substances[subs];
    56.  
    57.                 if (substance == null)
    58.                 {
    59.                     continue;
    60.                 }
    61.  
    62.                 string substancePath = AssetDatabase.GetAssetPath(substance.GetInstanceID());
    63.                 SubstanceImporter substanceImporter = AssetImporter.GetAtPath(substancePath) as SubstanceImporter;
    64.                 int substanceMaterialCount = substanceImporter.GetMaterialCount();
    65.                 ProceduralMaterial[] substanceMaterials = substanceImporter.GetMaterials();
    66.  
    67.                 if (substanceMaterialCount <= 0)
    68.                 {
    69.                     continue;
    70.                 }
    71.  
    72.                 string basePath = substancePath.Replace("/" + substance.name + ".sbsar", "");
    73.  
    74.                 if (!Directory.Exists(basePath + "/" + substance.name))
    75.                 {
    76.                     AssetDatabase.CreateFolder(basePath, substance.name);
    77.  
    78.                     AssetDatabase.ImportAsset(basePath + "/" + substance.name);
    79.                 }
    80.  
    81.                 if (!Directory.Exists("EXPORT_HERE"))
    82.                 {
    83.                     Directory.CreateDirectory("EXPORT_HERE");
    84.                 }
    85.  
    86.                 System.Type substanceImporterType = typeof(SubstanceImporter);
    87.                 MethodInfo exportBitmaps = substanceImporterType.GetMethod("ExportBitmaps", BindingFlags.Instance | BindingFlags.Public);
    88.  
    89.                 foreach (ProceduralMaterial substanceMaterial in substanceMaterials)
    90.                 {
    91.                     bool generateAllOutputs = substanceImporter.GetGenerateAllOutputs(substanceMaterial);
    92.  
    93.                     if (!Directory.Exists(basePath + "/" + substance.name + "/" + substanceMaterial.name))
    94.                     {
    95.                         AssetDatabase.CreateFolder(basePath + "/" + substance.name, substanceMaterial.name);
    96.  
    97.                         AssetDatabase.ImportAsset(basePath + "/" + substance.name + "/" + substanceMaterial.name);
    98.                     }
    99.  
    100.                     string materialPath = basePath + "/" + substance.name + "/" + substanceMaterial.name + "/";
    101.                     Material newMaterial = new Material(substanceMaterial.shader);
    102.  
    103.                     newMaterial.CopyPropertiesFromMaterial(substanceMaterial);
    104.  
    105.                     AssetDatabase.CreateAsset(newMaterial, materialPath + substanceMaterial.name + ".mat");
    106.  
    107.                     AssetDatabase.ImportAsset(materialPath + substanceMaterial.name + ".mat");
    108.  
    109.                     substanceImporter.SetGenerateAllOutputs(substanceMaterial, true);
    110.  
    111.                     exportBitmaps.Invoke(substanceImporter, new object[] { substanceMaterial, materialPath,true });
    112.  
    113.                     if (!generateAllOutputs)
    114.                     {
    115.                         substanceImporter.SetGenerateAllOutputs(substanceMaterial, false);
    116.                     }
    117.  
    118.                     string[] exportedTextures = Directory.GetFiles("EXPORT_HERE");
    119.  
    120.                     if (exportedTextures.Length > 0) foreach (string exportedTexture in exportedTextures)
    121.                         {
    122.                             File.Move(exportedTexture, materialPath + exportedTexture.Replace("EXPORT_HERE", ""));
    123.                         }
    124.  
    125.                     AssetDatabase.Refresh();
    126.  
    127.                     int propertyCount = ShaderUtil.GetPropertyCount(newMaterial.shader);
    128.                     Texture[] materialTextures = substanceMaterial.GetGeneratedTextures();
    129.  
    130.                     if ((materialTextures.Length <= 0) || (propertyCount <= 0))
    131.                     {
    132.                         continue;
    133.                     }
    134.  
    135.                     foreach (ProceduralTexture materialTexture in materialTextures)
    136.                     {
    137.                         string newTexturePath = materialPath + materialTexture.name + ".tga";
    138.  
    139.                         Texture newTextureAsset = AssetDatabase.LoadAssetAtPath(newTexturePath, typeof(Texture)) as Texture;
    140.  
    141.                         for (int i = 0; i < propertyCount; i++)
    142.                         {
    143.                             if (ShaderUtil.GetPropertyType(newMaterial.shader, i) == ShaderUtil.ShaderPropertyType.TexEnv)
    144.                             {
    145.                                 string propertyName = ShaderUtil.GetPropertyName(newMaterial.shader, i);
    146.  
    147.                                 Texture oldTex = newMaterial.GetTexture(propertyName);
    148.  
    149.                                 if (oldTex!=null)
    150.                                 {
    151.                                     if (newMaterial.GetTexture(propertyName).name == newTextureAsset.name)
    152.                                     {
    153.                                         newMaterial.SetTexture(propertyName, newTextureAsset);
    154.                                     }
    155.                                 }
    156.                                 else
    157.                                 {
    158.                                     Debug.LogFormat("Property {0} has null Texture", propertyName);
    159.                                 }
    160.  
    161.                             }
    162.                         }
    163.  
    164.                         if (materialTexture.GetProceduralOutputType() == ProceduralOutputType.Normal)
    165.                         {
    166.                             TextureImporter textureImporter = AssetImporter.GetAtPath(newTexturePath) as TextureImporter;
    167.  
    168.                             textureImporter.textureType = TextureImporterType.NormalMap;
    169.  
    170.                             AssetDatabase.ImportAsset(newTexturePath);
    171.                         }
    172.                     }
    173.                 }
    174.  
    175.                 if (Directory.Exists("EXPORT_HERE"))
    176.                 {
    177.                     Directory.Delete("EXPORT_HERE");
    178.                 }
    179.             }
    180.         }
    181.     }
    182. }
     
    JasonCNDG and Celtc like this.
unityunity