Groom binding algorithm improvement

This question was created in reference to: [Groom binding precision [Content removed]

Hello Charles,

I could invest time in this matter and have a solution to propose !

First thing first, I tested your previous patch using tangents and barycentric smoothing, and it did improve a lot the binding.

This method successfully removes the triangle-shaped clumping issue on our groom, thanks to a shared reference basis for neighbour triangles so it’s consistent. The smoothing also helps transitions.

Unfortunately, it has a caveat on UV seams, that you already mentioned and predicted.

Because the tangents are Mikkt tangents, they are based on the UV space.

And the tangents are calculated per-vertex, which means that on points that are on UV seams there can be multiple and different tangents. This causes separate deformations from triangles apart the seam, which creates artefacts.

Here is an example:

[Image Removed]

Also, depending on the orientation of a triangle in the UV space and the deformation’s “direction”, the tangent will move more or less from one triangle to another even if the deformation is similar, which causes unnatural tangent deformation.

I previously mentioned that Houdini was doing a great job with groom binding, so I looked at what they do, and (after reaching out to them to have more details on their method) I could reproduce the math behind the Attribute Reorient node (which is the core of the method).

Here are the details of the implementation:

First step is to compute a basis on each point of the geometry (much like tangents, but necessitate both rest and deformed geometries, and not on UV vertices):

  • compute normals on points of both geometries (smooth normals)
  • retrieve connected edges and rotate them to align the normals (from rest to deformed)
  • compute the angles between rest and deformed around the deformed normal axis
  • average those angles to compute a mean rotation for this point (around the deformed normal axis)

Then the second step is the actual binding:

  • use the 3 points of the triangle on which the hair is and interpolate the 3 rotations based on the barycentric coordinates of the hair
  • apply this rotation (+ the translation difference of the root point) to all points of the hair

Attached is the VEX code of the first part, computing a rotation per-point.

What do you think of it ?

Would this be an acceptable solution to replace the current binding algorithm ?

How complex do you think it would be to implement in engine ?

Don’t hesitate if you have any questions about it :slight_smile:

Thanks a lot,

Maxime

[Attachment Removed]

Hi Maxime,

thank you for the feedback and trying to solve this on your side.

I’m not fully clear on how easy it would be to implement this. In particular this part:

> retrieve connected edges and rotate them to align the normals (from rest to deformed)

I believe this is the non-trivial part, as I don’t think we have edge information available at runtime. I know some system use some (implicit?) form of this (e.g.: the blendshape normal recompute pass use some kind of edge info IIRC), but this might requires some non trivial change.

/Charles.

[Attachment Removed]

Hi Maxime,

Maybe this is doable indeed. You can try on your side to check if that’s feasable.

Another alternative that you could investigate is using deformer graph for having your own logic for deforming a groom. I don’t recall the state in 5.6, nor if it would be stable enough/have all the functionality for doing what you want (in particular I don’t know the state of geo cache in deformer graph), but you could technically do you own deformation there.

/Charles.

[Attachment Removed]

Ah that’s great to hear! If you have some code change to share, I’m happy to have a look! Thank you :slight_smile:

/Charles.

[Attachment Removed]

Thank you for sharing!

I tried the change and it seems to behave correctly in the cases I tested. However it adds a non-trivial cost to the interpolation pass.

On simple profiling on our test map, the cost went from 2.7ms to 4.0ms, which is pretty steep. I took a stab at optimizing it and could get it down to 3.2ms. The step is still a bit too steep to integrate and enable it by default. As mentioned, we are likely going to change how binding works overall, so we will see when we go back to this if that method make sense.

I attached the optimized version in case you are interested.

Again thank you for sharing!

/Charles.

[Attachment Removed]

Hello Charles,

Thanks for the answer !

Indeed, implementing this method would need non-trivial changes as more buffers are needed and mainly a kind of “pre-pass” per-geometry to compute the data before the hair interpolation pass.

About the point’s connected edges, I guess they could be pre-computed early (when creating the dedicated triangle list for example) and put in a specific buffer. They don’t change at runtime in theory (that would be a constraint anyway).

This could be 2 buffers:

  • one containing the list of connected points per-point
  • one being the bridge to know where to look in the first one, where we can pack the offset and the number of values to read for each point.

Thanks !

Maxime

[Attachment Removed]

Hello !

Adding new buffers and all seem quite complex to go try this on my side unfortunately.

About deformers, I remember testing and having big limitations so I don’t think I can go this way (in 5.6 at least).

Instead, I thought about implementing a simpler version of the algorithm relying on a single triangle’s points only, ignoring other edges of other triangles.

This way I could implement it directly in the hair interpolation shader with the triangle data already accessible.

And results are quite good !

Less smooth than the original, but can be enough in the short term, until houdini’s algorithm is properly implemented in Unreal.

Would this be something Epic can take on ?

It would be a great improvement to groom binding, without noticeable performance impact I believe.

Thanks !

Maxime

[Attachment Removed]

Yes ! Here is my implementation in HairStrandsInterpolation.usf.

I hope this will help you make the real implementation soon :slight_smile:

Thanks !

Maxime

[Attachment Removed]

Hello !

Thanks for that optimization, I’ll take a look :slight_smile:

It’s expected to be not optimized tough, as I did an implementation with what I had easily access to.

The real algorithm is supposed to run once per geometry points, what I did is running once per strands ^^’

Thanks !

Maxime

[Attachment Removed]