Search Unity

How to access multiple render materials on single mesh C#

Discussion in 'General Graphics' started by Proto-G, Feb 24, 2021.

  1. Proto-G

    Proto-G

    Joined:
    Nov 22, 2014
    Posts:
    213
    I have a runtime color switcher on a world canvas. I'm trying to access multiple materials on a single mesh. Currently it only is able to modify a single material. I would like to have a public material index where I can drag however many materials in and be able to edit each of them.

    For example: My bed mesh has 3 materials. I can currently only modify a single (unchosen) material. I want to be able to modify all 3 materials on the bed mesh based on the public materials that I have dragged into each public material slot.

    Here is the script that I'm applying to the bed model:
    Code (CSharp):
    1. using UnityEngine;
    2. namespace HSVPicker.Examples
    3. {
    4.     public class ColorPickerTester : MonoBehaviour
    5.     {
    6.  
    7.         public new Renderer renderer;
    8.         public ColorPicker picker;
    9.  
    10.         public Color Color = Color.red;
    11.         public bool SetColorOnStart = false;
    12.  
    13.         // Use this for initialization
    14.         void Start ()
    15.         {
    16.             picker.onValueChanged.AddListener(color =>
    17.             {
    18.                 renderer.material.color = color;
    19.                 Color = color;
    20.             });
    21.  
    22.             renderer.material.color = picker.CurrentColor;
    23.             if(SetColorOnStart)
    24.             {
    25.                 picker.CurrentColor = Color;
    26.             }
    27.         }
    28.  
    29.         // Update is called once per frame
    30.         void Update () {
    31.  
    32.         }
    33.     }
    34. }
    I know it's something to do with render.materials
    Thanks!
     
    Last edited: Feb 24, 2021
  2. judah4

    judah4

    Joined:
    Feb 6, 2011
    Posts:
    256
    You will want to reference the materials array like this.
    Code (CSharp):
    1. // Use this for initialization
    2.         void Start ()
    3.         {
    4.             picker.onValueChanged.AddListener(color =>
    5.             {
    6.                 for(int cnt = 0; cnt < renderer.materials.Length; cnt++)
    7.                 {
    8.                     renderer.materials[cnt].color = color;
    9.                 }
    10.                
    11.                 Color = color;
    12.             });
    13.  
    14.             //setting initial color
    15.             for(int cnt = 0; cnt < renderer.materials.Length; cnt++)
    16.             {
    17.                 renderer.materials[cnt].color = picker.CurrentColor;
    18.             }
    19.            
    20.            
    21.             if(SetColorOnStart)
    22.             {
    23.                 picker.CurrentColor = Color;
    24.             }
    25.         }
     
    Proto-G likes this.
  3. npatch

    npatch

    Joined:
    Jun 26, 2015
    Posts:
    247
    If those colors are per-instance, I'd use MaterialPropertyBlocks. .color would probably create material instances, which means different SetPasses when it's ultimately just one material with one property that changes per instance.
    Read this.
     
    Proto-G and judah4 like this.
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    That article is great for showing how to use material property blocks.


    It's also completely misunderstood by almost every person who reads it! It doesn't help that the word "instance" is used to mean completely different things with Unity.

    Using a
    MaterialPropertyBlock
    instead of modifying the material directly means you aren't changing the parent material or creating a copy, aka a "material instance" in Unity parlance, of that parent material. Property blocks are very lightweight as they're a list of only the values that are being changed and no more. Where as the
    Material
    class is a list of values for all of the properties, in addition to a lot more information than just those properties. You also need to keep track of any
    Material
    you create at runtime and manually destroy it, because Unity will not do that for you.

    https://docs.unity3d.com/ScriptReference/Renderer-material.html
    Also note, "material instance" in this case has nothing to do with "instancing". Instancing being the ability for a GPU to render multiples of the same mesh & material as one draw. That article isn't even talking about instancing at all, because it predates Unity adding instancing support by a year! There used to be a few unfortunate misunderstandings in the article that caused additional confusion, but which appear to have since been thankfully fixed.

    As I mentioned above a "material instance" is just what Unity decided to call a material that was automatically created as a copy of a pre-existing material. It's equivalent to calling
    new Materal(myMaterial)
    in c#, because that's exactly what it's doing when you access the
    renderer.material
    or
    renderer.materials[i]
    for the first time. Or anytime you use either (access or assign) after assigning
    .sharedMaterial
    . Functionally it's identical to creating a new material from scratch and copying all of the settings, because again that's exactly what it's doing. Each unique material will always be a separate "draw call", even if they have exactly the same settings and properties, even if it's an "instance".

    Changing the material properties with a property block doesn't change the fact it's a separate "draw call". Assuming the property values are different at least. The performance advantage shown in that link purely comes down to the fact material property blocks are a lot cheaper to deal with on the CPU. A big part of that is because it only changes properties. Material property blocks can't change anything to do with the shader being used, keywords, or render state (which are sometimes controlled by material properties) which removes a ton of work the CPU has to do when evaluating changing property values on a material normally.


    That said, you should absolutely use a
    MaterialPropertyBlock
    to set the colors instead of modifying the
    renderer.material
    directly.

    Code (csharp):
    1. private MaterialPropertyBlock matBlock;
    2.  
    3. // in the function
    4. if (matBlock == null)
    5.   matBlock = new MaterialPropertyBlock(); // just need to make it once and reuse it
    6.  
    7. for (int i=0; i < renderer.sharedMaterials.Length; i++)
    8. {
    9.   matBlock.SetColor("_Color",  picker.CurrentColor);
    10.   renderer.SetPropertyBlock(matBlock, i);
    11. }
    Once you call
    SetPropertyBlock
    you can modify the property block and it won't affect anything you've used it on before. It's just a list of properties that's being passed along.
     
    npatch and Proto-G like this.
  5. Proto-G

    Proto-G

    Joined:
    Nov 22, 2014
    Posts:
    213
    Thank you. I'll give it a try!
     
  6. Proto-G

    Proto-G

    Joined:
    Nov 22, 2014
    Posts:
    213
    I'm a novice at coding and not able to get any of this working. Both provided scripts compile, but I'll have to figure out how to get either one functioning. Thanks for the right direction!