I thought about testing WebXR generic hand models in Babylon.js and downloaded them from here:
WebXR Generic Hand models
I only had to set rigMapping correctly and generate a new UV map in Blender using Project from View (Bounds) so that the babylon hand node material would work somewhat correctly.
For comparison, I put the generic hand model on the left hand and Babylon’s hand model on the right hand. The previously mentioned issues can be seen in these images:
Here’s both imported in Blender:
The generic hand is the same one used in other 3D engines, and it works really well. I suggest adding it as the default hand model in Babylon.js. Or make it an easy option.
Here’s the workaround for anyone interested:
import handsShaderUser from "../../assets/json/handsShaderUser.json"; // babylon.js hand mesh shader
import lHandRhsGlb from "../../assets/glb/left_new-uv.glb";
import rHandRhsGlb from "../../assets/glb/right_new-uv.glb";
protected async importHandMeshes(scene: Scene) {
const handMaterial = NodeMaterial.Parse(handsShaderUser, scene);
handMaterial.name = "handMaterial";
const tipFresnelColorBlock = handMaterial.getInputBlockByPredicate((b) => b.name === "tipFresnelColor");
const fingerColorBlock = handMaterial.getInputBlockByPredicate((b) => b.name === "fingerColor");
if (tipFresnelColorBlock && fingerColorBlock) {
tipFresnelColorBlock.value = fingerColorBlock.value;
}
this.leftHandMesh = await this.importHandMesh(scene, lHandRhsGlb, "leftHand", handMaterial);
this.rightHandMesh = await this.importHandMesh(scene, rHandRhsGlb, "rightHand", handMaterial);
}
protected async importHandMesh(scene: Scene, handGlb: any, handName: string, handMaterial: NodeMaterial | StandardMaterial): Promise<Mesh> {
const handImportResult = await SceneLoader.ImportMeshAsync("", "", handGlb, scene);
const handMesh = handImportResult.meshes[0] as Mesh;
handMesh.name = handName;
handMesh.scaling = new Vector3(-1, 1, 1);
// dispose and replace imported material
const handMeshChild = handMesh.getChildMeshes(true)[0];
handMeshChild.material?.dispose();
handMeshChild.material = handMaterial;
return handMesh;
}
const handTrackingFeature = featuresManager.enableFeature(WebXRFeatureName.HAND_TRACKING, "latest", {
xrInput: xr.input,
jointMeshes: {
invisible: true,
disableDefaultHandMesh: true,
handMeshes: {
right: this.rightHandMesh?.getChildMeshes(true)[0],
left: this.leftHandMesh?.getChildMeshes(true)[0],
},
// WebXR standard hand rig mapping
rigMapping: {
right: [
"wrist",
"thumb-metacarpal",
"thumb-phalanx-proximal",
"thumb-phalanx-distal",
"thumb-tip",
"index-finger-metacarpal",
"index-finger-phalanx-proximal",
"index-finger-phalanx-intermediate",
"index-finger-phalanx-distal",
"index-finger-tip",
"middle-finger-metacarpal",
"middle-finger-phalanx-proximal",
"middle-finger-phalanx-intermediate",
"middle-finger-phalanx-distal",
"middle-finger-tip",
"ring-finger-metacarpal",
"ring-finger-phalanx-proximal",
"ring-finger-phalanx-intermediate",
"ring-finger-phalanx-distal",
"ring-finger-tip",
"pinky-finger-metacarpal",
"pinky-finger-phalanx-proximal",
"pinky-finger-phalanx-intermediate",
"pinky-finger-phalanx-distal",
"pinky-finger-tip"
],
left: [
"wrist",
"thumb-metacarpal",
"thumb-phalanx-proximal",
"thumb-phalanx-distal",
"thumb-tip",
"index-finger-metacarpal",
"index-finger-phalanx-proximal",
"index-finger-phalanx-intermediate",
"index-finger-phalanx-distal",
"index-finger-tip",
"middle-finger-metacarpal",
"middle-finger-phalanx-proximal",
"middle-finger-phalanx-intermediate",
"middle-finger-phalanx-distal",
"middle-finger-tip",
"ring-finger-metacarpal",
"ring-finger-phalanx-proximal",
"ring-finger-phalanx-intermediate",
"ring-finger-phalanx-distal",
"ring-finger-tip",
"pinky-finger-metacarpal",
"pinky-finger-phalanx-proximal",
"pinky-finger-phalanx-intermediate",
"pinky-finger-phalanx-distal",
"pinky-finger-tip"
]
},
},
} as IWebXRHandTrackingOptions) as WebXRHandTracking;
The uv-modified generic hand models and babylon hand node material:
webxr generic hand in babylonjs.zip (130.8 KB)