High Level: Is there any way to render a skinned mesh a second time in a frame with an alternate material? Details: Hoping someone can help me, but I'm worried what I want isn't possible, at least not in an efficient way. I am trying to make an overlay system, meaning a way of rendering an object a second time with another material to get layered effects to play on the object. I have been able to get this to work fine using Graphics.DrawMesh() on regular MeshFilters, but I am hitting a wall with SkinnedMeshRenderers. My solution so far was to use BakeMesh() every frame to generate a mesh that could then go through the DrawMesh() path. While this works, the profiler shows me (unsurprisingly) that it is very slow, not a good solution. So, does anyone have any ideas? Pulling my hair out. Stephen
Thanks for the reply! I'd like to avoid the cost of a full camera setup if I can, since it comes with light culling, occlusion, etc. Tends to add a ton of overhead that seems unnecessary for the simple task of rendering an object twice. I know also you can kind of fake this by adding a material to the materials[] array on the object, and it will render with that extra material, but that also fails if the object has submeshes, since it will only render the last submesh over again.
If you put the object you want double rendered on it's own layer, and set the camera to only that layer, then it only draws one object. May not be appropriate to purpose
You could make new shaders that renders additional passes. Downside is you would need to make a new shader for each two separate ones you wanted to combine.
Thermal, Yeah, that sounds like a lot of specific content, which is what this system is trying to avoid, plus, I have run into a problem where Surface Shaders don't allow you to place more than one pass in them, and these objects use Surface Shader shaders. It's sounding like this is something Unity just doesn't support, which is really disappointing. I used to do a lot of work in Unreal, and this was pretty trivial in there (programmers had to add it of course, but they didn't seem to think it was a difficult feature). Considering how great I have found Unity to be, it's surprising to me that this is so hard to do in an efficient way (especially since DrawMesh() does exactly what I need, just fails for Skinned Meshes).
I made a quick test just duplicating the skinned mesh renderers and assigning a new material to it. The image below is of a Generic rig mechanim avatar. Code and shader supplied as downloads. Just pop the script on a mechanim model (or any parent with SkinnedMeshRenderers) , and put an overlay material in the addedMaterial slot. Code (csharp): using UnityEngine; using System.Collections; public class ExtraSkinnedMeshRenderer : MonoBehaviour { public Material addedMaterial; private SkinnedMeshRenderer[] addedRenderers; private void Awake() { SkinnedMeshRenderer[] sourceRenderers = GetComponentsInChildren<SkinnedMeshRenderer>(); addedRenderers = new SkinnedMeshRenderer[sourceRenderers.Length]; for (int i = 0; i < sourceRenderers.Length; i++) { GameObject sourceGo = sourceRenderers[i].gameObject; Transform sourceTransform = sourceRenderers[i].transform; SkinnedMeshRenderer sourceMeshRenderer = sourceRenderers[i]; GameObject cloneGameObject = (GameObject)GameObject.Instantiate(sourceGo, sourceTransform.position, sourceTransform.rotation); Transform cloneTransform = cloneGameObject.transform; SkinnedMeshRenderer cloneRenderer = cloneGameObject.GetComponent<SkinnedMeshRenderer>(); cloneTransform.parent = sourceTransform.parent; cloneTransform.localPosition = sourceTransform.localPosition; cloneTransform.localRotation = sourceTransform.localRotation; cloneTransform.localScale = sourceTransform.localScale; Material[] cloneMaterials = new Material[sourceRenderers[i].sharedMaterials.Length]; for (int m = 0; m < cloneMaterials.Length; m++) { cloneMaterials[m] = addedMaterial; } cloneRenderer.sharedMaterials = cloneMaterials; } } }
It may or may not be beneficial to actually parent the clones to the sources instead of its parent if the source has animations. My renderers did have animations on their nodes, which caused a slight desync in z values. Fixing this allowed me to set the ZTest to Equal and remove the Offset from the overlay shader. Code (csharp): using UnityEngine; using System.Collections; public class ExtraSkinnedMeshRenderer : MonoBehaviour { public Material addedMaterial; private SkinnedMeshRenderer[] addedRenderers; private void Awake() { SkinnedMeshRenderer[] sourceRenderers = GetComponentsInChildren<SkinnedMeshRenderer>(); addedRenderers = new SkinnedMeshRenderer[sourceRenderers.Length]; for (int i = 0; i < sourceRenderers.Length; i++) { GameObject sourceGo = sourceRenderers[i].gameObject; Transform sourceTransform = sourceRenderers[i].transform; SkinnedMeshRenderer sourceMeshRenderer = sourceRenderers[i]; GameObject cloneGameObject = (GameObject)GameObject.Instantiate(sourceGo, sourceTransform.position, sourceTransform.rotation); Transform cloneTransform = cloneGameObject.transform; SkinnedMeshRenderer cloneRenderer = cloneGameObject.GetComponent<SkinnedMeshRenderer>(); cloneTransform.parent = sourceTransform; cloneTransform.localPosition = Vector3.zero; cloneTransform.localRotation = Quaternion.identity; cloneTransform.localScale = Vector3.one; Material[] cloneMaterials = new Material[sourceRenderers[i].sharedMaterials.Length]; for (int m = 0; m < cloneMaterials.Length; m++) { cloneMaterials[m] = addedMaterial; } cloneRenderer.sharedMaterials = cloneMaterials; } } } Code (shader): Shader "Custom/OverlayShader" { Properties { _Color ("Color", Color) = (1,1,1,0.5) } SubShader { Pass { Tags { "RenderType"="Transparent" "Queue"="Geometry+1" } ZTest Equal Blend SrcAlpha OneMinusSrcAlpha ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag fixed4 _Color; float4 vert(float4 v:POSITION) : SV_POSITION { return mul (UNITY_MATRIX_MVP, v); } fixed4 frag() : COLOR { return _Color; } ENDCG } } } Edit: Can we have shader syntax highlighting somehow?
Thermal, Thanks, this might be a good alternative. I don't love the overhead of generating a new game object whenever I want to run an overlay, but it certainly seems preferable to the alternatives (assuming all the animations automatically drive the new mesh, which I think they will). Will test soon! EDIT: Tested and this is a pretty good solution! Thanks Thermal, if I can find a way to make this work off some kind of object pool this will be perfect. As it is it works well. Thanks!
I'm happy to help. Regarding pooling, I'd probably apply this setup ahead of time to any object potentially needing overlays, then you could just enable or disable the renderers or gameobjects of the overlays on demand. You'd have twice the objects loaded, but you would not have garbage collection penalties from having to destroy and create new objects constantly. No render performance loss for anything disabled either (I'd hope). It would certainly be useful to be able to do this without creating new GameObjects, but most importantly being able to skip the extra skinned meshes being calculated which I'm guessing is worse for performance than rendering the same skinned mesh with several material passes, not having to recalculate the skinning for each layer. Being able to supply equal numbers of extra materials for a renderer. Creating passes like adding one extra material for a single material mesh does, would be one way to approach it. Another would be to simply add additional renderers to an object, each creating an extra layer of materials. Perhaps these are some features you could request in the Feedback section.
Added a suggestion to the feedback page, can be seen here http://feedback.unity3d.com/suggest...-with-different-materials-for-layered-effects