- Home /
Adding to SkinnedMeshRenderer Materials array
Hey everyone, I'm facing a huge problem that many days of googling has not answered. I am using a library for Unity called UMA that generates humans for me. Their have skinned mesh renderers that have two materials -- the 1st seems to be for their body and the 2nd for their hair. I'm trying to add a red outline material (with an outline shader) to them so I can show the character as selected. If I simply replace the array of two with my outline material, I see only the outline. If I leave the two as-is and I add a 3rd material, it seems to apply to the hair only. So, somehow UMA created a set of meshes that is dependent on material INDEX. ONLY index 0 applies to the body but I cannot replace it, nor do I know enough about shaders to make a shader that will show the body AND the outline. Is there a simple way for me to add my outline material but apply it to all meshes in the renderer? I don't care about draw calls. Only a single object will ever be selected in the scene at any given time anyway.
One more thing to note... I want my technique to work for all other creatures in my game, besides UMA humans, so the materials could vary. A water elemental, for example, will have a completely different material/shader so I would like my solution to be something along the lines of adding an additional material for the outline and apply it to those same meshes rather than trying to make a whole new set of shaders to parallel all of my existing shaders.
Thanks in advance! :)
Answer by Bunny83 · Dec 09, 2017 at 10:10 PM
Unfortunately that is not possible. The way submeshes works is like this:
For every submesh in the mesh you have to have one material in the materials / sharedMaterials array.
Each submesh is paired with the material from the materials / sharedMaterials array with the same index. So submesh 0 uses material 0 and submesh 1 uses material 1.
However if you add more materials than there are submeshes, only the last submesh will be re-rendered again for every additional material. So the submesh index is basically clamped.
It would be great if Unity would simply wrap the index around and keep going but unfortunately it doesn't. That means when you have two submeshes but specify 4 materials Unity will render this:
submesh 0 with material 0
submesh 1 with material 1
submesh 1 with material 2
submesh 1 with material 3
That means a single skinned mesh renderer with submeshes can only render the mesh once with one set of materials. If you want to render the whole mesh again with different materials you would have to duplicate the object. Another way could be to actually create a "selected shader" that is a combination of your two shaders. So you can either switch the selection on / off by using a shader parameter or by replacing the materials with the selection material variant.
edit
Another way if merging of the shaders is too complicated would be to "manually" render the objects again using a replacement shader inside OnPostRender. This requires your objects to have a specific tag. When rendering the camera with a replacement shader and using a tag value only the object with that tag will be rendered again with the replacement shader. So you could dynamically assign the tag "selected" to the desired objects and they should get their outline.
I replied to this but it wouldn't let me post it as a reply to your comment. $$anonymous$$aybe it was too long. I added it as an answer.
Thank you for your help :)
Thanks for telling me about OnPostRender and SetReplacementShader... two concepts I didn't know about. I did try your suggestion but I didn't get it right.
I added this tag to my subshader: Tags { "Queue" = "Transparent" "RenderType" = "selected" }
I created this script and added it to my camera:
using UnityEngine;
public class PostRender : $$anonymous$$onoBehaviour
{
Shader shader;
void Start() {
shader=Shader.Find("Outlined/Silhouette Only");
}
public void OnPostRender() {
Camera.main.SetReplacementShader(shader,"selected");
}
}
Then, when I ran my game, the camera wouldn't render anything. Furthermore, in the editor, I set an object's tag to "selected" and even that one object's outline didn't render. Nothing did. I'm sorry if I'm missing something simple here. I assumed this would only be an additional pass after my scene was rendered.
Also, I just noticed I was getting this warning repeatedly: Attempting to render from a camera that is currently rendering. Create a copy of the camera (Camera.CopyFrom) if you wish to do this.
You should use RenderWithShader not SetReplacementShader. "SetReplacementShader" does permanentally set the given replacement shader for the camera which you do not want. "RenderWithShader" does render the camera immediately. That's why this method can only be called during rendering.
You may need to use a second camera as the warning tells you to do the replacement shader rendering.
Answer by ChristmasEve · Dec 09, 2017 at 11:38 PM
Thank you for clarifying this for me Bunny! That's so unfortunate though. You know what? My experience was that it worked like this:
submesh 0 with material 0
submesh **1** with material 1
submesh **1** with material 2
submesh **1** with material 3
Body was 0 and hair was 1. When I started adding more materials, it was only affecting the hair. Yeah, it's funny that you mentioned you wished Unity "wrapped the indexes" because that's exactly what I hoped for. I tried adding materials, hoping that would happen but :( no... I thought about duplicating the object but I have to handle animating both at exactly the same time. All my code has to be changed to loop through both. The combination of the two shaders sounds like my best bet but I've been shying away from that since I use different shaders throughout my players/mobs in the game. I don't know a lot about shaders but, let's say you were using the standard "built-in" shader.... how hard would it be to add this shader script and make it switchable (on/off):
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Outlined/Silhouette Only" {
Properties {
_OutlineColor ("Outline Color", Color) = (0,0,0,1)
_Outline ("Outline width", Range (0.0, 0.03)) = .005
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : POSITION;
float4 color : COLOR;
};
uniform float _Outline;
uniform float4 _OutlineColor;
v2f vert(appdata v) {
// just make a copy of incoming vertex data but scaled according to normal direction
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 norm = normalize(mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal));
float2 offset = TransformViewToProjection(norm.xy);
o.pos.xy += offset * o.pos.z * _Outline;
o.color = _OutlineColor;
return o;
}
ENDCG
SubShader {
Tags { "Queue" = "Transparent" }
Pass {
Name "BASE"
Cull Back
Blend Zero One
// uncomment this to hide inner details:
//Offset -8, -8
SetTexture [_OutlineColor] {
ConstantColor (0,0,0,0)
Combine constant
}
}
// note that a vertex shader is specified here but its using the one above
Pass {
Name "OUTLINE"
Tags { "LightMode" = "Always" }
Cull Front
// you can choose what kind of blending mode you want for the outline
//Blend SrcAlpha OneMinusSrcAlpha // Normal
//Blend One One // Additive
Blend One OneMinusDstColor // Soft Additive
//Blend DstColor Zero // Multiplicative
//Blend DstColor SrcColor // 2x Multiplicative
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
half4 frag(v2f i) :COLOR {
return i.color;
}
ENDCG
}
}
Fallback "Diffuse"
}
Yes, my bad i fixed my answer. I also added another alternative which might fit your situation better.
Your answer
Follow this Question
Related Questions
QuickOutline: how can I change the shader to work with objects with two materials? 3 Answers
Outline diffuse shader (toon) : no results? 1 Answer
Shader Edit Help: 2D Outline Coloring 2 Answers
Object outline component 0 Answers
Outline Shader HELP 0 Answers