Search Unity

  1. We are migrating the Unity Forums to Unity Discussions by the end of July. Read our announcement for more information and let us know if you have any questions.
    Dismiss Notice
  2. Dismiss Notice

In Editor - Update Material and save it to disk

Discussion in 'Scripting' started by JDB-Artist, Aug 22, 2018.

  1. JDB-Artist

    JDB-Artist

    Joined:
    Dec 5, 2012
    Posts:
    41
    It seems to be straightforward in runtime mode to update the textures of a material and save the changes to disk. However I currently try to automate setting up materials upon the import of new textures in editor mode.

    Basically I am loading a material asset, update some of its textures and try to save the changes back to disk, but I don't seem to be able to find the right command for saving the changes back to the asset.

    Here's my code:
    Code (CSharp):
    1.  
    2. // Asset does exist, setup maps
    3.              Material material = (Material)AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[0]), typeof(Material));
    4.              if (tex_name.Contains("_col"))
    5.              {
    6.                  material.SetTexture("_MainTex", tex);
    7.              }
    8.              if (tex_name.Contains("_nor"))
    9.              {
    10.                  material.SetTexture("_BumpMap", tex);
    11.              }
    12.              if (tex_name.Contains("_par"))
    13.              {
    14.                  material.EnableKeyword("_EMISSION");
    15.                  material.SetTexture("_EmissionMap", tex);
    16.              }
    17.              // Save Updated Material to Disk              
    18.              AssetDatabase.SaveAssets();
     
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,319
  3. JDB-Artist

    JDB-Artist

    Joined:
    Dec 5, 2012
    Posts:
    41
    Thank you for your help!

    Sadly neither
    Undo.RecordObject
    nor
    EditorUtility.SetDirty
    resulted in the material to be updated and saved to disk.
     
  4. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,319
    You sure? I just checked it myself and both of these methods worked, independently. (In one of my assets I actually use both of them at once but it seems that's a bit redundant)
     
  5. JDB-Artist

    JDB-Artist

    Joined:
    Dec 5, 2012
    Posts:
    41
    I feel like I am hitting a wall here... Would you mind showing me your working example? I tried it several times now to no effect.
     
  6. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,319
    What are the symptoms of it not getting updated/saved? Are the changes not getting reflected at all? Or are they not persisting past a reload? Something else?

    This is the entirety of the snippet I used:

    Code (CSharp):
    1. var mat = AssetDatabase.LoadAssetAtPath<Material>( "Assets/Material.mat" );
    2.  
    3. // either RecordObject here
    4. Undo.RecordObject( mat, "Set Color" );
    5.  
    6. mat.SetColor( "_Color", Color.red );
    7.  
    8. // or SetDirty here
    9. EditorUtility.SetDirty( mat );
    10.  
     
  7. Cuku_

    Cuku_

    Joined:
    Sep 2, 2012
    Posts:
    11
    Thanks for the code.
    It works when setting the color, but in my case doesn't work when setting the texture.

    The texture is actually set but the when saving the asset/s the texture texture field is cleard.
    I tried saving with File - Save Project and with
    AssetDatabase.SaveAssets();
    AssetDatabase.Refresh();

    Did you encountered this with textures? Thanks.
     
  8. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,319
    If you are using a texture you're creating on the fly, you will need to save the texture as well. It is somewhat awkward to work with textures, since you need to save them to disk as an image asset, then reload the newly imported image asset (which has all its import properties reset to the default, so you also probably need to modify those and re-import), and apply that version of the texture to the material.

    In other words, whatever texture you set on the material needs to exist as an asset in the project in order for the reference to be savable.

    I'm open to anyone who has found a more elegant workflow when working with generated textures, it would be very nice to know about it.
     
    JDB-Artist likes this.
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,420
    With what little I know of how Unity is architected, I can't see any better way than this. Assets on disk can only refer to other assets on disk, full stop. :)
     
    Madgvox likes this.
  10. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,319
    I was referring more to the fact that you need to serialize a texture to disk (as PNG, for example) in order to then load it as a new different texture, rather than just writing the texture directly to disk and immediately being able to use it as you would any other type of asset. The whole create texture -> serialize to disk -> import -> modify import settings to match original texture -> re-import -> load texture workflow is garbage.

    Your point still stands, though, and is a very succinct way to put the overall concept.
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,420
    And I will agree, that really IS a garbage work flow!!
     
  12. JDB-Artist

    JDB-Artist

    Joined:
    Dec 5, 2012
    Posts:
    41
    Thanks @Madgvox , I checked my old project and indeed I was trying to update the texture but didn't save the changes back to disk. I just tried the serialize to disk and reimport workflow ( a bit tedious as you already said) and it works just fine! 2 years and several projects later, glad we solved this one. :)
     
    Madgvox likes this.
  13. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    @Madgvox @JDB-Artist Hey guys, please forgive me profusely for resurrecting this thread from a year ago, but I'm having the exact same issue you guys are describing. In edit mode I'm creating dynamic textures by doing screen captures and then saving them as texture assets. Then I'm 'attempting' to reload them and set them to a Texture2D field in a ScriptableObject.

    Creating the texture asset works, but everything I've tried of reloading the texture back in doesn't. It 'appears' to work, the Icon field seems updating and clicking it even pings the texture asset. Which makes it all the more confusing when I re-open Unity later and the reference is gone.

    Would either of you mind just providing the most simple example of "The whole create texture -> serialize to disk -> import -> modify import settings to match original texture -> re-import -> load texture workflow"?
    I'd forever been in your debt. I have spent so much time trying to figure this out and I don't know where else to look. All I want to do is:
    1) Create a texture in edit mode and save as texture asset
    2) Reload the saved texture and assign it to a field
    3) Have that field retain reference to newly created texture upon Unity reload

    This thread has been the only place I can find any reference to this issue and I don't know where else to turn! Any assistance is appreciated more than you could know! I am not proud enough to admit I have spent so many hours on this and just feel so dumb I've spent this much time and couldn't figure out what you'd think wouldn't be so hard.

    Thanks!
    Will
     
  14. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,319
    @lorewap3 I'm not in the position to provide an example to you at the moment, but I wanted to create a post to let you know I will get back to you on this soon. This is a standard Unity pattern for working with textures and deserves to have an online reference!

    Just from your post, it sounds like you're creating the asset ok, but not properly dirtying the asset you're setting the texture reference on. In order to keep everything in sync, you also need to make sure the asset you're setting the reference on also gets serialized to disk, using
    EditorUtility.SetDirty
    or
    Undo.RecordObject
    .
     
  15. Serge_Billault

    Serge_Billault

    Joined:
    Aug 26, 2014
    Posts:
    190
    I made you an exemple

    Link to sample project: https://drive.google.com/file/d/13rd3eQ2fgm7dtsJXzuDOcML84VQnCv0K/view?usp=sharing


    screenshot_0.jpg

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. //********************************************************************************
    7. //
    8. //********************************************************************************
    9.  
    10. public class Materials : MonoBehaviour
    11. {
    12.     //****************************************************************************
    13.     //
    14.     //****************************************************************************
    15.  
    16.     [ SerializeField ] public Material[] m_materials = new Material[ 0 ];
    17. }
    18.  
    Code (CSharp):
    1.  
    2. using System;
    3. using System.IO;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using UnityEngine;
    7.  
    8. #if UNITY_EDITOR
    9.  
    10. using UnityEditor;
    11. using UnityEditor.SceneManagement;
    12.  
    13. //********************************************************************************
    14. //
    15. //********************************************************************************
    16.  
    17. public class CreateSomeMatsAndTextures
    18. {
    19.     //****************************************************************************
    20.     //
    21.     //****************************************************************************
    22.  
    23.     private const string DEFAULT_DIRECTORY = "Assets/MatsAndTextures";
    24.                                            
    25.     private const string DEFAULT_SHADER    = "Standard";
    26.  
    27.     //****************************************************************************
    28.     //
    29.     //****************************************************************************
    30.  
    31.     static private Texture2D CreateSomeTexture( int w, int h, Color[] pix )
    32.     {
    33.         Texture2D tex = new Texture2D( w, h, TextureFormat.RGBA32, false, true );
    34.  
    35.         tex.SetPixels( pix );
    36.  
    37.         return tex;
    38.     }
    39.  
    40.     //****************************************************************************
    41.     //
    42.     //****************************************************************************
    43.  
    44.     static private Material CreateSomeMaterial( Shader shd, Texture tex )
    45.     {
    46.         Material mat = new Material( shd );
    47.  
    48.         mat.mainTexture = tex;
    49.  
    50.         return mat;
    51.     }
    52.  
    53.     //****************************************************************************
    54.     //
    55.     //****************************************************************************
    56.  
    57.     static private Tuple< Material[], Texture2D[] > CreateMatsAndTextures( int nb )
    58.     {
    59.         if( nb <= 0 ) return null;
    60.  
    61.         Material[]  mats = new Material [ nb ];
    62.  
    63.         Texture2D[] texs = new Texture2D[ nb ];
    64.  
    65.         Shader      shdr = Shader.Find( DEFAULT_SHADER );
    66.  
    67.  
    68.         int       w   = 64;
    69.                      
    70.         int       h   = 64;
    71.  
    72.         Color[]   pix = new Color[ w * h ];
    73.  
    74.         for( int x = 0; x < w; ++x )
    75.         {
    76.             for( int y = 0; y < h; ++y )
    77.             {
    78.                 pix[ x + ( y * w ) ] = Color.HSVToRGB( ( float )y / ( float )h, ( float )x / ( float )w, 1.0f );
    79.             }
    80.         }
    81.  
    82.  
    83.         try
    84.         {
    85.             AssetDatabase.DisallowAutoRefresh();
    86.        
    87.             for( int t = 0; t < nb; ++t )
    88.             {
    89.                 Texture2D tex_tmp  = CreateSomeTexture( w, h, pix );
    90.  
    91.                 string    tex_path = AssetDatabase.GenerateUniqueAssetPath( DEFAULT_DIRECTORY + "/SomeTexture.jpg" );
    92.  
    93.                 File.WriteAllBytes   ( tex_path, tex_tmp.EncodeToJPG() );
    94.  
    95.                 AssetDatabase.Refresh();
    96.  
    97.                
    98.                
    99.                 Texture2D tex = AssetDatabase.LoadAssetAtPath< Texture2D >( tex_path );
    100.  
    101.                 Material  mat = CreateSomeMaterial( shdr, tex );
    102.  
    103.                 AssetDatabase.CreateAsset( mat, AssetDatabase.GenerateUniqueAssetPath( DEFAULT_DIRECTORY + "/SomeMaterial.mat" ) );
    104.  
    105.                 EditorUtility.SetDirty   ( tex );
    106.  
    107.                 EditorUtility.SetDirty   ( mat );
    108.  
    109.                 texs[ t ] = tex;
    110.  
    111.                 mats[ t ] = mat;
    112.             }
    113.         }
    114.  
    115.         finally
    116.         {
    117.             AssetDatabase.SaveAssets();
    118.  
    119.             AssetDatabase.Refresh   ();
    120.  
    121.             AssetDatabase.AllowAutoRefresh();
    122.         }
    123.  
    124.         return new Tuple< Material[], Texture2D[] >( mats, texs );
    125.     }
    126.  
    127.     //****************************************************************************
    128.     //
    129.     //****************************************************************************
    130.  
    131.     static private void CreateObjectMatsAndTextures( GameObject obj, int nb )
    132.     {
    133.         Tuple< Material[], Texture2D[] > resources = CreateMatsAndTextures( 4 );
    134.  
    135.         if( resources != null )
    136.         {
    137.             Materials mats_comp = ( obj != null ) ? obj.GetComponent< Materials >() : null;
    138.  
    139.             if( mats_comp != null )
    140.             {
    141.                 mats_comp.m_materials = resources.Item1;
    142.  
    143.                 EditorSceneManager.MarkSceneDirty( obj.scene );
    144.  
    145.                 EditorSceneManager.SaveOpenScenes();
    146.             }
    147.         }
    148.     }
    149.  
    150.     //****************************************************************************
    151.     //
    152.     //****************************************************************************
    153.  
    154.     [ MenuItem( "GameObject/CREATE LOREWAP3 TEXTURES", false, 0 ) ]
    155.  
    156.     [ MenuItem( "Assets/CREATE LOREWAP3 TEXTURES",     false, 0 ) ] static private void Generate( MenuCommand cmd )
    157.     {
    158.         GameObject obj = ( ( cmd.context != null ) && ( cmd.context is GameObject ) )? cmd.context as GameObject : Selection.activeGameObject;
    159.  
    160.         CreateObjectMatsAndTextures( obj, 4 );
    161.     }
    162. }
    163.  
    164. #endif
    165.  
     
    Last edited: Dec 29, 2021
    Madgvox likes this.
  16. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Thank you @Serge_Billault ! This is exactly what I was looking for!

    I had been using EditorUtility.SetDirty on the texture, but no idea if the ordering was correct. And I didn't even know about AssetDatabase.GenerateUniqueAssetPath or Disallow/Allow AutoRefresh. This makes alot of sense!

    I've been using AssetDatabase.Refresh(AssetImportOptions.ForceUpdate / ForceSynchronousImport) to try and force the refresh but using DisallowAutoRefresh certainly makes more sense. I've read that AssetDatabase.Refresh() is asynchronous. I'm guessing the DisallowAutoRefresh also causes Refresh to be synchronous? I kept having issues with the texture not necessarily being available by the time that next line happens to load it back in. I'd been using weird coroutines that waited a few seconds to see if it had been imported yet. Really hokey...

    Thanks to you too @Madgvox for the quick reply! It's always refreshing when people are happy to help even on a super old thread.

    Thanks again guys this helps me so much!
    Will
     
    lacas8282 likes this.
  17. lacas8282

    lacas8282

    Joined:
    Apr 25, 2015
    Posts:
    139
    I have to same issue, but set mainTexture is working like a charm.

    If I use:

    Code (CSharp):
    1. if (propertyName.Equals("_MainTex"))
    2.     foundMaterial.mainTexture = texture; //working code
    3. else
    4.     foundMaterial.SetTexture(propertyName, texture); //??? not working, why?? when propertyName is
    5. //_BaseMap or _NormalTex or _BumpMap or _OcclusionMap or _MainTex
    As you see the

    material.SetTexture("_MainTex") not working only the
    material.mainTexture=texture;

    I want to update the normal texture slots and other texture slots, not only the mainTex slot.

    Thanks