Search Unity

Question Material Property Block's GetTexture returns null even when I know the material has a texture

Discussion in 'Shaders' started by skullthug, Jan 18, 2023.

  1. skullthug

    skullthug

    Joined:
    Oct 16, 2011
    Posts:
    202
    Hello, trying to do a thing where I swap out the texture on a material instance at runtime at the start of a routine sequence, and then swap it back to what it started with with the routine is finished.

    The material has a texture assigned to it through the inspector, I can guarantee it is there upon launch & runtime, however when I attempt to GetTexture on it, it returns null.
    Code (CSharp):
    1.  
    2. Texture originalTexture;
    3. originalTexture = mPropertyBlock.GetTexture("_BlueNoise");
    4. mPropertyBlock.SetTexture("_BlueNoise", newTexture);
    5. //later...
    6. mPropertyBlock.SetTexture("_BlueNoise", originalTexture);
    This code will error out with a ArgumentNullException: Value cannot be null.

    I've verified the texture string is correct, and it does successfully do the SetTexture part at least.

    Alright so what weird tech mumbo jumbo is preventing me from being able to cache what texture it starts off assigned with here??
     
  2. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,429
    It's been a while since I checked this myself so I may be off-base, but is your texture flagged as Read/Write(*)? Things that aren't marked as Read/Write are often sent off to the graphics card and then set null to avoid hogging CPU memory. I know this is true for mesh data, but maybe for texture data also.

    (*) candidate for Dumbest Name Award; if false, it means you cannot even read it

    Actually, I think Material Property Blocks start out as basically empty Lists, and only contain stuff you've Set on them yourself. Getting the MPB from the Material for the first time, there's nothing. If there's nothing then nothing has overridden it, and you can get the same property directly from the Material and set that in your MPB so it's populated for later.
     
    Last edited: Jan 18, 2023
    skullthug likes this.
  3. skullthug

    skullthug

    Joined:
    Oct 16, 2011
    Posts:
    202
    Ah, that's an extremely good guess! I was almost certain that would do it, however setting the originalTexture's asset to be Read/Write enabled didn't seem to change anything unfortunately :(
     
  4. skullthug

    skullthug

    Joined:
    Oct 16, 2011
    Posts:
    202
    Huh! Alright, that would explain what is happening then. I've never 100% understood what a Material Property Block contained when created, so I guess I always assumed it was starting with a copy/instance of the original material.
     
  5. skullthug

    skullthug

    Joined:
    Oct 16, 2011
    Posts:
    202
    Ah, ok. Just referencing the original texture via renderer.material.GetTexture() seems to work for me, and I think verifies that yeah material property blocks start off pretty empty.

    Thanks for your help @halley you saved me a ton of grief!
     
    halley likes this.
  6. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,429
    I would suggest you fetch renderer.sharedMaterial instead of .material, just to avoid undoing all that work the MPB is trying to do.
     
  7. skullthug

    skullthug

    Joined:
    Oct 16, 2011
    Posts:
    202
    Ah yes. Good call. sharedMaterial is specifically that runtime instance of the material if I recall right?

    Since I'm only retrieving data from renderer.material in this situation and not setting it, I skirt by fine. But you're right sharedMaterial is probably better form.
     
  8. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,429
    Let's ignore MPB a moment.

    When you enter Play mode, on Start, the Inspector field you see labeled "Material" for an object is put into .sharedMaterial and also .material. ALL instances of the same original pre-Start material are shared, saving tons of memory. If you did renderer.sharedMaterial.color = red, ALL uses of that material go red. If you did renderer.material.color = red, Unity would fork this renderer's material reference, stop using the shared material, clone the original material, and make the change to the clone. Only this renderer has the new forked clone with its changes, all other references to the original sharedMaterial are unaffected.

    It's easy to accidentally make invisible changes when you start layering a bunch of different systems into your game, but those cloned/forked material copies start multiplying, taking up memory and ruining batching (important for the legacy render pipeline).

    Material Property Blocks were intended as an alternative to letting Material copies fork like bunnies and potentially leaking memory when you destroy renderers or unload scenes, while still giving you per-instance control of the material properties. That said, if you use MPB and accidentally modify renderer.material also, you have played yourself and get the worst management drawbacks of both systems.
     
  9. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,546
    To clarify, that only avoids the backing native collection of the texture pixel data being on the CPU, but the Texture container that wraps references to it and other useful information is still kept in CPU memory since it still has to keep up with passing around references and eventually telling the GPU to flush the texture when done with it.
     
    halley likes this.