• Unity
  • Multiple materials on SkeletonRenderSeparator

Related Discussions
...

Hi there!

I'm trying to figure out how to have a different material on each SkeletonPartsRenderer. I assign them in the inspector, however when playing they are overridden by the default material. The additional materials use the same texture, but I want to change some values of the shader of each.

Any help is greatly appreciated!

Edit: Actually I've noticed the material change is overridden at edit time as well. Is it possible to use a different material for each separate renderer?

If you only want to change material property values but the same shader, you can use Unity's MaterialPropertyBlock API on the SkeletonPartsRenderer's MeshRenderer without any problems.

MaterialPropertyBlock mpb = new MaterialPropertyBlock();
mpb.SetColor("_FillColor", Color.red); // "_FillColor" is a named property on a hypothetical shader. 
GetComponent<MeshRenderer>().SetPropertyBlock(mpb);

And to remove the custom values:

MaterialPropertyBlock mpb = this.cachedMaterialPropertyBlock; // assuming you had cached a MaterialPropertyBlock with values in it.
mpb.Clear(); // Clear the property values from the MaterialPropertyBlock
GetComponent<Renderer>().SetPropertyBlock(mpb); // set the renderer property block using the empty MaterialPropertyBlock.

But

If you need to replace it with a different Material and Shader altogether, it's probably better to use SkeletonAnimation/SkeletonRenderer's custom materials.

// For custom materials on slots
GetComponent<SkeletonAnimation>().CustomSlotMaterials[mySlot] = myCustomMaterial; // to change the material of a slot
GetComponent<SkeletonAnimation>().CustomSlotMaterials.Remove(mySlot); // to remove the custom material assignment

Hi Pharan,

Thanks for the reply!

So I'm trying to get property blocks to work, however I'm not having any luck:

readonly float[] _values = { 0, 0.85f, 0.2f };

protected void Start() {
    for (int i = 0; i < SkeletonRenderSeparator.partsRenderers.Count; i++) {
        MaterialPropertyBlock mpb = new MaterialPropertyBlock();
        mpb.SetFloat("_Heat", _values[i]);
        SkeletonRenderSeparator.partsRenderers[i].MeshRenderer.SetPropertyBlock(mpb);

    Debug.Log(string.Format("Setting heat of {0} to: {1}", SkeletonRenderSeparator.partsRenderers[i].gameObject.name, _values[i]));
}
}

I am getting the correct log results, unfortunately I'm not actually seeing the change. Any ideas? This is the property in the shader:

_Heat ("Heat", Range(0.0, 1.0)) = 0.0

Solved it! I needed to uncheck "Copy Property Block" on the SkeletonRenderSeparator. That's awesome, thanks!

So this is great for my current setup, but going forward if I wanted an individual material per renderer, is there a way to replace each material? As I have a lot of slots, so the separator allows me to group these up. Where if I had to change the material on each slot, it would be somewhat annoying.

Ah, I forgot that checkbox was there. What you did was correct.

You'll probably want to cache references to those objects if you need to set the property values more than once.

If you wanted a different Material per parts renderer, I suppose SkeletonPartsRenderer could have a modification so that it ignores the material given to it by SkeletonAnimation, and instead uses whatever you give it, if you provide it a Material.

2 years later
Pharan wrote

If you wanted a different Material per parts renderer, I suppose SkeletonPartsRenderer could have a modification so that it ignores the material given to it by SkeletonAnimation, and instead uses whatever you give it, if you provide it a Material.

Is there a recommended way to use different Materials for each SkeletonPartsRenderer?

My characters have shadows underneath them that are animated as part of their skeleton. I'm using Skeleton Render Separator to separate the shadow from the body, and putting the shadow on a different layer underneath the characters.

I'm working on a dissolve-shader effect that I'd like to only apply to the body and not the shadow - if I could override the Material for the SkeletonPartsRenderer somehow that would probably be the easiest way? :think:

A quick fix for this specific situation might be to use the CustomSlotMaterial and handle the shadow's material that way - but in the long run I will probably want some sort of configuration that works for an entire SkeletonPartsRenderer, instead of per-slot. Unless there is a way to get the list of slots that a SkeletonPartsRenderer is rendering, and apply CustomSlotMaterial to all of them? :think: :think:

Think the easiest way should be to use the delegate SkeletonRenderer.OnMeshAndMaterialsUpdated which is called after the materials are set, and then override the target MeshRenderer Materials with your custom one.

You could also query the slots via ExposedList<Slot> drawOrder = skeleton.drawOrder; and then iterate over them (as seen in the GenerateSkeletonRendererInstruction method here) and compare them to the slots in SkeletonRenderer.separatorSlots. However, that would be much more effort and would need to be updated when the draw order changes.

Harald wrote

Think the easiest way should be to use the delegate SkeletonRenderer.OnMeshAndMaterialsUpdated which is called after the materials are set, and then override the target MeshRenderer Materials with your custom one.

You could also query the slots via ExposedList<Slot> drawOrder = skeleton.drawOrder; and then iterate over them (as seen in the GenerateSkeletonRendererInstruction method here) and compare them to the slots in SkeletonRenderer.separatorSlots. However, that would be much more effort and would need to be updated when the draw order changes.

Thanks Harald! The delegate should work perfectly, and the slot-based approach could be handy for unique cases :detective:


Harald wrote

Think the easiest way should be to use the delegate SkeletonRenderer.OnMeshAndMaterialsUpdated which is called after the materials are set, and then override the target MeshRenderer Materials with your custom one.

You could also query the slots via ExposedList<Slot> drawOrder = skeleton.drawOrder; and then iterate over them (as seen in the GenerateSkeletonRendererInstruction method here) and compare them to the slots in SkeletonRenderer.separatorSlots. However, that would be much more effort and would need to be updated when the draw order changes.

It seems like the OnMeshAndMaterialsUpdated delegate doesn't get called (each frame, at least) if you're using the Skeleton Render Separator - is there another delegate that I could get a callback from maybe?

Thanks for reporting, it is indeed not called since the mesh generation is disabled and moved to the SkeletonRenderSeparator component. We have added a similar callback delegate to the SkeletonRenderSeparator and SkeletonPartsRenderer components.

You can download the updated 3.8 unitypackge here as usual:
Spine Unity Download

For reference: this issue has been tracked under this ticket id:
https://github.com/EsotericSoftware/spine-runtimes/issues/1752

Harald wrote

Thanks for reporting, it is indeed not called since the mesh generation is disabled and moved to the SkeletonRenderSeparator component. We have added a similar callback delegate to the SkeletonRenderSeparator and SkeletonPartsRenderer components.

You can download the updated 3.8 unitypackge here as usual:
Spine Unity Download

For reference: this issue has been tracked under this ticket id:
https://github.com/EsotericSoftware/spine-runtimes/issues/1752

Ooooooh, perfect! You're the man Harald!

2 months later

I couldn't follow the explanations here. I don't know how to use delegates.
How exactly can I create a code that change the material for an object with Skeleton Part Renderer, why can't I simply drag a material inside the mesh?

Another question is, how can I get all slots above a slot in draw order?
For example: findslot("specificslot") and after that I get all slots that is above this one in the draw order.

MichelVictor wrote

I couldn't follow the explanations here. I don't know how to use delegates.

I have added example code to the spine-unity documentation page:
spine-unity Runtime Documentation: Life cycle

MichelVictor wrote

How exactly can I create a code that change the material for an object with Skeleton Part Renderer, why can't I simply drag a material inside the mesh?

Please see the documentation page here:
spine-unity Runtime Documentation: Materials

MichelVictor wrote

Another question is, how can I get all slots above a slot in draw order?

You can access the slots in draw order like this:

var drawOrderItems = skeletonAnimation.Skeleton.drawOrder.Items;

Skeleton drawOrder

Please always have a look at the documentation pages first:
API Reference - Spine Runtimes Guide
spine-unity Runtime Documentation


Well I couldn't make it work.
The only way I make it work is using this script and manually add all slots I want, all different material are in top of draworders, so If i could separate the parts and change the material of all at once was so much easier, I am not a programmer so sometimes is hard to understand how to make it work.

MichelVictor wrote

I am not a programmer so sometimes is hard to understand how to make it work.

Please learn the basic programming skills first. You have to be able to distinguish between what a class and what an object (an instance of a class) is.

The delegate is a non-static (instance) member. You are trying to register it as if it were a static member by using ClassName.DelegateName. Apart from that, I'm not sure if you really want to register at a SkeletonPartsRenderer, or instead at your normal SkeletonRenderer subclass, like SkeletonAnimation or SkeletonMecanim. You then register like this:

SkeletonAnimation skeletonAnimation; // = GetComponent<SkeletonAnimation>();
skeletonAnimation.OnMeshAndMaterialsUpdated += ...;

The only way I make it work is using this script and manually add all slots I want, all different material are in top of draworders, so If i could separate the parts and change the material of all at once was so much easier, I am not a programmer so sometimes is hard to understand how to make it work.

I don't quite understand what you are trying to achieve. Could you please describe what you would like to do?