Search Unity

  1. Looking for a job or to hire someone for a project? Check out the re-opened job forums.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Help Wanted One shader for multiple materials with different textures

Discussion in 'Shader Graph' started by kamoski, May 15, 2019.

  1. kamoski

    kamoski

    Joined:
    Jan 11, 2019
    Posts:
    4
    So i have enemy model that uses 3 materials (each having it own's texture). I created dissolve shader, and applied it in C# code to those materials. My question is:

    Is there a way to apply shader to material with ceratin texture, and then apply the same shader with different texture to different material?

    What i'm trying to do is dissolve a enemy body after he died, but make him keep propper texture while dissolving (now he turns gray after shader is applied).
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    9,842
    This is mostly a c# question rather than directly shader graph related. There’s kind of two ways to go about this.

    One is to try to make sure your dissolve shader’s properties use the same reference names as the shader you’re swapping from. Both the LWRP and HDRP use “_BaseMap” for the main texture for example. Then, to swap, just change the shader on the materials directly. If you use renderer.materials[].shader it’ll make a new material “instance” (really just a copy) that you can modify without affecting the original material asset. Then swap the shader or the whole material back (use .sharedMaterials[] if you go back to the original material, and keep the material “instance” around for later when you want to dissolve again).

    The other option, and the one I personally use, is to create a MaterialPropertyArray, copy the properties of the original material I care about into it, set that on the renderer for that material slot, then replace the .sharedMaterials[] entry with the one dissolve material. That way no new materials are ever created.
     
  3. kamoski

    kamoski

    Joined:
    Jan 11, 2019
    Posts:
    4
    Thank you very much, that solution worked just fine for me :)
     
  4. rashiddevo

    rashiddevo

    Joined:
    Dec 10, 2018
    Posts:
    7
    Hi
    How can I do that in shader graph?
     
  5. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    14
    Hello,

    I found this post while searching for the same type of issue I am having. Would you mind expanding on and explaining the method you prefer with some detail/code? I would really appreciate it. Or perhaps the API documentation/example of this in action?

    Thanks!
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    9,842
    Well, I have a typo in there that's probably confusing you a bit. You'd want to set
    MaterialPropertyBlock
    s, not a "MaterialPropertyArray".
    https://docs.unity3d.com/ScriptReference/MaterialPropertyBlock.html

    The basic code would be something like this:
    Code (csharp):
    1. public Renderer rend;
    2. public Material dissolveMaterial;
    3. private Material[] originalMaterials;
    4. private Material[] dissolveMaterials;
    5. private MaterialPropertyBlock matBlock;
    6.  
    7. void Start()
    8. {
    9.   matBlock = new MaterialPropertyBlock();
    10.   int numMaterials = rend.sharedMaterials.Length;
    11.  
    12.   // make copy of original materials for later
    13.   originalMaterials = rend.sharedMaterials;
    14.  
    15.   // make array of dissolve materials
    16.   dissolveMaterials = new Material[numMaterials];
    17.  
    18.   // iterate over shared materials
    19.   for (int i=0; i<numMaterials; i++)
    20.   {
    21.     matBlock.Clear();
    22.  
    23.     // copy the properties of the material you want
    24.     // these property names are whatever the values that are important to your shaders are
    25.     matBlock.SetTexture("_MainTex", rend.sharedMaterials[i].GetTexture("_MainTex"));
    26.     matBlock.SetColor("_Color", rend.sharedMaterials[i].GetColor("_Color"));
    27.     matBlock.SetFloat("_SomeFloat", rend.sharedMaterials[i].GetColor("_SomeFloat"));
    28.     // etc ...
    29.  
    30.     // set the property block on the renderer for that material index
    31.     rend.SetPropertyBlock(matBlock, i);
    32.  
    33.     // fill in the dissolve materials array
    34.     dissolveMaterials[i] = dissolveMaterial;
    35.   }
    36.  
    37. }
    38.  
    39. void SetDissolved(float dissolve)
    40. {
    41.   // assuming disolve 0.0 is fully visible
    42.   if (dissolve <= 0.0f)
    43.   {
    44.     rend.sharedMaterials = originalMaterials;
    45.     return;
    46.   }
    47.  
    48.   // else swap to dissolve material
    49.   rend.sharedMaterials = dissolveMaterials;
    50.  
    51.   // iterate over property blocks
    52.   int numMaterials = rend.sharedMaterials.Length;
    53.   for (int i=0; i<numMaterials; i++)
    54.   {
    55.     // and set the dissolve amount
    56.     matBlock = rend.GetPropertyBlock(i);
    57.     matBlock.SetFloat("_Dissolve", dissolve);
    58.     rend.SetPropertyBlock(matBlock. i);
    59.   }
    60. }
    }
     
    Last edited: Aug 5, 2020
    M_R_M and ph_ like this.
  7. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    14
    Wow, I wasn't sure if I'd get a response for a while. Thank you for providing the example and the documentation link. I am going to give it a shot tomorrow to get it working in my project and will reply back with the results. :) Really appreciate it!!!
     
  8. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    14
    So here is my code thus far. I have it almost working? I can get it to change the material to my dissolve material, and it will dissolve into the scene but its not grabbing the original materials "_BaseColor" and placing it on the dissolve material. The dissolve material is retaining its _BaseColor value that is set inside the shader.

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class MaterialPropertyDebugger : MonoBehaviour
    7. {
    8.  
    9.     public Renderer rend;
    10.     public Material dissolveMaterial;
    11.     public Material[] originalMaterials;
    12.     public Material[] dissolveMaterials;
    13.     private MaterialPropertyBlock matBlock;
    14.  
    15.     void Start()
    16.     {
    17.         // Create new MPB
    18.         matBlock = new MaterialPropertyBlock();
    19.         int numMaterials = rend.sharedMaterials.Length;
    20.  
    21.         // make a copy of original materials for later use
    22.         originalMaterials = rend.sharedMaterials;
    23.  
    24.         // make array of dissolve materials
    25.         dissolveMaterials = new Material[numMaterials];    
    26.  
    27.         // iterate over shared materials
    28.         for (int i=0; i<numMaterials; i++)
    29.         {
    30.             matBlock.Clear();
    31.  
    32.             // copy the properties of the material we want
    33.             //these are the property values from the shader that we care about
    34.  
    35.             // Grab the shared materials from original material. URP uses "_BaseColor" instead of _"Color"
    36.             matBlock.SetColor("_BaseColor", rend.sharedMaterials[i].GetColor("_BaseColor"));
    37.          
    38.             //matBlock.SetTexture("_Texture", rend.sharedMaterials[i].GetTexture("_Texture"));
    39.  
    40.             // set the property block on the renderer for that material index
    41.             rend.SetPropertyBlock(matBlock, i);
    42.  
    43.             // fill in the dissolve materials array
    44.             dissolveMaterials[i] = dissolveMaterial;
    45.         }
    46.      
    47.      
    48.     }
    49.  
    50.     // Update is called once per frame
    51.     void Update()
    52.     {
    53.         if(Input.GetKeyDown(KeyCode.D))
    54.         {
    55.             // swap to dissolve material
    56.             rend.sharedMaterials = dissolveMaterials;
    57.  
    58.             // iterate over property block(s)
    59.             int numMaterials = rend.sharedMaterials.Length;
    60.             for (int i=0; i<numMaterials; i++)
    61.             {
    62.                 // set the dissolve trigger
    63.                 matBlock = rend.GetPropertyBlock(i);
    64.                 matBlock.SetFloat("Vector1_83EFB224", Time.time);
    65.                 rend.SetPropertyBlock(matBlock, i);
    66.             }
    67.         }
    68.      
    69.     }
    I believe the issue is with line matBlock = rend.GetPropertyBlock(i); I have to leave this commented out in order to have it working as described before. I've tried getting this line of code to work but keeps telling me "cannot convert from 'int' to 'UnityEngine.MaterialPropertyBlock'
    }

    Edit: My DissolveIN shader was made using URP PBR Graph. Not sure if this is also part of the problem? Any further help is appreciated.
     

    Attached Files:

  9. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    14
    Before and After pictures of the inspector if this helps.
     

    Attached Files:

  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    9,842
    Because I wrote it wrong. I just typed all that code out from memory, I didn't try to run it. ;) Replace:
    Code (csharp):
    1. matBlock = rend.GetPropertyBlock(i);
    With:
    Code (csharp):
    1. rend.GetPropertyBlock(matBlock, i);
    https://docs.unity3d.com/ScriptReference/Renderer.GetPropertyBlock.html

    You won't necessarily see the value change in the inspector. But also you don't necessarily have a
    _BaseColor
    on that dissolve shader to set, unless you actually changed the reference name to
    _BaseColor
    and not left it as the usual
    Vector4_R4ND0M5TUFF
    .
     
    adamgffrd likes this.
  11. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    14
    That is what I was doing wrong, I needed to change the "_BaseColor" to
    "Color_18AF198E" It didn't click at first what you meant and then found this page https://answers.unity.com/questions/1509757/shader-graph-edit-parameters-from-script.html and realized I'm dumb cause the property names are in the inspector.


    Thanks for all your help, its working as intended now. :)
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    9,842
    You can also just rename the references to something more sane.
     
    adamgffrd likes this.
  13. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    14
    Yeah thats my next move. Just got the _BumpMap in and working. Going to test on something with more than one material.

    Edit: Everything is working great. Thanks again! @bgolus
     
    Last edited: Aug 8, 2020
  14. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    14
    Having another issue. This code works fine if there is only one material on each renderer. When I add a second material to any of the renderer's, it starts breaking. I noticed that the dissolveMaterials array will remain the total length of whatever the last renderer it iterated through. When I go to run my public void dissolveBuildingIn2() it thinks that all of the dissolvedMaterials are of size 2. I believe this is the part in my function that I need to change rend[r].sharedMaterials= dissolveMaterials;

    I was going to create another array that holds all of the dissolveMaterials inside their own index spot so I could then iterate through it correctly in my function but I am not sure if that is correct and having trouble implementing this. Maybe you can see something that I cannot but I've been debugging and trying everything I can think of, hoping you can take a peak... Sry to keep bugging you.

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class Texture2dDissolveIN : MonoBehaviour
    7. {
    8.     public GameObject objectWeAreDissolvingIN;
    9.     public Renderer[] rend;
    10.     public Material dissolveMaterial;
    11.     public Material[] originalMaterials;
    12.     private Material[] dissolveMaterials;
    13.  
    14.     private MaterialPropertyBlock matBlock;
    15.    
    16.     void Start()
    17.     {
    18.         rend = objectWeAreDissolvingIN.GetComponentsInChildren<Renderer>();
    19.         int numRendChildren = rend.Length;
    20.        
    21.  
    22.         for (int r=0; r<numRendChildren; r++)
    23.         {
    24.         // Create new MPB
    25.         matBlock = new MaterialPropertyBlock();
    26.         int numMaterials = rend[r].sharedMaterials.Length;
    27.         Debug.Log("The number of materials under renderer index- " + r + " is equal to " + numMaterials);
    28.         // make a copy of original materials for later use
    29.         originalMaterials = rend[r].sharedMaterials;
    30.         // make array of dissolve materials
    31.         dissolveMaterials = new Material[numMaterials];
    32.        
    33.  
    34.        
    35.         // iterate over shared materials
    36.             for (int i=0; i<numMaterials; i++)
    37.             {
    38.                 matBlock.Clear();
    39.  
    40.                 if(rend[r].sharedMaterials[i].GetTexture("_BaseMap") != null)
    41.                 {
    42.                     //Debug.Log(rend[r]);
    43.                     // Get and set texture with new dissolve IN shader
    44.                     matBlock.SetTexture("_MainTex", rend[r].sharedMaterials[i].GetTexture("_BaseMap"));
    45.                     matBlock.SetTexture("_NrmMapTex", rend[r].sharedMaterials[i].GetTexture("_BumpMap"));
    46.                     matBlock.SetColor("_BaseColor", rend[r].sharedMaterials[i].GetColor("_BaseColor"));
    47.                 }
    48.                 else
    49.                 {
    50.                     matBlock.SetColor("_BaseColor", rend[r].sharedMaterials[i].GetColor("_BaseColor"));
    51.                 }
    52.                 // set the property block on the renderer for that material index
    53.                 rend[r].SetPropertyBlock(matBlock, i);
    54.                 // fill in the dissolve materials array
    55.                 dissolveMaterials[i] = dissolveMaterial;
    56.                 Debug.Log("The number of materials under renderer index- " + r + " is equal to " + numMaterials);
    57.                
    58.             }
    59.         }
    60.  
    61.     }
    62.     // Update is called once per frame
    63.     public void dissolveBuildingIn2()
    64.     {
    65.             int numRendChildren = rend.Length;
    66.            
    67.             for (int r=0; r<numRendChildren; r++)
    68.             {
    69.                
    70.                 // swap to dissolve material
    71.                 Debug.Log("DissolveBuildingIN2 - dissolve materials array length for renderer index " + r + " is equal to " + dissolveMaterials.Length);
    72.                 int numOfSharedMaterials = rend[r].sharedMaterials.Length;
    73.                 Debug.Log("DissolveBuildingIN2 - sharedMaterials array length for renderer index " + r + " is equal to " + rend[r].sharedMaterials.Length);
    74.                
    75.                 rend[r].sharedMaterials= dissolveMaterials;
    76.                
    77.  
    78.                 // iterate over property block(s)
    79.                 int numMaterials = rend[r].sharedMaterials.Length;
    80.  
    81.                 for (int i=0; i<numMaterials; i++)
    82.                 {
    83.                     // set the dissolve trigger
    84.                     rend[r].GetPropertyBlock(matBlock, i);
    85.                     matBlock.SetFloat("Vector1_83EFB224", Time.time);
    86.                     rend[r].SetPropertyBlock(matBlock, i);
    87.                 }
    88.            
    89.  
    90.        
    91.        
    92.     }
    93.     }
    94.    
    95. }
    96.  
    97.  
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    9,842
    Part of the benefit of this technique is there aren't multiple dissolve materials. At least assuming all objects that are dissolving are all using the same common opaque material settings. The arrays are just lists of the same material over and over.

    However the array assigned to the renderer's
    .sharedMaterials
    needs to match the size of that renderer's mesh's material count. If you assign an array that's smaller, then parts of the mesh won't render at all. If you assign an array that's longer, then some parts will render multiple times! The pseudo code example above was written assuming it was be a unique script component per renderer component, not a single component to drive multiple renderers. If that's the case you'll need to copy off the arrays of every renderer component uniquely, at least if you need the ability to re-assign the original materials.

    If your enemies are simply destroyed and new ones are instantiated, then don't worry about that. Just make a new materials array populated with the dissolveMaterial for each renderer component when you start to dissolve them. Then you don't have to worry about re-assigning the sharedMaterials array every time.
     
    adamgffrd likes this.
unityunity