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

Bug [IN-47534] Hair rebuild fails in build when HDLines are used. (hair vanishes)

Discussion in 'High Definition Render Pipeline' started by schema_unity, Jul 13, 2023.

  1. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    Hello,

    when making a build containing hair, triggering a rebuild will currently make the hair vanish, if if High Quality LInes are used. It works perfectly fine in the editor (both playmode and editmode)

    A rebuild can be triggered by either setting the hair instance checksums to null or loading new settings (which will essentially do the same).

    The rebuilding works perfectly fines on built-in lines, however when HDlines are used, it makes the hair vanish (while still having shadows).

    Rebuilding hair is important, as it's the only way to load new hair assets and settings without keeping a prefab of each variant, which would be a lot, and very messy. It's also a quick way to reset the simulation state, though there is a method that doesn't require a rebuild.

    Explanation:
    I have analyzed this and found the reason on why this is failing. Upon rebuild, the HDAdditionalMeshRendererSettings component gets added to all MeshRenderers of the hair instance.
    However, HDAdditionalMeshRendererSettings has a OnValidate() method which loads a compute shader (VertexSetup) via AssetDatabase. This method gets called in the editor when the component is added, and that's the reason why it works in the editor. It doesn't get called in any build, so the HDAdditionalMeshRendererSettings will never be valid for HQ line rendering (I found this through checking LineRendererIsValid(), and then emulating its functionality, ruling out all other points of failures.)

    Workaround:
    Because m_VertexSetupCompute is a private member I cannot directly assign it. However, I was able to create a persistent Dummy game object with HDAdditionalMeshRendererSettings and tag the GameObject. Using this, I can copy all serialized properties via reflection to any newly added HDAdditionalMeshRendererSettings, which assigns the compute shader on them, in the hair package. This workaround has been confirmed to work without problems.

    Actual Fix:
    m_VertexSetupCompute in HDAdditionalMeshRendererSettings needs to either be public, or assigned using a Resources.Load instead of AssetDatabase (or any other way to assign the computeshader that works during runtime). Otherwise, the only way to fix the hair package is to use the convoluted workaround.
     
  2. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
    wow, looks like the High Quality Lines feature really need some extra work to make it usable. We were promised to have it ready by Unity 2022.2 first, but it seems to be much more complicated to get it production ready. I am waiting to use this since more than a year now, seems to be another few month until the bugs will be fixed.
    @chap-unity are the issues with High Quality Lines not working in builds (this bug and the other, even more severe Unity crash bug) at least on the radar?
     
  3. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
    @schema_unity I am now in same situation, tried the first build, and hair vanishes on creation. Can you explain in more detail how your workaround with reflection is working? I found the added components on some mesh renderer, but not sure how to create a persistent copy of it and overwrite any newly created components? Also, did you get a public link for the issue?
    I also found a new big issue, performance goes badly down if hair object is far way from camera, but visible. I made another thread for this, and reported the issue.
     
  4. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    Sure, so basically the dummy object I'm using is a gameobject with a MeshRenderer (and mesh filer without a mesh) with a material that I use for one of the hairs (can be any, but it's a requirement for the line renderer to be valid).
    Then I add the 'Mesh Renderer Extension' with the 'High Quality Line Rendering' module, and set that to enabled (though i don't think that's even necessary). All other options just default.
    Then, I tagged the gameobject so I can find it from the demoteam.hair package.

    In that package, I modified the "public static T CreateComponent<T>" function:

    Code (CSharp):
    1. public static T CreateComponent<T>(GameObject container, HideFlags hideFlags) where T : UnityEngine.Component
    2.         {
    3.             T component;
    4.             if (typeof(T) == typeof(HDAdditionalMeshRendererSettings))
    5.             {
    6.                 const string dummyTag = "HDAdditionalMeshRendererSettingsDummy";
    7.  
    8.                 GameObject dummy = GameObject.FindGameObjectWithTag(dummyTag);
    9.  
    10.                 if (dummy == null)
    11.                 {
    12.                     throw new Exception("No renderer dummy for hair " + dummyTag);
    13.                 }
    14.  
    15.                 if (!dummy.TryGetComponent(out HDAdditionalMeshRendererSettings dummySettings))
    16.                 {
    17.                     throw new Exception("No renderer dummy component for hair " + dummyTag);
    18.                 }
    19.                 Debug.Log("######## CREATED HDAdditionalMeshRendererSettings from dummy " );
    20.                 var ct = container.AddComponent(dummySettings);
    21.                 ct.enabled = false;
    22.                 component = ct as T;
    23.                
    24.             }
    25.             else
    26.             {
    27.  
    28.                 component = container.AddComponent<T>();
    29.             }
    30.  
    31.             component.hideFlags = hideFlags;
    32.             return component;
    33.         }
    (you can cache it if needed)

    Adding a copy of a component to another component is not something unity provides out of the box, so I added that as an extension:
    Code (CSharp):
    1.  public static class ComponentExtensions
    2.     {
    3.         public static T GetCopyOf<T>(this T comp, T other) where T : UnityEngine.Component
    4.         {
    5.             Type type = comp.GetType();
    6.             Type othersType = other.GetType();
    7.             if (type != othersType)
    8.             {
    9.                 Debug.LogError($"The type \"{type.AssemblyQualifiedName}\" of \"{comp}\" does not match the type \"{othersType.AssemblyQualifiedName}\" of \"{other}\"!");
    10.                 return null;
    11.             }
    12.  
    13.             BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Default;
    14.             PropertyInfo[] pinfos = type.GetProperties(flags);
    15.  
    16.             foreach (var pinfo in pinfos)
    17.             {
    18.                 if (pinfo.CanWrite)
    19.                 {
    20.                     try
    21.                     {
    22.                         pinfo.SetValue(comp, pinfo.GetValue(other, null), null);
    23.                     }
    24.                     catch
    25.                     {
    26.                         /*
    27.                          * In case of NotImplementedException being thrown.
    28.                          * For some reason specifying that exception didn't seem to catch it,
    29.                          * so I didn't catch anything specific.
    30.                          */
    31.                     }
    32.                 }
    33.             }
    34.  
    35.             FieldInfo[] finfos = type.GetFields(flags);
    36.  
    37.             foreach (var finfo in finfos)
    38.             {
    39.                 finfo.SetValue(comp, finfo.GetValue(other));
    40.             }
    41.             return comp as T;
    42.         }
    43.         public static T AddComponent<T>(this GameObject go, T toAdd) where T : UnityEngine.Component
    44.         {
    45.             return go.AddComponent<T>().GetCopyOf(toAdd) as T;
    46.         }
    47.     }
    (not my code, but pretty straight forward)

    So basically, this creates a copy of the component's serialized fields, and assigns them via reflection.

    This will copy the serializable private member that holds the ComputeShader that fails to initialize when a default AddComponent<HDAdditionalMeshRendererSettings> is used by the hair package, because it's initialization happens in an OnValidate method which doesn't exist outside the editor.
     
  5. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    I submitted the issue when I first opened this thread, but I have yet to hear back from unity.
     
  6. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
    nice, thanks for the explanation! I wonder if it would be easier to create a renamed copy of HDAdditionalMeshRendererSettings, fix the OnValidate() stuff in this new class, and use this new class in Hair package.
     
  7. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    Yeah, since we probably have to make a local copy of the HDRP package anyways to fix the crashes, fixing it at the source would be the easiest.
     
    Qleenie likes this.
  8. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    Qleenie likes this.
  9. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
    Great, can you post the public link (to issuetracker) as soon as available so I can vote on it? The link you posted is still internal, only visible to you and Unity´.
     
  10. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
    Thanks again for the tipp to make it work in build. It works fine in my case.

    The positive side effect: Now that we have a dummy HDLineRenderer, it's easy to change the settings. I changed the LOD mode to camera, which at least brings some performance improvements if camera is far away, but it's still way too slow, though. I saw also some code in the Enemies demo dealing with this in a more elaborate way, but even here it takes like 10-15 ms on my 3080 to do the full render pass of HDLines (with a sensational 240 draw calls).
     
    schema_unity likes this.
  11. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    Oh wow, I haven't seen a camera LOD mode. Was this added recently, or have I been missing something? I've only seen manual and automatic (which it says isn't implemented), so I've been doing manual LOD updates.

    I've been turning off HDLine rendering at about 5-7m distance of hair to the camera, and only then starting to decrease the LOD value from there towards 0, as it didn't affect my fps much (maybe that is related to the hair type used, as it does seem to affect simulation). It's not perfect visually, but it's not too noticeable either. However, the jump in fps at that distance, especially in 1080p make it pretty much a necessity.
     
  12. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
    It's a different LOD setting on the Mesh Renderer Extension / HDLineRenderer. It's LOD for the actual line renderer, while the LOD in hair package is for simulation only. I only learned about this from QA;) There is no documentation about this, but you can have a look at the Enemies demo for a sample implementation.
     
  13. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    Oh, I had no idea. The more I find out about this feature, the more I get the feeling it wasn't really prioritized to get production ready, which is a shame, considering how great it looks.
     
    Qleenie likes this.
  14. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
    Yes, 100% this. I made another thread with general feedback about this feature.
     
    schema_unity likes this.
  15. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    Update: The bug has been marked as fixed and under review. (don't think I ever got a public ID/link)
     
    Qleenie likes this.
  16. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
    Does it say in which version it will be fixed? and there is no issuetracker link?
     
  17. Qleenie

    Qleenie

    Joined:
    Jan 27, 2019
    Posts:
    734
  18. schema_unity

    schema_unity

    Joined:
    Jun 13, 2018
    Posts:
    109
    Qleenie likes this.