I’m passing in various properties to my shader. If I don’t declare those properties in a Properties block at the top of the shader, Unity likes to reset them to their default values every time I tab away from the Unity editor and back (this is in Mac OSX by the way). My objects then all disappear because they can’t render without valid property values.
Declaring my properties in a Properties block fixes the problem and then Unity knows to restore the values they had before it does whatever internal reset it’s doing, but I can’t find a way to declare an array property set with SetFloatArray(), SetVectorArray(), etc. So when I tab away from the editor and back, my array gets reset to 0 and my objects all disappear.
When this happens, Unity doesn’t call Update() or OnValidated() on my MonoBehaviour. Is there any way to either declare the array property so Unity knows how to restore its value, or detect this reset in code and do a rewrite of all my shader properties manually?
Edit - if it helps, this frequently also happens when I save the scene.
Edit 2 - The more I mess with it, the more I think I need a way to just rewrite all the values whenever Unity resets the Material.
Good news, I think I’ve found a workaround! Unity doesn’t call Update() or OnValidate(), but it does call OnWillRenderObject() before it redraws in the scene view. So the fix is to NOT declare any of my dynamic properties in the shader, and do something like this:
void OnWillRenderObject() {
#if UNITY_EDITOR
if (!material.HasProperty("_SomeProperty"))
ApplyShaderPropertiesToMaterial(material);
#endif
}
Where material is my dynamically created material. Of course this is a hacky workaround, so I’d still be interested in knowing why this is happening or how to properly fix it.
I’m setting them in code. But each of my objects has its own Material instance created by a custom inspector for the object. I’m not using a custom Material inspector, if that’s what you mean. Properties get set on the MonoBehaviour and then I send them to the shader in Update().
Working as intended, btw. Declaring values in the properties block is how Unity knows it shouldn’t discard them. You could define the properties and use the [HideInInspector] material property drawer to prevent from from showing, but still get saved. However these will get saved to the asset itself if you’re applying them directly to the material asset or using .sharedMaterial(s), but if you use .material(s) it’ll create a material instance when in play mode which won’t get saved, but will also get destroyed when you leave play mode. However defining them in the properties block will ensure they have useful defaults.
If the values aren’t in the property block Unity assumes they’re values that’ll be set every frame. So setting them OnWillRender is a perfectly valid way of handling it. Alternatively you can try using MaterialPropertyBlock assigned on the renderer component. If you are setting the value every frame it’s cheaper to use that, or Shader.SetGlobal_() if they’re values all shaders can share.
Okay, thanks for all the info! That all makes sense, except that I can’t find any way to add arrays in a shader properties block. So in my case it sounds like OnWillRender() is the way to go, and I’m glad to hear it’s not as hacky as I thought. I am just setting the properties whenever they need to be changed rather than every frame, but I do need to be able to restore them every time after Unity decides to clear them.
For what it’s worth, I did try using MaterialPropertyBlock but that has its own pitfalls when dealing with arrays and it didn’t end up working for me unfortunately.
It’s a little odd that they added texture arrays as first class citizens of materials and shaders with 5.4 (type 2DArray in the properties block), but didn’t quite flesh out float, vector, and matrix arrays. They have Set_Array() but no Get_Array() and they don’t appear to have FloatArray, VectorArray, MatrixArray property types.
As for any gotchas you might of had with MaterialPropertyBlocks, I’m curious what those were since the same limitations to arrays should apply to both.
My case might be a little weird. Problem one was I’m sharing some code between GUI and non-GUI, and MaterialPropertyBlock doesn’t work with Unity GUI. So it was making my code more complicated to support both ways of doing it.
Problem two was that I’m supporting back to Unity 5.3.3 (this is for an Asset Store asset), and back then you had to pass arrays to shaders with a little hack where you could set a property for _Array0, _Array1, _Array2, etc and it would just work. Unfortunately that hack does not work with MaterialPropertyBlock, so I couldn’t find any way of passing an array to my shader that would work.
The lack of a GetVectorArray and a way to declare the array as a property is pretty annoying, because it means you can’t rely on the material to store the data for you if you’re writing a custom editor - you have to store that array somewhere else, such as on a component, where you can actually read the data and then apply it with SetVectorArray. But since you can’t put a component on a material or, say, require and object on the material to get the data from, there’s nothing preventing someone from using the material and not adding that component.
Sorry to resurrect this very old thread, but I’m running into a similar issue.
I’ve got a few arrays that I’m updating via C# script (light positions / colors), but when I focus away from Unity the values are lost. This also happens outside of play mode… which makes it tough to iterate on while building my scene.
Is there a callback that gets triggered outside of play mode that I could use to update my shader data?
My other solution was to replace the arrays with a texture… but I haven’t had much success packing my position data into the texture and then getting it back out in my shader.
John
EDIT: Ah! I can just use [ExecuteInEditMode]! Sorry about that…
Thought it would be great if these arrays could be serialized!
Necroing this thread, but if it is indeed working as intended, this is a very odd design choice because:
Not all shader properties are supported in the property block (matrices, arrays, etc.)
In play mode, those shader values not in properties never get cleared, leading to inconsistent behaviour
Passing shader properties through script has an efficiency cost, so forcing it every frame is a performance hit
I really wish the Unity editor didn’t decide to discard my shader properties set through script. Ultimately, it leads to a bunch of hacks and workarounds to make edit mode behave like play mode in the source.