Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Accessing MeshFilter/vertices of a static object

Discussion in 'General Graphics' started by dgoyette, Jun 24, 2018.

  1. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    I've written a simple script that causes a MeshRenderer to "glow". This is accomplished by creating a copy of the object's MeshFilter and assigning it to a new MeshRenderer that uses a special material. The material's shader is basically a variation of a vertex extrusion shader.

    This worked fine until I marked the object as Static, after which point I could no longer access the objects MeshFilter at runtime, because its mesh had been combined with other static meshes. I found that if I made a copy of the MeshFilter in Awake(), the object's MeshFilter hadn't been optimized yet, and I could still access it. But unfortunately, this doesn't work in a release build, and I get errors like this at runtime:

    So, aside from marking an object as not static, is there a way to access its mesh at runtime?
     
    Last edited: Jun 24, 2018
  2. LennartJohansen

    LennartJohansen

    Joined:
    Dec 1, 2014
    Posts:
    2,394
    I do not think you can. but did you try to change the material of the meshrenderer run-time?
     
  3. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,239
  4. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    Yes, read/write is enabled.

    upload_2018-6-24_15-18-37.png
     
  5. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,239
  6. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    Thanks. I'll make a test case and report a bug on this then. The workaround in that post seems to be to remove the static flag from the object. That's not ideal in my case, as part of the door is emissive, and if I remove the static flag then the emission won't contribute to realtime GI (which is pretty important to me).

    Thanks for the feedback.
     
    richardkettlewell likes this.
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,236
    Why not just swap the original material out with another with a two pass shader?
     
  8. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    Probably two reasons. 1) I don't assume that would help at all, as the shader would still depend on being able to access the model's mesh filter, which isn't available. 2) The reason for copying the mesh filter is so that this effect can work on arbitrary objects, with arbitrary materials on them. As I only need the mesh values, I wouldn't want to write multiple versions of this shader to support metallic, specular, or all other kinds of shaders that might on used on the original object. The goal is to be able to attach this component to anything, and have it work without the underlying object needing to have specific characteristics.
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,236
    I've come across basically the same issue on multiple projects now, and solved it in multiple ways.

    Originally I just had a copy of the game object with another mesh filter & renderer component using the highlight material and toggled it on. Works really well, but it's clunky for editing and easily broken as they are two objects.

    For a few projects I was using DrawMesh() but any static object would end up rendering the entire combined static mesh. Kind of entertaining when almost everything in the scene suddenly got highlighted when hovering over a button. This got solved in two ways. Originally it was solved by disabling static on the offending meshes which, as you noted, is bad for lightmaps. However you can selectively disable only Batching Static for an object but leave on Lightmap Static to solve the issue! This certainly solves the problem, but it also disables static batching for that object which can be bad for performance.

    The shipped solution for that game was to have the highlight script cache the original mesh reference rather than try to use the one in the mesh filter component at runtime. For non-static objects there's no duplication since it's just a reference to the same asset, so only static objects end up using a little more memory. This still broke sometimes as the mesh reference and the original mesh could get out of sync, but that's more a tooling problem than an issue with the technique and could be solved with an editor script to set the proper mesh on save or build.

    For a while I switched to using multiple materials on a single renderer, double the material index count for a mesh and overriding with the highlight shader for all those past the original. This worked pretty well, and Unity smartly only renders the part of the combined static mesh it's supposed to. It also works on skinned meshes which the original method does not! This has also been subtly broken for the last year or two such that only the first submesh gets used for all material indices over the submesh count, so on meshes that have multiple materials one part just gets drawn multiple times. I have not reported the issue because I've been lazy and I found a better solution anyway.

    Current solution is to use command buffers and DrawRenderer. Like the multiple material indices solution it properly draws only the part of the combined mesh that's required, and it doesn't require keeping an extra copy of the original mesh! Plus it works on anything.
     
    Last edited: Jun 25, 2018
    dgoyette likes this.
  10. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    That's a pretty interesting glimpse of your process. I'll take a look at DrawRenderer, which I didn't know about. Thanks for the suggestion.
     
  11. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    I'm following up on my efforts to get this working, as it appears I now have a reasonable implementation that works well in all the cases I wanted. Most importantly, and to my pleasant surprise, the effect works very well on a SkinnedMeshRenderer, and follows the mesh as it gets deformed by animations on the SMR. This means I can apply my glow shader to an animated model, and the glow will follow the model's changes.

    I played around with DrawRenderer, and that was a good thing to try. I ultimately didn't use that approach, but it confirmed that there was a way to get the various pixels for static objects at runtime, since DrawRenderer was doing a pretty good job of giving me just the pixels of my object at runtime.

    The approach I used was to make a public "Renderer" property on my script, which conveniently means this supports both MeshRenderers and SkinnedMeshRenderers with no additional fuss. I clone the Renderer, recompute its normals to give the glow mesh a smoothed appearance, and then set the material on the clone to my MeshGlow material. Basically, pretty simple stuff. The key observation is that (for reasons I don't really understand), `Instantiate(SourceRenderer)` results in an object whose mesh has all the attributes I need (vertexes, normals) even even for static objects in a built version of the game.

    Here's the code I used for setting up the glow object. I haven't included the code for the shader, but that's basically just a tweak of the Normal Extrusion shader shown here: https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

    Code (CSharp):
    1.  
    2.         public float Speed = 1;
    3.         public float MaxThickness = 0.25f;
    4.         public float MaxAlpha = 0.5f;
    5.         [ColorUsage(true, true)]
    6.         public Color GlowColor = Color.yellow * 3;
    7.         public Material GlowMaterial;
    8.         public Renderer SourceRenderer;
    9.  
    10.         void Start()
    11.         {
    12.             var clonedRenderer = Instantiate(SourceRenderer);
    13.  
    14.             // Prevent unwanted recursive behavior. If the MeshGlow is being added directly
    15.             // to the object that has the Renderer (which is common), then cloning the renderer will
    16.             // clone the current MeshGlow component, which will cause another MeshGlow to be added
    17.             // to the clone. This will recursively add forever, crashing the engine. So, we check
    18.             // whether the clone that we create itself contains a MeshGlow component, and if so we
    19.             // destroy it to prevent this recursion.
    20.             var childMeshGlow = clonedRenderer.GetComponent<MeshGlow>();
    21.             if (childMeshGlow != null)
    22.             {
    23.                 Destroy(childMeshGlow);
    24.             }
    25.  
    26.             var meshFilter = clonedRenderer.GetComponent<MeshFilter>();
    27.             if (meshFilter != null)
    28.             {
    29.                 var clonedMesh = clonedRenderer.GetComponent<MeshFilter>().mesh;
    30.  
    31.                 // If we don't recompute normals, we get ugly, disconnected sheets of material for any models with hard edges.
    32.                 // Recomputing normals smooths everything, so the glow mesh stays together.
    33.                 RecalculateNormals(clonedMesh, 100);
    34.             }
    35.             else if (clonedRenderer is SkinnedMeshRenderer)
    36.             {
    37.                 UnityEngine.Debug.Log("Yup...SMR");
    38.                 RecalculateNormals(((SkinnedMeshRenderer)clonedRenderer).sharedMesh, 100);
    39.             }
    40.  
    41.             // Configure the material by copying the given material.
    42.             var mat = new Material(GlowMaterial == null ? Shader.Find("Gravia/MeshGlow") : GlowMaterial.shader);
    43.             mat.SetFloat("_Speed", Speed);
    44.             mat.SetFloat("_MaxThickness", MaxThickness);
    45.             mat.SetFloat("_MaxOpacity", MaxAlpha);
    46.             mat.SetColor("_Color", GlowColor);
    47.             mat.SetFloat("_ColorStrength", 1);
    48.             clonedRenderer.material = mat;
    49.  
    50.             // No shadows.
    51.             clonedRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
    52.             clonedRenderer.name = "MeshGlow";
    53.  
    54.             // Place the object in/on the original so they overlap.
    55.             clonedRenderer.transform.position = SourceRenderer.transform.position;
    56.             clonedRenderer.transform.rotation = SourceRenderer.transform.rotation;
    57.             clonedRenderer.transform.SetParent(SourceRenderer.transform);
    58.         }
    59.  
    60.      
    The result looks pretty good, shown here on the static door frame, and the dynamic character model. (It looks a bit off on the model because the model is actually four separate SMRs, so there's some overlap weirdness in the shader.)

    MeshGlow 400@20.gif
     
    bgolus likes this.
  12. Umresh

    Umresh

    Joined:
    Oct 14, 2013
    Posts:
    56
    Can you share the glow shader ?
     
  13. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    Here's the SG version of it, which I haven't updated for 2019.3 yet, but which I think should generally work. The glowing looks pretty good, but I haven't reworked this shader yet to avoid ugly looking overlapping when the mesh "grows".
     

    Attached Files:

  14. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,236
    Currently unsolvable with Shader Graph, at least with the URP version of Shader Graph. Requires the use of stencils, or a depth pre-pass, neither of which are allowed with Shader Graph and the later requires some hacks with the URP.
     
    dgoyette likes this.
  15. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    Thanks for saving me a bunch of time. I'll just keep using this shader without having it "grow", in that case.
     
  16. Twelv445

    Twelv445

    Joined:
    Feb 22, 2013
    Posts:
    31
    The Awake() function is called before static batching occurs, so you may also access the un-batched mesh by caching it as such:

    Code (CSharp):
    1. public Mesh mesh;
    2.  
    3. void Awake()
    4. {
    5.       mesh = GetComponent<MeshFilter>().sharedMesh;
    6. }
    Take note to access the .sharedMesh and not the .mesh as that will return an instance per object.
    Once you've done this you can just access the mesh variable to get the original mesh without being returned the combined mesh.
     
  17. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,117
    I'm pretty sure that's only true when running within the editor, and is not the case when running in a build of the game.
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,236
    Yeah, that's probably a quirk of the editor. Proper builds of the game won't even have the original un-batched mesh at all unless it's directly referenced by a script or non-static renderer. And in that case only those other scripts / renderers will have that reference, the static renderer's shared mesh will be the static mesh during
    Awake()
    .
     
    Vad3rInHale likes this.