Search Unity

Official Uniform Distribution with Skinned Mesh Sampling

Discussion in 'Visual Effect Graph' started by PaulDemeulenaere, Oct 26, 2021.

  1. PaulDemeulenaere

    PaulDemeulenaere

    Unity Technologies

    Joined:
    Sep 29, 2016
    Posts:
    154
    Hello,

    I would like to share an interesting use case of SampleMesh and it’s probably a good opportunity to explain how the point cache tool is implemented behind the curtain.

    You may have already tried to spawn uniformly particles over a mesh or skinned mesh in the Visual Effect Graph. The naïve approach consists in a random selection of the triangle index, then, picking two other floats to spawn particles over the chosen triangle. This is also the internal behavior of the block Set Position (Mesh) with the spawn mode Random.

    The problem with this solution is that since we don’t consider the triangle area, it’s highly probable to choose one of the small triangles in the first selection phase. This leads to an uneven repartition of spawned particles.
    skinning_simple.gif

    There are more triangles on the head than the rest of the body. The problem and the reason become more obvious looking at the wireframe of this scene.
    _skinning_wireframe_2.gif

    We can avoid this issue by having a mesh with all triangles of the same size, but this isn’t an easy or reasonable constraint to respect during mesh modelling.

    With the point cache bake tool, you can eventually bake a list of positions uniformly distributed on a static mesh, but this won’t be appropriate if you are using a sampling of a skinned mesh directly in Visual Effect Graph.

    Actually, we can use the same trick as the point cache bake tool but applicable to a skinned mesh.

    First thing first, let me describe how the point cache bake tool is able to generate a uniform set on vertices on any uneven mesh surface:
    1. Extract vertices and indices from a mesh
    2. Compute area of all triangles
    3. Compute the accumulated prefix sum array of the area of all triangles (the first element of this array will be the area of the first triangle, the following one will be the accumulated area of the first and second triangle, the last element will be the total area of the surface of the mesh)
    4. For every sample to generate:
      1. Pick a random number between 0 and total area
      2. Find the corresponding triangle index in accumulated sum array (using a dichotomy)
      3. Pick two random floats to generate an uniform barycentric coordinate
      4. Interpolate corresponding triangle vertices using previously computed barycentric coordinates, append this result.
    Back on our issue, this kind of process can’t be easily done in real time but we can store the result of the computed barycentric coordinate and triangle index in a GraphicsBuffer and use this indirection to interpolate skinned vertices. In other words, we can apply the same algorithm but replace step 4.4. by a simple storage of triangle index and barycentric coordinate in a list to build a buffer later.

    Instead of using random triangle index selection, we will use this indirection to sample the skinned mesh.
    skinning_baked.gif

    Some details about this sample:
    • The VFXMeshSamplingHelper has been widely inspired by the PointCacheBakeTool.Mesh implementation
    • The VFXMeshSamplingHelper static class provides three public functions ComputeDataCache, GetNextSampling & GetInterpolatedVertex
    • The basic usage of this class is :
      Code (CSharp):
      1. var meshData = VFXMeshSamplingHelper.ComputeDataCache(mesh);
      2. var rand = new System.Random(1234);
      3. var bakedSampling = new TriangleSampling[SampleCount];
      4. for (int i = 0; i < 1000; ++i)
      5. {
      6.     bakedSampling[i] = VFXMeshSamplingHelper.GetNextSampling(meshData, rand);
      7. }
      8.  
    • A custom monobehaviour called UniformBaker stores the list of baked positions in scene to build a GraphicsBuffer on the fly in runtime and assign it to the VFX. The baking process is only applied in editor.
    This solution isn’t perfect because it assumes the skinning doesn’t change too much the area of the triangles. Furthermore, I choose to store the computed barycentric coordinate, but there is a reason for this: during the baking of barycentric coordinates, we can also interpolate the vertices and do special sorting of the baked coordinates, which is the purpose of the function GetInterpolatedVertex.
    skinning_baked_order.gif

    Here, I arbitrarily choose to order the baked positions based on the distance of the first channel of texture coordinates with the center of the main texture (See UniformBakerCustomOrder)
    Code (CSharp):
    1. bakedSampling = bakedSampling.OrderBy(o =>
    2. {
    3.     var vertex = VFXMeshSamplingHelper.GetInterpolatedVertex(meshData, o);
    4.     var texCoord = vertex.uvs[0];
    5.     return (texCoord - new Vector4(0.5f, 0.5f, 0.5f, 0.5f)).sqrMagnitude;
    6. }).ToArray();
    7.  
    In closing, we are aware this kind of approach can be generalized, for instance, the point cache bake tool can optionally output triangle indices and barycentric coordinates instead of interpolated vertices. Meanwhile, feel free to take inspiration from this use case and implement your own custom solution.

    I hope this kind of trick can inspire you for a more advanced effect, we are always glad to see your creativity.
    You can find all sources and assets used in this sample on this git repository (you can clone it or download a zip), it can be opened with Unity 2021.2.0b16 or greater.
    Feel free to share your impressions and don’t hesitate if you have any additional questions or if something isn’t clear.

    skinning_sum_up.gif
     
    Last edited: Oct 27, 2021
  2. jiraphatK

    jiraphatK

    Joined:
    Sep 29, 2018
    Posts:
    300
    Thanks. Wish this could be out of the box tool.
     
    koirat, merpheus and PaulDemeulenaere like this.
  3. dstrictxrlab

    dstrictxrlab

    Joined:
    Oct 26, 2020
    Posts:
    2
    omg this is so awesome!!! THanks!!!!
     
  4. nehvaleem

    nehvaleem

    Joined:
    Dec 13, 2012
    Posts:
    436
    Why there is
    Code (csharp):
    1. #if UNITY_EDITOR
    in the baker script? It makes it unusable in the built player. Is there any reason for that?
     
  5. PaulDemeulenaere

    PaulDemeulenaere

    Unity Technologies

    Joined:
    Sep 29, 2016
    Posts:
    154
    In this example, I clearly separated the bake process which is done in editor and fill the serialized property m_BakedSampling from the runtime behavior which is kept minimal. The runtime is only in charge of filling a GraphicsBuffer.
    There are several way to use these suggested helpers.
    However, I would recommended to avoid the usage of VFXMeshSamplingHelper in runtime, the function ComputeDataCache is processing the area of all triangles of a mesh, it can be a really costly operation.
     
    izzynab likes this.
  6. nehvaleem

    nehvaleem

    Joined:
    Dec 13, 2012
    Posts:
    436
    Got it, thanks for the explanation! Thus, I must admit that it would be totally awesome if such behavior was available by default inside the vfx graph itself without the need of baking another data.
     
  7. izzynab

    izzynab

    Joined:
    May 7, 2021
    Posts:
    56
    Thanks @PaulDemeulenaere for this awesame explanation.
    I have a question about usage of this code. Can I use it in my Unity Asset Store package I am developing, to help create better-looking effects?
    There is no license or anything here, so I would like to make sure that its ok that I use this code commercially.
     
    Last edited: Jul 19, 2023
  8. PaulDemeulenaere

    PaulDemeulenaere

    Unity Technologies

    Joined:
    Sep 29, 2016
    Posts:
    154
    Hello,
    Thanks for the notification, I'll update the GitHub repository using the Creative Commons License.

    From my point of view, nothing really deserve to be copyrighted in this code. I would expect this to be used as boiler plate for a more specific integration.

    Meanwhile, feel free to reuse this code even in a commercial project, you don't need to credit or link the source but, if you are copy/pasting some sections, I would recommend to link this forum thread: In case of a bug or someone trying to dig in your source code, it can help to understand the algorithm intention.
     
    Last edited: Jul 21, 2023
  9. Dagos32

    Dagos32

    Joined:
    Aug 22, 2015
    Posts:
    6
    Thank you for the code, it worked perfectly in 2022.3.11!

    One question - do you think it would be possible to sample multiple skinned meshes at once? i.e. when you have one mesh for a character's body and another for their clothes - the simplest solution I can think would be to just duplicate the skinnedmesh and buffer properties, but in our game we can have a bunch of separate skinned meshes attached, so that could quicky get out of hand