• RuntimesUnity
  • Custom shader does not work with Spine animation

I recorded a video. It seems to reveal the essence of the problem.
I have a weapon (made in Spine) with Weapon_01_Material.

Before launching the game, I disabled SkeletonAnimation because we found out that the SkeletonAnimation component does not allow color changes.
At the start of a game level, the AllinOneShaders component change color of my weapon using the HueShift effect (I do it using code).
But when I enable the SkeletonAnimation component during gameplay, it returns the original values for Weapon_01_Material.
How can I prevent SkeletonAnimation from changing the material?

Related Discussions
...

@VOXELIUM Thanks for uploading the video. In your video you can see that when you're entering play mode and before enabling the SkeletonAnimation component, the assigned material is Weapon_01_Material (Instance). So any of your components seems to have modified the material and thus created a material instance. After the material was modified and a material instance (clone) is assigned, when the SkeletonAnimation component is activated, it assigns materials based on active attachments, which resets the material to the shared original material in your assets folder.

Please see the documentation below, especially the "Note" section reading:
"Note: Direct modifications to the Materials array have no effect "
https://esotericsoftware.com/spine-unity#Materials
https://esotericsoftware.com/spine-unity#Changing-Materials-Per-Instance

VOXELIUM I tried using SkeletonRendererCustomMaterials
It didn't help

Then you likely did something wrong.

Most likely you are modifying the material via code and thus creating the Material instance. If you don't want to prepare material assets and assign them, but instead want to change properties via code at runtime, you should be using MaterialPropertyBlocks. Again, see the documentation on how to do that:
https://esotericsoftware.com/spine-unity#Changing-Materials-Per-Instance

    Harald

    Thank you.
    I looked at the information. There are only comments written there. They are probably understandable to an experienced Spine developer. I didn’t find any code examples there on how I can implement SkeletonAnimation.OnMeshAndMaterialsUpdated into my code and then update the material.
    Can you give me some advice?

    Harald

    `using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Spine.Unity;
    using System;
    using Spine;

    public class WeaponChangeDesign : MonoBehaviour
    {
    [SerializeField] private Weapon weapon;
    private Material matItem;
    private string aOneSpriteFX1 = "_HsvShift";

    private SkeletonAnimation skeletonAnimation;
    private Skeleton skeleton;
    
    void Awake()
    {
        skeletonAnimation = GetComponent<SkeletonAnimation>();
    }
    
    void OnEnable()
    {
        skeletonAnimation.OnMeshAndMaterialsUpdated += ChangeColor;
    }
    
    void Start()
    {
        matItem = GetComponent<MeshRenderer>().material;
    }
    
    private void ChangeColor(SkeletonRenderer skeletonRenderer)
    {
        float hsv;
        int levelForChange = weapon.weaponLevel % 6;
    
        Debug.Log("Change color process " + levelForChange);
    
        switch (levelForChange)
        {
            case 1:
                hsv = 0f;
                break;
            case 2:
                hsv = 60f;
                break;
            case 3:
                hsv = 120f;
                break;
            case 4:
                hsv = 180f;
                break;
            case 5:
                hsv = 240f;
                break;
            case 0:
                hsv = 300f;
                break;
    
            default:
                hsv = 0;
                break;
        }
    
        matItem.SetFloat(aOneSpriteFX1, hsv);
    }

    }
    `

    Ok
    I wrote code to call the color change function after OnMeshAndMaterialsUpdated. I see that the ChangeColor function is called every frame, but the color still does not change if Skeleton Animation is enabled

    @VOXELIUM Did you perhaps stop reading after the first paragraph, thinking that the sections is over already? Please note that the section "CustomMaterialOverride and CustomSlotMaterials" and subsequent sub-sections are part of this larger section.

    In my above message I recommended that you use MaterialPropertyBlocks since you mentioned you are setting properties via code. There is a complete subsection with code examples and with a reference to an example scene demonstrating its use that comes with the spine-unity runtime.

    Regarding SkeletonAnimation.OnMeshAndMaterialsUpdated: You can click the method text, it then takes you to the Life Cycle section listing all callback delegates that you can hook your methods up to. I just noticed that the OnMeshAndMaterialsUpdated delegate is not explicitly demonstrated in the code below, and has a slightly different signature than UpdateComplete:

    // your delegate method
    void AfterMeshAndMaterialsUpdated (SkeletonRenderer renderer) {
        // this is called after mesh and materials have been updated.
    }
    
    // register your delegate method
    void Start() {
       skeletonAnimation.OnMeshAndMaterialsUpdated -= AfterMeshAndMaterialsUpdated;
       skeletonAnimation.OnMeshAndMaterialsUpdated += AfterMeshAndMaterialsUpdated;
    }

    I will add this to the documentation, thanks for bringing this to our attention.

    In general it is highly recommended to get familiar with programming basics in C#, like how to use delegates. Also please be sure to understand how Unity Materials behave in general:
    https://forum.unity.com/threads/confused-about-material-instances.402037/#post-2621170
    If you skip the fundamentals, you will run into a lot of frustration down the road.

      Harald

      I don't understand your complaint when I ask you for help.

      Harald I recommended that you use MaterialPropertyBlocks since you mentioned you are setting properties via code.
      There is no description of the solution to my problem. There it is offered.
      That's what it says in the instructions.
      `MaterialPropertyBlock mpb = new MaterialPropertyBlock();
      mpb.SetColor("FillColor", Color.red); // "FillColor" is a named property on the used shader.
      mpb.SetFloat("FillPhase", 1.0f); // "FillPhase" is another named property on the used shader.
      GetComponent<MeshRenderer>().SetPropertyBlock(mpb);

      // to deactivate the override again:
      MaterialPropertyBlock mpb = this.cachedMaterialPropertyBlock; // assuming you had cached the MaterialPropertyBlock
      mpb.Clear();
      GetComponent<Renderer>().SetPropertyBlock(mpb);`

      I do not need it. I need to trigger the color change using the AllinOneShaders component. I want to apply a material to an object that is changed using the AllinOneShaders component.

      Harald skeletonAnimation.OnMeshAndMaterialsUpdated += AfterMeshAndMaterialsUpdated;
      The code you suggested is not much different from the one I wrote. My code works exactly the same as yours.
      Your code also doesn't solve the problem in this thread, so it's pretty inappropriate of you to talk about the need to learn the basics of Unity.
      I'm very disappointed that you can't offer a solution to your product. At the same time, I don’t have to know it - I’m a buyer, and you could understand how to solve the current problem, because you are a Spine developer.
      Sorry for taking up your time.

      p.s

      Harald Also please be sure to understand how Unity Materials behave in general:
      https://forum.unity.com/threads/confused-about-material-instances.402037/#post-2621170
      If you skip the fundamentals, you will run into a lot of frustration down the road.

      This makes no sense at all in this conversation. I understand the difference between Shared Material and Material. And in this problem, this knowledge does not help to prevent the Spine Skeleton Animation component from constantly appropriating old material

        VOXELIUM

        I don't understand your complaint when I ask you for help.

        Sorry if this was coming across as a complaint, it was not intended as such.

        VOXELIUM I do not need it. I need to trigger the color change using the AllinOneShaders component. I want to apply a material to an object that is changed using the AllinOneShaders component.

        Sorry, then I misunderstood the line "I do it using code" in your earlier posting.

        At the start of a game level, the AllinOneShaders component change color of my weapon using the HueShift effect (I do it using code).

        VOXELIUM This makes no sense at all in this conversation. I understand the difference between Shared Material and Material. And in this problem, this knowledge does not help to prevent the Spine Skeleton Animation component from constantly appropriating old material

        This does make sense in that one of your components (likely the AllinOneShaders component, or one of your scripts interacting with the material) is modifying material properties and creating a material instance. I mentioned this since if your components would not create material instances, the problem would not occur at all, as the documentation explains.

        To summarize, the problem seems to be that your AllinOneShaders component is modifying material parameters, creating a material instance. Now it either needs to be avoided that the instance is created in the first place (which I would recommend), or the material instance needs to be assigned after the SkeletonRenderer component updated the materials, by using the OnMeshAndMaterialsUpdated update callback (or by changing your script's exectution order to run after the SkeletonRenderer component).

        In general, why do you need a AllinOneShaders component to just assign material properties and thus create a Material instance, when you could just directly create a material (asset) with the same desired properties?

          VOXELIUM There is no description of the solution to my problem. There it is offered.
          That's what it says in the instructions.
          `MaterialPropertyBlock mpb = new MaterialPropertyBlock();
          mpb.SetColor("FillColor", Color.red); // "FillColor" is a named property on the used shader.
          mpb.SetFloat("FillPhase", 1.0f); // "FillPhase" is another named property on the used shader.
          GetComponent<MeshRenderer>().SetPropertyBlock(mpb);

          // to deactivate the override again:
          MaterialPropertyBlock mpb = this.cachedMaterialPropertyBlock; // assuming you had cached the MaterialPropertyBlock
          mpb.Clear();
          GetComponent<Renderer>().SetPropertyBlock(mpb);`

          I do not need it. I need to trigger the color change using the AllinOneShaders component. I want to apply a material to an object that is changed using the AllinOneShaders component.

          MaterialPropertyBlock is used to change Material properties, without creating unnecessary Material instances each time, which I thought you would need. I assume that your AllinOneShaders component also just sets material parameters, just the "bad way" which creates a new Material instance with each change.

          Harald
          All In 1 Sprite Shader is a great way to quickly apply great effects to sprites.
          This is a great solution for working quickly and doing it beautifully.
          In comparison, using MaterialPropertyBlock is like walking around with Nokia210 when people use iPhone 15 and wear virtual glasses.
          I found a solution to the problem - change the AllinOneShaders parameter through the animator.
          It's a shame that Spine doesn't allow a simple solution through code to work with such good tools as AllinOneShader

            VOXELIUM All In 1 Sprite Shader is a great way to quickly apply great effects to sprites.
            This is a great solution for working quickly and doing it beautifully.

            Things that work quickly and easily are unfortunately not always efficient, flexible or robust. Unfortunately sometimes it takes a few extra steps which might be more uncomfortable to make things work well in a real world scenario. It's like driving in the middle of the road instead of on the right side - as long as there is no traffic on the opposing side it's alright and provides you with much more space.

            VOXELIUM I found a solution to the problem

            Very glad to hear!

            VOXELIUM In comparison, using MaterialPropertyBlock is like walking around with Nokia210 when people use iPhone 15 and wear virtual glasses.

            Please note that we neither designed Unity's material- nor batching system, you could request a better solution on their forums if you're unhappy with the interface.

            VOXELIUM It's a shame that Spine doesn't allow a simple solution through code to work with such good tools as AllinOneShader

            Please note that the spine-unity runtime needs to update materials to match the active attachments. It's common for thirdparty tools to not be compatible out of the box, like when AllinOneShader wants to park in the same parking slot, the one who gets there first wins the race. spine-unity provides three or more ways to assign material properties, if AllinOneShader can do only one which is not compatible, that's a bit unfortunate.

            You could as well argue that it's a shame that such good tools like AllinOneShader don't offer a simple checkbox Use MaterialPropertyBlock to do that all for you behind the scenes, but only rigidly offers creating material instances, which leads to e.g. 10 instead of 1 draw calls when you set the same hue on 10 objects.

            Nevertheless thanks for sharing your problems and insights with us, sorry that it has been frustrating for you. We will have a look whether we can provide some kind of an easy solution for less experienced programmers to check for modified materials and re-apply them automatically.

              Harald
              Thank you. Our communication was fruitful.