Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Persistent AnimationCurve inside ShaderGUI

Discussion in 'Shaders' started by liqid, Feb 15, 2022.

  1. liqid

    liqid

    Joined:
    Oct 4, 2016
    Posts:
    17
    Hello,

    I got an AnimationCurve inside my ShaderGui and then I bake the values of the curve into a texture and send it to my shader. That works great.
    What doesn't work is having the AnimationCurve persist when I close the material window and open it up again.
    How would I be able to "save" the curve in the ShaderGUI so that next time I open up the material with the shader assigned to it, I see the same curve I set last time?
     
  2. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,495
    Whatever value type you're sending the animation curve data as into the shader, you need to make those variables as properties in the Properties shaderlab section at the top of your shader file, not just in the CG/HLSLPROGRAM section.

    If it is, then I'd ask how you're going about setting those values in the ShaderGUI code.
    Are you making sure to get a reference to the property by using
    var prop = ShaderGUI.FindProperty("PropertyName", onGUIInputProperties);
    and then doing
    prop.floatValue = curveValue;
    as an example?
    If you're simply calling like
    material.SetFloat("_Property", value)
    then that change is not going to get recorded into the material asset, only in the GPU side.
     
  3. liqid

    liqid

    Joined:
    Oct 4, 2016
    Posts:
    17

    The thing is that I'm using the AnimationCurve to generate a texture! And then I pass that texture to the material and shader. I have no problems in displaying the generated texture in my shader. Also, the texture persists in the material.

    My problem is that the texture is dependent on the AnimationCurve, as I'm using the data from it to generate it, and if I reopen the material my AnimationCurve gets re-instantiated because I can't just pass that to the material to persist it.
    So then I got a new AnimationCurve and the old baked texture from the previous AnimationCurve.
    I'm trying to have them in sync. How would I persist that AnimationCurve inside the material?

    Basically its: Make changes to AnimationCurve -> BakeToTexture() -> Texture send to material/shader and that Texture persists.
    Next time I open the material: AnimationCurve is re-instantiated and I do have the result from BakeToTexture() in form of a texture in my material but I do not have the AnimationCurve that was the origin of this texture.
     
  4. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,495
    Material only serializes texture references and basic value types. So you either have to create properties to deconstruct the animation curve values into floats to store into, or not sure what else you could do other than generating a ScriptableObject asset that has the animation curves and your code would access a material through this SO and the SO would apply the curve to the material.

    With the first option, another problem you'll face is when using this in a build. Editor code doesn't exist in the Build, so your ShaderGUI functions that generate the texture for your shader aren't even going to run.

    It might be better to deconstruct how an animation curve works and calculate the curve inside your shader using the float values it utilizes. So you can still have the editor GUI but no runtime texture generation needed.

    If you are saving the texture for use in build and won't be changing the curve at runtime, then another option might be to just serialize the animation curve to an asset at the same path/name as the material, which your ShaderGUI can then load when the GUI starts displaying again.
     
    liqid likes this.
  5. liqid

    liqid

    Joined:
    Oct 4, 2016
    Posts:
    17

    So, for the build, what I would do is save the texture that I got, as I don't require runtime changes on that curve.
    Thanks so much for mentioning that! I probably would have forgotten to save that and would have wasted lots of debugging time. :)

    I will definitely go for your last option! Do you have any advice on serializing the Unity AnimationCurve object and then loading it when the GUI starts?
     
  6. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,495
    Simplest solution would probably be just letting Unity's serialization take care of it by creating a ScriptableObject class that holds the animation curve. So your material can read and write to that SO and no real extra work needing to be done other than in
    OnClosed(Material mat)
    you need to call
    EditorUtility.SetDirty(thatScriptableObject)
    and
    AssetDatabase.SaveAssets()
    after, to push the SO's changes to disk.

    Also, I would probably put the SO into an
    Editor
    subfolder of the location to make sure it doesn't get included in build.
     
    liqid likes this.
  7. liqid

    liqid

    Joined:
    Oct 4, 2016
    Posts:
    17

    Just finished implementing this. Thanks a ton! Everything works but the final saving on closing of the editor doesn't work.

    Code (CSharp):
    1.         public override void OnClosed(Material material)
    2.         {
    3.             base.OnClosed(material);
    4.             EditorUtility.SetDirty(curveSO);
    5.             AssetDatabase.SaveAssets();
    6.         }

    If I add
    EditorUtility.SetDirty(curveSO);
    and
    AssetDatabase.SaveAssets();
    just somewhere in the ShaderGUI code so that it gets called every time I open the UI it works... but if I only put it in the OnClosed it never gets executed. It seems like OnClosed never gets called somehow
     
    Last edited: Feb 16, 2022
  8. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,495
    Well that's a bit annoying. Then probably best to just do it at the end of OnGUI if it's not causing any editor performance issues for you.

    Could also try OnValidate instead of OnClosed, which for some reason isn't documented but is actually called, I assume whenever a Material inspector value changes, similar to the MonoBehaviour OnValidate.
     
    liqid likes this.
  9. liqid

    liqid

    Joined:
    Oct 4, 2016
    Posts:
    17
    I get for all methods below the error "no suitable method found to override, although
    ValidateMaterial(Material material)
    is even mentioned in the documentation

    Code (CSharp):
    1.         public override void ValidateMaterial(Material material)
    2.         {
    3.         }
    4.  
    5.         public override void OnValidate(Material material)
    6.         {
    7.         }
    8.  
    9.         public override void OnValidate()
    10.         {
    11.         }
     
  10. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,495
    What specific version of Unity are you on? As it appears ValidateMaterial was added in 2021.1
     
  11. liqid

    liqid

    Joined:
    Oct 4, 2016
    Posts:
    17
    Oh, I see, I'm on 2019.4.32f1. I just went ahead and added a button to explicitly bake the texture and save the curve in case the user needs it for a build or wants to continue using that curve in the future. Would be nice to always do that without user input but sadly doing it all the time when the curve is changed via
    EditorGUI.BeginChangeCheck();
    and
    EditorGUI.EndChangeCheck();
    in
    OnGUI(...)
    causes too much editor performance issues. Also,
    OnValidate(...)
    doesn't seem to exist and
    OnClosed(...) 
    never gets executed.
     
  12. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,495
    You could yourself check if the curve values actually changed between OnGUI calls by comparing the editor one to the one stored in the SO. If it did then replace it and do those expensive calls.