Search Unity

Question What happens when I set a material's color/texture values at runtime?

Discussion in 'General Graphics' started by TzuriTeshuba, Nov 8, 2022.

  1. TzuriTeshuba

    TzuriTeshuba

    Joined:
    Aug 6, 2019
    Posts:
    185
    So this is something that has intrigued me for a while and havent found much info on. At runtime I like to change a material's base map and emissive map color for good feedback and communication to the player. I also swap out textures on a material for some variability. I have always been hesitant to do this (especially to perform the set operation frequently). Of course I test and do not find any performance issues, but I wonder if setting a materials base map color / emissive color / emissive map "simply" sets a value like any other setter, or does some complicated communication with the GPU about a change to the material.

    Just to clarify, I am speaking about setting values to the Material directly (i.e. someMaterial.Set(...)), and NOT through someMeshRenderer.material.Set(...) (i noticed most info I found was regarding that case)

    Thanks in advance, all tips are appreciated.
     
  2. georgerh

    georgerh

    Joined:
    Feb 28, 2020
    Posts:
    72
    Not much happens if you change the values on the source material, except
    - all instances which use the material will see the change
    - the changes will persist between runs
    - once you save the project or shut down Unity, the material changes will be saved
    - one way to avoid this nuisance is to make a copy of the material if you run it in the editor (but not in builds)

    If you use someMeshRenderer.material, however, you'll get a copy of the material
    - .material makes a copy which you have to destroy manually! (unlike .sharedMaterial)
    - will only affect one instance
    - might break batching depending on the render pipeline that you are using (SRP batcher or not)

    Also, instead of writing mat.SetTexture("_MainTex", newTexture) cache the shader property id for better performance:

    private static readonly int _mainTexId = Shader.PropertyToID("_MainTex");
    ....
    mat.SetTexture(_mainTexId, newTexture);
     
    Last edited: Nov 8, 2022
    TzuriTeshuba likes this.
  3. TzuriTeshuba

    TzuriTeshuba

    Joined:
    Aug 6, 2019
    Posts:
    185
    Much Appreciated! couple follow up questions

    So setting a value is not a heavy operation? i shouldnt be wary/paranoid of setting it every frame on a couple materials for example?

    Great tip! thank you!
     
    Last edited: Nov 9, 2022
  4. georgerh

    georgerh

    Joined:
    Feb 28, 2020
    Posts:
    72
    It's not a heavy operation (unless I misunderstood you).

    You shouldn't take my word for it, though. Always profile your code! Profile both the function that makes the material changes as well as the overall frame time. Also make sure it's not allocating any memory. Unity's profiler is quite good, there is no excuse not to use it. Also look at the frame stats to make sure that you are not breaking batching.
     
    TzuriTeshuba likes this.
  5. TzuriTeshuba

    TzuriTeshuba

    Joined:
    Aug 6, 2019
    Posts:
    185
    True true and true, no replacement for profiling. Havent found anything concerning yet in the profiler, but ill report back here if i find something for you and future readers. I appreciate the help! I was overprofiling a bit cause of unfounded superstition i think and just hearing it from another dev put me more at ease lol.
    Cheers, and thanks again mate
     
    georgerh likes this.
  6. georgerh

    georgerh

    Joined:
    Feb 28, 2020
    Posts:
    72
    I don't know the actual implementation but my understanding is that a material is basically just a Dictionary or SortedDictionary. Textures are classes, which is a reference type. So you are basically just inserting a pointer (64 bits) into a hash map (amortized O(1)) or tree map (O(log N)). If you use pre-cached ids, Unity doesn't have to do the string-to-id conversion either.

    Unity pulls the current texture from the material when the frame is rendered every frame (when the texture is bound to the shader property). The cost is the same, whether it pulls the old or new texture from it.

    Batching is my only concern, but since you are modifying the original material, it shouldn't break batching either.

    Now, I'm assuming you have already loaded the textures. The loading would be slow if not done asynchronously.
     
  7. TzuriTeshuba

    TzuriTeshuba

    Joined:
    Aug 6, 2019
    Posts:
    185
    I never actually "Load Assets" explicitly. It's my first serious game so I imagine soon enough I will be. I have all my textures that I plan to use as Serialized Fields in an "Emissive Textures Getter" class.

    Code (CSharp):
    1. [SerializeField] List<Texture2D> textures;
    I Imagine Unity loads them when the object with the attached script is loaded. I Checked the profiler and the "TextureMemory" (or any other memory value) does not increase when the swap out happens. Also I dont see any related CPU spikes. I would assume that my solution is viable, but simply not scaleable as the number of textures increases?