Loading...
Loading...
Drive ARKit-blendshape-rigged head/face models in Unity with the wearer's facial expressions on Meta Quest via Meta Movement SDK (face tracking + A2E). Use when a user has an FBX with the 52 ARKit blendshapes (any prefix, _L/_R suffixes) and wants it to animate from face tracking on Quest Pro / Quest 3 / Quest 3S.
npx skill4agent add meta-quest/agentic-tools hz-unity-face-trackingOVRCustomFaceOVR_INTERNAL_CODE_L_ReyeBlink_LjawOpenmouthSmile_RblendShape2.eyeBlink_LPackages/manifest.jsoncom.meta.xr.sdk.coreOVRFaceExpressionsOVRCustomFacecom.meta.xr.sdk.movementAssets/Oculus/OculusProjectConfig.assetfaceTrackingSupport: 12eyeTrackingSupport: 1Assets/Plugins/Android/AndroidManifest.xml<uses-feature android:name="oculus.software.face_tracking" android:required="false" /><uses-permission android:name="com.oculus.permission.FACE_TRACKING" /><uses-permission android:name="android.permission.RECORD_AUDIO" />oculus.software.eye_trackingcom.oculus.permission.EYE_TRACKINGOVRManager.FaceTrackingDataSourcesAudiometa_update_android_manifestARKitOVRCustomFacereferences/ARKitOVRCustomFace.csOVRFaceExpressionsSkinnedMeshRendererARKitOVRCustomFaceReset()FaceExpressionsOVRFaceExpressionsARKitEyeGazeBlendshapeDriverOVRFaceExpressions.EyesLook*OVREyeGazeeyeLook*ARKitEyeGazeBlendshapeDriverOVREyeGazeTrackingMode = HeadSpaceCenterEyeAnchoreyeLookUp/Down_{L,R}eyeLookIn/Out_{L,R}LateUpdateARKitOVRCustomFaceUpdateeyeLook*OVREyeGazeApplyRotation = trueCenterEyeAnchorLeftEyeGazeRightEyeGazeOVREyeGazeEye = LeftRightTrackingMode = HeadSpaceApplyPosition = falseApplyRotation = trueConfidenceThreshold = 0.5ARKitEyeGazeBlendshapeDriverARKitOVRCustomFaceleftEyerightEyereferenceFrameCenterEyeAnchormaxAngleDegsmoothingreferences/ARKitOVRCustomFace.csAssets/Scripts/ARKitOVRCustomFace.csMapBlendshapes()SkinnedMeshRenderer.sharedMesh.GetBlendShapeName(i).OVRFaceExpressions.FaceExpression.MaxProject Settings → Meta XR → Face Tracking Support = Supported
Project Settings → Meta XR → Eye Tracking Support = Supported (if needed)meta_update_android_manifestScene Hierarchy
├── OVRCameraRig (from meta_add_camerarig)
│ └── (add) OVRFaceExpressions component
└── YourHeadModel
└── ...SkinnedMeshRenderer GO...
├── SkinnedMeshRenderer (existing)
└── (add) ARKitOVRCustomFace component
└── FaceExpressions = the OVRFaceExpressions ref
└── retargetingType = Custom (set automatically by base when overriding)
└── Mappings[] = auto-filled on Reset()
└── BlendShapeStrengthMultiplier = 100 (default; OVR weights are 0–1, mesh wants 0–100)SkinnedMeshRendererARKitOVRCustomFaceOVRCameraRigMapBlendshapes()Reset()OnValidate()Mappingsgo.GetComponent<ARKitOVRCustomFace>().MapBlendshapes();Unity_RunCommandGameObject.Find("YourHeadModel")
.GetComponentInChildren<SkinnedMeshRenderer>().gameObject
.SendMessage("MapBlendshapes", SendMessageOptions.RequireReceiver);ARKitOVRCustomFaceMappings.LengthSkinnedMeshRenderer.sharedMesh.blendShapeCountMapBlendshapes()mapped X/N blendshapes| Symptom | Cause / fix |
|---|---|
| Mesh blendshape names don't follow ARKit convention. Verify with |
| Face is frozen | |
| Mouth doesn't move when speaking | A2E disabled. Enable Audio under |
| Eyes don't blink | Mesh's eyelid shapes aren't named |
| Eyes don't move (look around) | You're relying on |
| Eye gaze in wrong direction | Note the deliberate ARKit ↔ OVR swap: ARKit's |
| Weights look half-strength or clamped | |
| Base method is |
| Mesh changed since mapping was generated. Re-run |
ARKitTablebrowInnerUpcheekPuffmouthFunnelmouthPuckermouthRollLowermouthRollUpperMapBlendshapesSlothARKitFaceDrivereyeLookIn/OutmouthCloseLipsTowardtongueOutTongueOutSKILL.mdreferences/ARKitOVRCustomFace.csOVRCustomFacereferences/ARKitEyeGazeBlendshapeDriver.cseyeLook*MonoBehaviourOVRFaceExpressions[expr]OVRCustomFaceOVRCustomFaceRetargetingType.ARKitBlendshapes#if OVR_INTERNAL_CODEOVRCustomFaceRetargetingType.CustomGetCustomBlendShapeNameAndExpressionPairs