Search Unity

Creating a Render Overlay System

Discussion in 'Scripting' started by ruj, Sep 18, 2014.

  1. ruj

    ruj

    Joined:
    Feb 28, 2013
    Posts:
    113
    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
     
  2. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
  3. ruj

    ruj

    Joined:
    Feb 28, 2013
    Posts:
    113
    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.
     
  4. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    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
     
    ThermalFusion likes this.
  5. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    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.
     
  6. ruj

    ruj

    Joined:
    Feb 28, 2013
    Posts:
    113
    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).
     
  7. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    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.
    extraskinnedmesh.jpg
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ExtraSkinnedMeshRenderer : MonoBehaviour {
    6.     public Material addedMaterial;
    7.     private SkinnedMeshRenderer[] addedRenderers;
    8.     private void Awake() {
    9.         SkinnedMeshRenderer[] sourceRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
    10.         addedRenderers = new SkinnedMeshRenderer[sourceRenderers.Length];
    11.         for (int i = 0; i < sourceRenderers.Length; i++) {
    12.             GameObject sourceGo = sourceRenderers[i].gameObject;
    13.             Transform sourceTransform = sourceRenderers[i].transform;
    14.             SkinnedMeshRenderer sourceMeshRenderer = sourceRenderers[i];
    15.  
    16.             GameObject cloneGameObject = (GameObject)GameObject.Instantiate(sourceGo, sourceTransform.position, sourceTransform.rotation);
    17.             Transform cloneTransform = cloneGameObject.transform;
    18.             SkinnedMeshRenderer cloneRenderer = cloneGameObject.GetComponent<SkinnedMeshRenderer>();
    19.  
    20.             cloneTransform.parent = sourceTransform.parent;
    21.             cloneTransform.localPosition = sourceTransform.localPosition;
    22.             cloneTransform.localRotation = sourceTransform.localRotation;
    23.             cloneTransform.localScale = sourceTransform.localScale;
    24.  
    25.             Material[] cloneMaterials = new Material[sourceRenderers[i].sharedMaterials.Length];
    26.             for (int m = 0; m < cloneMaterials.Length; m++) {
    27.                 cloneMaterials[m] = addedMaterial;
    28.             }
    29.             cloneRenderer.sharedMaterials = cloneMaterials;
    30.         }
    31.     }
    32. }
    33.  
     

    Attached Files:

  8. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    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):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ExtraSkinnedMeshRenderer : MonoBehaviour {
    6.     public Material addedMaterial;
    7.     private SkinnedMeshRenderer[] addedRenderers;
    8.     private void Awake() {
    9.         SkinnedMeshRenderer[] sourceRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
    10.         addedRenderers = new SkinnedMeshRenderer[sourceRenderers.Length];
    11.         for (int i = 0; i < sourceRenderers.Length; i++) {
    12.             GameObject sourceGo = sourceRenderers[i].gameObject;
    13.             Transform sourceTransform = sourceRenderers[i].transform;
    14.             SkinnedMeshRenderer sourceMeshRenderer = sourceRenderers[i];
    15.  
    16.             GameObject cloneGameObject = (GameObject)GameObject.Instantiate(sourceGo, sourceTransform.position, sourceTransform.rotation);
    17.             Transform cloneTransform = cloneGameObject.transform;
    18.             SkinnedMeshRenderer cloneRenderer = cloneGameObject.GetComponent<SkinnedMeshRenderer>();
    19.  
    20.             cloneTransform.parent = sourceTransform;
    21.             cloneTransform.localPosition = Vector3.zero;
    22.             cloneTransform.localRotation = Quaternion.identity;
    23.             cloneTransform.localScale = Vector3.one;
    24.  
    25.             Material[] cloneMaterials = new Material[sourceRenderers[i].sharedMaterials.Length];
    26.             for (int m = 0; m < cloneMaterials.Length; m++) {
    27.                 cloneMaterials[m] = addedMaterial;
    28.             }
    29.             cloneRenderer.sharedMaterials = cloneMaterials;
    30.         }
    31.     }
    32. }
    33.  
    Code (shader):
    1.  
    2. Shader "Custom/OverlayShader" {
    3.     Properties {
    4.         _Color ("Color", Color) = (1,1,1,0.5)
    5.     }
    6.     SubShader {
    7.         Pass {
    8.             Tags { "RenderType"="Transparent" "Queue"="Geometry+1" }
    9.             ZTest Equal
    10.             Blend SrcAlpha OneMinusSrcAlpha
    11.             ZWrite Off
    12.  
    13.             CGPROGRAM
    14.  
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.             fixed4 _Color;
    18.  
    19.             float4 vert(float4 v:POSITION) : SV_POSITION {
    20.                 return mul (UNITY_MATRIX_MVP, v);
    21.             }
    22.  
    23.             fixed4 frag() : COLOR {
    24.                 return _Color;
    25.             }
    26.  
    27.             ENDCG
    28.         }
    29.     }
    30. }
    31.  
    Edit: Can we have shader syntax highlighting somehow?
     
    ruj likes this.
  9. ruj

    ruj

    Joined:
    Feb 28, 2013
    Posts:
    113
    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!
     
    Last edited: Sep 20, 2014
  10. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    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.
     
  11. ruj

    ruj

    Joined:
    Feb 28, 2013
    Posts:
    113
    ThermalFusion likes this.