Skip to main content

On Sale: GamesAssetsToolsTabletopComics
Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

You need to use BVH to create a skeleton in the rest position, then you move the skeleton GameObject to align with the rest position of the model, then you use MeshSkinner to apply the skeleton to the model. The problem here is that the BVH rest position has the arms straight down but the model rest position is in T-pose.

In the example below I rearranged the arms of the model you linked to so they became a child of the shoulder GameObject and then I rotated and repositioned the shoulders so the model was no longer in T-pose. This was done in Unity with "dummy_obj.obj" (didn't use Blender or something else). Now the rest pose of the BVH was roughly the same as the rest pose of the model and I could align the skeleton with the model.

The arrangements was made in Unity's play mode with a debug BVH skeleton (xray turned on). After everything looked good I copied the root "dummy_obj" and stopped play mode. The model was reset, I deleted it and then pasted the arranged version. Now when I enter play mode the model is aligned with the skeleton created by BVH.

Finally I call BVH's static animateSkeleton() helper method to animate the thing. I used "Full-body Walking" from the page you linked to (notch-format.bvh), it was tiny so I scaled it up by 100 to fit the model. I imported the BVH at 10 fps and looping between frame -36 and -10 with an extra loop keyframe added produced a seamless walking animation. However, due to the rest pose of the BVH the MeshSkinner incorrectly thinks part of the model's hip is part of the hand bone. It might be possible to tweak this issue away in MeshSkinner before the work call, but best would be to find .bvh files that have the skeleton rest positions in T-pose or A-pose.

BVH bvh = new BVH("C:/temp/notch-format.bvh", -10);
bvh.scale(100);
GameObject skeleton = bvh.makeDebugSkeleton(animate:false, xray:true);

MeshSkinner ms = new MeshSkinner(GameObject.Find("dummy_obj"), skeleton);
ms.workAndFinish();

Animation a = BVH.animateSkeleton(skeleton, bvh.makeAnimationClip(-36, -10, true));


Depending on how well you fit the skeleton inside the model you might want to instruct MeshSkinner to not "ignoreJointsUntilInsideMesh". Below is a version of my example code with makeSkeleton() instead of makeDebugSkeleton(). I had to set ignoreJointsUntilInsideMesh=false because when makeDebugSkeleton() is called with a jointSize!=0 meshes are created to visualize the joints, causing makeSkeleton() to look different. I've made a note to try to fix this if I update MeshSkinner (will make the work routine not take meshes on the skeleton GameObjects into account).

BVH bvh = new BVH("C:/temp/notch-format.bvh", -10);
bvh.scale(100);
GameObject skeleton = bvh.makeSkeleton();  //changed line
MeshSkinner.DESTROY_ORIGINAL_MESH_RENDERER = 1;  //new line
MeshSkinner ms = new MeshSkinner(GameObject.Find("dummy_obj"), skeleton);
ms.workAndFinish(ignoreJointsUntilInsideMesh:false);  //changed line
Animation a = BVH.animateSkeleton(skeleton, bvh.makeAnimationClip(-36, -10, true));

While testing with Unity 2019.1 it seems like an old quirk have been fixed in Unity now, creating "SkinnedVersion" GameObjects is no longer needed. That's why I'm setting DESTROY_ORIGINAL_MESH_RENDERER to 1 above, it's not needed but should free up some resources while the game's running.

Another issue I noticed with Unity 2019.1 is that system locale matters now (coutry number formats, "comma instead of period"). Thanks to this BVH could throw FormatException when parsing numbers. Hopefully this is just something Unity has overlooked and will revert, I don't see it as a good thing when code don't run the same on different computers simply because comma is expected instead of a period. I'll take the possibility into account if I update BVH but in the meantime this can be fixed by defining the desired culture in your code (only needs to be done once, before the BVH class is used):

System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");

Thank you for the quick and detailed answer :)

It worked nicely, thank you very much!