• Unity
  • Per-instance material

Related Discussions
...

It might make sense to be in the official runtime. What do you think Pharan? I think the only issue is how much it affects people who aren't using this feature, which is probably not much.

Note if you use Git to pull down the runtimes, then any changes you make will be automatically merged when you update. If that isn't possible, you'll get a conflict and it should be pretty clear how to fix it. I very highly recommend SmartGit.

@[borrado]
There's that. I definitely opt for extensions rather than modifying the core as much as I can unless it just makes sense for everyone.

I think it should be in the official runtime.
But I don't think an inspector in SkeletonRenderer is a good idea at this point. That part (the serialization and inspector part) could be a separate MonoBehaviour though.

What do you think ZimM? Dictionary<Slot, Material>? Or Dictionary<Attachment, Material>?
I'm gravitating towards Slot.

Also, how do we skip the dictionary lookup so it doesn't affect people who aren't using it? if (materialDictionary.Count > 0)? Should we field-initialize the dictionary or lazy-instantiate?

Our current project requires us to do a bunch of fancy nonsense in order to make spine work for us. One such bit of nonsense involves using special cutout shaders to draw spine characters in some contexts (since we position them within 3d environments) and special UI shaders in other contexts (since we also use them in Unity UI hierarchies). Spine is, by necessity perhaps, a little bit bossy about how/when it creates and assigns materials, making it very difficult for us to achieve this!

One thing we can do is iterate through the materials list in LateUpdate() and simply swap shaders where it's necessary, but this suffers from a) update order problems and b) inefficiency, as even inspecting a MeshRenderer's sharedmaterials allocates garbage. We could also do it on one of Unity's render callbacks, though those get called once per camera (which in some cases would be more than we require).

It might be helpful for us to have a way to listen for when SkeletonRenderer's LateUpdate completes (so that we can futz with the meshrender at that point).

I would blame the Unity APIs to be fair, but I digress. :talk:

We are all for improving how things work. Don't be shy about changing SkeletonRenderer. If you have a solution that is useful in general without impacting performance, it makes sense to put it in Git.

@brendan.vance
Yeah. If you have a particular need. Just change SkeletonRenderer. And if you think it's useful for the general case, suggest it.

For modifying the material array, SkeletonRenderer actually caches that array as one of its fields before passing it to MeshRenderer. So you can access it without generating garbage if you make it [NonSerialized] public, I guess, and put an appropriate event you can listen to at the end of SkeletonRenderer to change it at the right time.

For Spine in Unity UI, check out SkeletonGraphic. It's part of the latest runtime. It does fancy nonsense to be able to draw in UI and be masked and stuff. It also suffers the same limitations of other Unity UI Graphic classes (only single-material meshes are supported, but it doesn't enforce a material so you can set it to whatever you want that has a shader with a _MainTex.)

The core of what SkeletonRenderer actually needs to do to render correctly is set the correct texture per submesh so it's actually pulling the right images from the right textures. There's no way to set a texture array, or use some kind of MaterialPropertyBlock to set the texture per submesh, so setting Material array was the solution. This is Unity's API working against it, and now against you. But again, you can fix whatever parts of it if you need to.


04 Mar 2016 3:49 am


Per slot materials have been added to the latest SkeletonRenderer/SkeletonAnimation.

Using the API looks like this:

using UnityEngine;
using System.Collections;

public class SampleClassThatUsesCustomSlotMaterials : MonoBehaviour {

   public SkeletonAnimation skeletonAnimation;

   [SpineSlot]
   public string slotName;

   public Material material;

   void Start () {
      if (material == null) 
         Debug.LogWarning("Warning!! A null material will cause the attachment to be rendered with the default Unity pink material.");

  Spine.Slot slotObject = skeletonAnimation.skeleton.FindSlot(slotName);
  skeletonAnimation.CustomSlotMaterials[slotObject] = material;
   }

   public void RemoveCustomMaterial () {
      Spine.Slot slotObject = skeletonAnimation.skeleton.FindSlot(slotName);
      skeletonAnimation.CustomSlotMaterials.Remove(slotObject);
   }

}
8 días más tarde

Oh, cool, you've done it already 🙂 I guess there are some use-cases for per-attachment overrides, but they are quite marginal.
Unfortunately, this does not solves the problem of changing material per-atlas. Sure, now we can just assign the material to each and every slot, but that would be pretty ugly and slow. In general, I often need to override the character material as a whole (per-atlas), and very occasionally change the material for a slot. Perhaps it'd make sense to add per-atlas override on top of that, with per-slot override having a higher priority? What do you think?

per-attachment overrides: It's also pretty easy to edit the code to do that. And since this does no serialization, nothing should break.

The material override was working well for you, right?
if (material == a) material = b?
then do per-slot or per-attachment.

Actually, the other way around would work too since the material check would fail if you did the per-slot override too.


12 Mar 2016 2:32 pm


There's a big cleanup and change to SkeletonRenderer and the SubmeshRenderer system coming so any further changes will probably be there.
https://github.com/EsotericSoftware/spine-runtimes/tree/unity-skeletonrenderer

6 meses más tarde

CustomSlotMaterial seems to be what I'm looking for as well. It almost works; I'm replacing the shirt slot material for one instance of an animation with a pink shirt. I made the material from a single 256x256 pink shirt png, but when I add the CustomSlotMaterial for that one instance, it turns into a 256x256 pink square and just goes on top of his first shirt. What could I be doing wrong with regards to the shader? The Materials settings for the pink shirt are the same as for the materials of the base atlas. I found another shader you put up somewhere called Skeleton Fillable, but that doesn't change anything.

The texture should be the exact same atlas, not a different texture.
This feature is for using different shaders per slot. Not for using different images.

Then how would I go about, say, sticking a moustache onto the head or a new shirt from a separate image onto the body, when the moustache or shirt comes from a separate image that isn't on the atlas? We very much need this functionality to make our animations as dynamic and interchangeable as possible. Is the only way really to dig into the guts of Spine-CSharp and modify the AtlasAsset.cs code?

P.S.: It looks to me like the shirt actually appears, but it's stretched out to size of the original atlas and rotated sideways (even though the texture properties claim it's the original size). Considering that AtlasAsset uses a Material to draw its pages, it seems like with some wrangling and code changes, this could work. It also seems like it can be a different atlas material without any such changes, as long as the sizes and offsets are the same.

P.P.S: Okay, this is weird. Setting the scale for the new tshirt material (which is supposed to be 256x256) HIGHER actually makes it SMALLER. The original atlas we used for this spine animation was 2048x1024, and the offset of the original shirt was 1034,648. This shirt from outside the atlas now fits perfectly if I scale its material's texture to (8,4) and set its offset to (-4,-0.5). This is obviously not a permanent solution and I know you said this isn't what it was intended to do, but it looks like the system can be twisted to make it so. I'd just like to know the pattern behind this madness. Pixels per unit for all the images was set to 1-1 as well.

Spine creates a mesh and maps vertex UVs according to where the atlas regions are.
This is how UV mapping works, even with 3D models. If your 3D character had a texture map for all parts, and you replaced the texture just for the torso part, with a texture that was not laid out the same, you would get the same results. A torso mapped to an incorrect location in the texture. The same thing would also happen if you similarly changed the material of a Unity Sprite.

Please see Skins - Spine User Guide and Equips, Customization and Mix and Match for what you're probably trying to do.

In combination with that, see the functionality of the sample SpriteAttacher.cs script module. Spine-Unity SpriteAttacher - YouTube

Thanks. I will definitely look into that. I actually figured out my own method using slot separators; I separated the shirt mesh into its own mesh, added a child mesh to that, copied the properties of shirt A, created a simple material for my outside shirt (shirt B), added it to the child mesh, copied the properties of the original shirt mesh, and then changed the UV coordinates of the child mesh to 0,0;1,0;0,1;1,1, and it worked. Of course, I haven't stress tested it yet and the links you gave could easily be more efficient so I'll look at those now.

10 meses más tarde

Hey,

Sorry for necroing this thread, but it seemed more appropriate than to start a new one.

I am currently attempting to override a material at runtime ("atlas material"?). We've got it setup so that theres just one material per character but the process of overriding that material still has me scratching me head.

We're using material property blocks, and thats working fine, however, at times the characters in question has got to disable/enable certain shader effects for performance reasons, which is why material property blocks cant be the end all solution, at least in this case.

Initially the shaders were #pragma multi_compile or feature shaders to deal with that, but they don't exactly go hand in hand with Spine, which is shame - so the only option left is to split the shader features into seperate shaders & materials and swapping it out at runtime depending on the situation.

I've seen methods such as GetComponent<SkeletonAnimation>().CustomMaterialOverride.Add(originalMaterial, newMaterial); mentioned, as well as skeletonAnimation.CustomSlotMaterials[slotObject] = material; but it's really unclear to me on how to override the atlas material in an efficient manner.

The Skeleton Custom Materials is nice but seems a bit "hacky" since it loops through all of the slots, if I understand it correctly? It would really like for some references and examples regarding how to best achieve it in C# in an efficient manner especially since it seems to be a topic which pops up every now and then.
Finally,(without having to delve too deep into the structure of spine) arent we looking at a substantial overhead when overriding materials with the way that spine is structured? Overhead is of a concern since we're working a mobile title, and that material swapping will occur quite often).

Hopefully I've just missed or overlooked something.

Thanks

What you want is CustomMaterialOverride.

skeletonAnimation.CustomMaterialOverride.Add(originalMaterial, newMaterial);

and if you want to remove it.

skeletonAnimation.CustomMaterialOverride.Clear();

The performance hit there is negligible. You should test it on device to confirm.

The performance hit of CustomSlotMaterial is worse because that's the one that causes it to check every slot because that's what you use for a custom material for a specific slot, rather than the whole thing.

Additionally, if you are using a single material, you can turn on the optional "Use Single Submesh" checkbox.
This skips checking for texture swaps and just uses the first Material it finds for the whole thing.
This is opt-in so that users who use gargantuan multi-page atlases will work by default.

If CustomMaterialOverride bothers you, you can make a separate component that executes a LateUpdate after SkeletonRenderer's LateUpdate (this is when the renderer materials are checked and set), and set your custom material on the MeshRenderer yourself. It's really up to you.

19 días más tarde

Thank you for the reply, Pharan.

Another question.

Currently working with SkeletonGraphic in tandem with Unity UI and I've noticed that theres no SkeletonGraphic.CustomSlotMaterials function for setting a slot material to something else.

Is there any other way to go about doing that?

For SkeletonGraphic, it's much easier. Just replace the material on the SkeletonGraphic.
That property doesn't update every frame so it won't overwrite your changes.

If you just need to replace the texture, you can set the OverrideTexture property.

It's done this way because this is how the underlying UnityEngine.UI.MaskableGraphic class works with the Unity UI system.

But how would I go about changing the material for a specific slot using SkeletonGraphic? The method that you mentioned would replace the material for the entire graphic, no?

UnityEngine.UI.MaskableGraphic only supports displaying one material.
So sadly, SkeletonGraphic has this limitation too.