VRIK is a high speed full body solver dedicated to animating VR avatars. The solver was originally developed for the game "Dead and Buried" by Oculus Studios.
A detailed description of the inner workings of VRIK can be found in this article originally published in Oculus Developer Blog.
Getting started:
Add VRIK to your character root. If it is a Humanoid, References will be automatically filled in using the Animator. If not, the bones used by the solvers will have to be manually assigned to the "References".
Move the camera to the avatar's eyes, move hand controllers to the avatar's hands as if they were worn by the avatar.
Make duplicates of the avatar's head and hand bones.
Parent the duplicates to the camera/hand controllers.
Assign the duplicates as Head/Hand Targets in VRIK.
VRIK samples the pose of the avatar at Start to find out which way to bend the limbs. Make sure that the elbow/knee bones are rotated so that the limbs are slightly bent in their natural bending directions, they must not be completely straight.
Examples of VRIK setup for Oculus and SteamVR can be found in "Plugins/RootMotion/FinalIK/_Integration". Oculus/SteamVR utilities packages from the Asset Store are required to run these demos.
Performance:
VRIK is about 2-3 times faster than FullBodyBipedIK. In Dead and Buried 2, it was used on 6 avatars simultaneously visible on screen and running on Oculus Quest hardware. Since Final IK v1.9 VRIK has a 3-level LOD system with level 1 providing roughly 30% gain and level 2 solver disabled with only the root position and rotation updated if the built-in locomotion solver was used.
Adding VRIK component in run-time:
void LateUpdate () {
var ik = gameObject.AddComponent<VRIK>();
ik.AutoDetectReferences();
}
Using VRIK with Rotation Limits:
VRIK has its own set of built-in constraints and RotationLimits can not be used in the solving process. It is possible however to apply RotationLimits on top of VRIK for instance to make sure that the hand bones do not bend unnaturally beyond reasonable limits. To do this, we would have to disable the rotation limits in Start to take control of their updating, then update them after VRIK using the VRIK.solver.OnPostUpdate delegate::
public VRIK ik;
public RotationLimit[] rotationLimits;
void Start() {
foreach (RotationLimit limit in rotationLimits) {
limit.enabled = false;
}
ik.solver.OnPostUpdate += AfterVRIK;
}
privatevoid AfterVRIK() {
foreach (RotationLimit limit in rotationLimits) {
limit.Apply();
}
}
Calibration:
VRIK offers 2 calibration methods:
Basic Head & Hands
Calibrates the avatar to the camera and hand controllers. That method is independent of avatar bone orientations and only requires position and rotation offsets to be defined for the head and hands. See the "VRIK (Basic Head & Hands Calibration) demo for more info and examples.
Full Body
Calibrates the avatar to the camera, hands and additional trackers on the body and ankles. Please take a look at the "VRIK (Calibration)" demo on how to use the VRIKCalibrator for full body tracking.
If you only need to calibrate the size of the avatar, it is easiest to do so by having the player stand up straight, then comparing the height of the head target to the height of the avatar's head bone:
VRIK supports 2 modes of locomotion - Procedural (legacy) and Animated.
Procedural (legacy)
The built-in procedural locomotion was developed in the early days of VR and designed for foot shuffling around a few square meters space. It does not work well for room-scale or thumbstick locomotion as it is not responsive enough and tends to fall behind of the camera when moving fast. Procedural locomotion locks the feet to "footsteps", that will be moved procedurally to make the character catch up to the HMD.
Animated
Animated locomotion module uses a simple 8-directional strafing animation blend tree to make the character follow the horizontal direction towards the HMD by root motion and scripted transformation.
The modulre requires having an enabled Animator with an Animator Controller that is compatible with the module, such as the "VRIK Animated Locomotion" controller (Humanoid) provided in the package. However it does not limit you to use that controller, it can manage any animation setup as long as it has and uses the required Animator parameters:
VRIK_Turn - Used for turning on spot. The idle state must be a 1D blend tree with looping turn-on-spot animations, setup like the "VRIK_Idle" state in the "VRIK Animated Locomotion" Animator Controller.
VRIK_Horizontal - Drives the lateral motion, must be the first parameter used in the "VRIK_Locomotion" blend tree.
VRIK_Vertical - Drives the forward/backward motion, must be the second parameter used in the "VRIK_Locomotion" blend tree.
VRIK_IsMoving - Parameter for transitioning between the "VRIK_Idle" and "VRIK_Locomotion" states.
VRIK_Speed - Multiplier for the "VRIK_Locomotion" state's speed.
Additional requirements:
Transition from "VRIK_Locomotion" to "VRIK_Idle" must be named as "VRIK_Stop".
After you have finished setting up all the motion clips in the "VRIK_Locomotion" blend trees, click on "Compute Positions" and select "Velocity XZ" to match horizontal and vertical parameters to animation velocities.
Extending the Animator Controller
Make a duplicate of the "VRIK Animated Locomotion" Animator Controller, so your work would not be lost the next time you update Final IK. You can also just copy the "VRIK Animated Locomotion" sub-state machine into your own Animator Controller.
Can add running/sprinting animations to the "VRIK_Locomotion blend tree.
- Can add crouching animations to the "VRIK_Locomotion" blend tree if you add an additional "Crouch" parameter and convert motions to blend trees using that parameter. The value of Crouch can be calculated using the height of the head target from the root of the avatar.
Networking Animated locomotion
When applying Animated locomotion to remote instances of networked avatars, will have to sync the following objects/data:
Head target position/rotation
Hand target positions/rotations
Y position of the avatar
Horizontal position and rotation of the avatar will be applied by the locomotion module automatically based on the above data.
It is important to interpolate the synced data to ensure VRIK has smooth input velocities.
Component variables:
VRIK.fixTransforms - if true, will fix all the Transforms used by the solver to their initial state in each Update. This prevents potential problems with unanimated bones and animator culling with a small cost of performance
VRIK.references - bone mapping. Right-click on the component header and select 'Auto-detect References' of fill in manually if not a Humanoid character. Chest, neck, shoulder and toe bones are optional. VRIK also supports legless and armless characters. If you do not wish to use legs/arms, leave all leg/arm references empty.
Solver variables:
VRIK.solver.IKPositionWeight - master weight of the solver.
VRIK.solver.LOD - LOD 0: Full quality solving. LOD 1: Shoulder solving, stretching plant feet disabled, spine solving quality reduced. This provides about 30% of performance gain. LOD 2: Culled, but updating root position and rotation if locomotion is enabled.
VRIK.solver.plantFeet - if true, will keep the toes planted even if head target is out of reach, so this can cause the camera to exit the head if it is too high for the model to reach. Enabling this increases the cost of the solver as the legs will have to be solved multiple times.
Spine variables:
VRIK.solver.spine.headTarget - the head target. This should not be the camera Transform itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the head bone. The best practice for setup would be to move the camera to the avatar's eyes, duplicate the avatar's head bone and parent it to the camera. Then assign the duplicate to this slot.
VRIK.solver.spine.positionWeight - positional weight of the head target. Note that if you have nulled the headTarget, the head will still be pulled to the last position of the headTarget until you set this value to 0.
VRIK.solver.spine.rotationWeight - rotational weight of the head target. Note that if you have nulled the headTarget, the head will still be rotated to the last rotation of the headTarget until you set this value to 0.
VRIK.solver.spine.headClampWeight - clamps head rotation. Value of 0.5 allows 90 degrees of rotation for the head relative to the headTarget. Value of 0 allows 180 degrees and value of 1 means head rotation will be locked to the target.
VRIK.solver.spine.minHeadHeight - minimum height of the head from the root of the character.
VRIK.solver.spine.useAnimatedHeadHeightWeight - allows for more natural locomotion animation for 3rd person networked avatars by inheriting vertical head bob motion from the animation while head target height is close to head bone height
VRIK.solver.spine.useAnimatedHeadHeightRange - if abs(head target height - head bone height) < this value, will use head bone height as head target Y.
VRIK.solver.spine.useAnimatedHeadHeightBlend - falloff range for the 'Use Animated Head Height Range' effect above. If head target height from head bone height is greater than useAnimatedHeadHeightRange + animatedHeadHeightBlend, then the head will be vertically locked to the head target again.
VRIK.solver.spine.pelvisTarget - the pelvis target (optional), useful for seated rigs or if you had an additional tracker on the backpack or belt are. The best practice for setup would be to duplicate the avatar's pelvis bone and parenting it to the pelvis tracker. Then assign the duplicate to this slot.
VRIK.solver.spine.pelvisPositionWeight - positional weight of the pelvis target. Note that if you have nulled the pelvisTarget, the pelvis will still be pulled to the last position of the pelvisTarget until you set this value to 0.
VRIK.solver.spine.pelvisRotationWeight - Rotational weight of the pelvis target. Note that if you have nulled the pelvisTarget, the pelvis will still be rotated to the last rotation of the pelvisTarget until you set this value to 0.
VRIK.solver.spine.maintainPelvisPosition - how much will the pelvis maintain its animated position?
VRIK.solver.spine.chestGoal - if chestGoalWeight is greater than 0, the chest will be turned towards this Transform.
VRIK.solver.spine.chestGoalWeight - weight of turning the chest towards the chestGoal.
VRIK.solver.spine.chestClampWeight - clamps chest rotation. Value of 0.5 allows 90 degrees of rotation for the chest relative to the head. Value of 0 allows 180 degrees and value of 1 means the chest will be locked relative to the head.
VRIK.solver.spine.rotateChestByHands - the amount of rotation applied to the chest based on hand positions.
VRIK.solver.spine.bodyPosStiffness - determines how much the body will follow the position of the head.
VRIK.solver.spine.bodyRotStiffness - determines how much the body will follow the rotation of the head.
VRIK.solver.spine.neckStiffness - determines how much the chest will rotate to the rotation of the head.
VRIK.solver.spine.moveBodyBackWhenCrouching - moves the body horizontally along -character.forward axis by that value when the player is crouching.
VRIK.solver.spine.maxRootAngle - will automatically rotate the root of the character if the head target has turned past this angle.
VRIK.solver.spine.rootHeadingOffset - angular offset for root heading. Adjust this value to turn the root relative to the HMD around the vertical axis. Usefulf for fighting or shooting games where you would sometimes want the avatar to stand at an angled stance.
Arm variables:
VRIK.solver.leftArm.target - the hand target. This should not be the hand controller itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the hand bone. The best practice for setup would be to move the hand controller to the avatar's hand as it it was held by the avatar, duplicate the avatar's hand bone and parent it to the hand controller. Then assign the duplicate to this slot.
VRIK.solver.leftArm.bendGoal - the elbow will be bent towards this Transform if 'Bend Goal Weight' > 0.
VRIK.solver.leftArm.positionWeight - positional weight of the hand target. Note that if you have nulled the target, the hand will still be pulled to the last position of the target until you set this value to 0.
VRIK.solver.leftArm.rotationWeight - rotational weight of the hand target. Note that if you have nulled the target, the hand will still be rotated to the last rotation of the target until you set this value to 0.
VRIK.solver.leftArm.shoulderRotationMode - different techniques for shoulder bone rotation.
VRIK.solver.leftArm.shoulderRotationWeight - the weight of shoulder rotation.
VRIK.solver.leftArm.shoulderTwistWeight - the weight of twisting the shoulders backwards when arms are lifted up.
VRIK.solver.leftArm.bendGoalWeight - if greater than 0, will bend the elbow towards the 'Bend Goal' Transform.
VRIK.solver.leftArm.swivelOffset - angular offset of the elbow bending direction.
VRIK.solver.leftArm.wristToPalmAxis - local axis of the hand bone that points from the wrist towards the palm. Used for defining hand bone orientation. If you have copied VRIK component from another avatar that has different bone orientations, right-click on VRIK header and select "Guess Hand Orientations" from the context menu.
VRIK.solver.leftArm.palmToThumbAxis - local axis of the hand bone that points from the palm towards the thumb. Used for defining hand bone orientation If you have copied VRIK component from another avatar that has different bone orientations, right-click on VRIK header and select 'Guess Hand Orientations' from the context menu..
VRIK.solver.leftArm.armLengthMlp - use this to make the arm shorter/longer. Works by displacement of hand and forearm localPosition.
VRIK.solver.leftArm.stretchCurve - evaluates stretching of the arm by target distance relative to arm length. Value at time 1 represents stretching amount at the point where distance to the target is equal to arm length. Value at time 2 represents stretching amount at the point where distance to the target is double the arm length. Value represents the amount of stretching. Linear stretching would be achieved with a linear curve going up by 45 degrees. Increase the range of stretching by moving the last key up and right at the same amount. Smoothing in the curve can help reduce elbow snapping (start stretching the arm slightly before target distance reaches arm length). To get a good optimal value for this curve, please go to the 'VRIK (Basic)' demo scene and copy the stretch curve over from the Pilot character.
Leg variables:
VRIK.solver.leftLeg.target - the foot/toe target. This should not be the foot tracker itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the foot/toe bone. If a toe bone is assigned in the References, the solver will match the toe bone to this target. If no toe bone assigned, foot bone will be used instead.
VRIK.solver.leftLeg.bendGoal - the knee will be bent towards this Transform if 'Bend Goal Weight' > 0.
VRIK.solver.leftLeg.positionWeight - positional weight of the toe/foot target. Note that if you have nulled the target, the foot will still be pulled to the last position of the target until you set this value to 0.
VRIK.solver.leftLeg.rotationWeight - rotational weight of the toe/foot target. Note that if you have nulled the target, the foot will still be rotated to the last rotation of the target until you set this value to 0.
VRIK.solver.leftLeg.bendGoalWeight - if greater than 0, will bend the knee towards the 'Bend Goal' Transform.
VRIK.solver.leftLeg.swivelOffset - angular offset of knee bending direction.
VRIK.solver.leftLeg.bendToTargetWeight - if 0, the bend plane will be locked to the rotation of the pelvis and rotating the foot will have no effect on the knee direction. If 1, to the target rotation of the leg so that the knee will bend towards the forward axis of the foot. Values in between will be slerped between the two.
VRIK.solver.leftLeg.legLengthMlp - use this to make the leg shorter/longer. Works by displacement of foot and calf localPosition.
VRIK.solver.leftLeg.stretchCurve - evaluates stretching of the leg by target distance relative to leg length. Value at time 1 represents stretching amount at the point where distance to the target is equal to leg length. Value at time 1 represents stretching amount at the point where distance to the target is double the leg length. Value represents the amount of stretching. Linear stretching would be achieved with a linear curve going up by 45 degrees. Increase the range of stretching by moving the last key up and right at the same amount. Smoothing in the curve can help reduce knee snapping (start stretching the arm slightly before target distance reaches leg length). To get a good optimal value for this curve, please go to the 'VRIK (Basic)' demo scene and copy the stretch curve over from the Pilot character.
Locomotion variables:
VRIK.solver.locomotion.mode - use Procedural (legacy) or Animated locomotion mode.
VRIK.solver.locomotion.weight - used for blending in/out of procedural or animated locomotion.
Procedural locomotion variables:
VRIK.solver.locomotion.footDistance - tries to maintain this distance between the legs.
VRIK.solver.locomotion.stepThreshold - makes a step only if step target position is at least this far from the current footstep or the foot does not reach the current footstep anymore or footstep angle is past the 'Angle Threshold'.
VRIK.solver.locomotion.angleThreshold - makes a step only if step target position is at least 'Step Threshold' far from the current footstep or the foot does not reach the current footstep anymore or footstep angle is past this value.
VRIK.solver.locomotion.comAngleMlp - multiplies angle of the center of mass - center of pressure vector. Larger value makes the character step sooner if losing balance.
VRIK.solver.locomotion.maxVelocity - maximum magnitude of head/hand target velocity used in prediction.
VRIK.solver.locomotion.velocityFactor - the amount of head/hand target velocity prediction.
VRIK.solver.locomotion.maxLegStretch - how much can a leg be extended before it is forced to step to another position? 1 means fully stretched.
VRIK.solver.locomotion.rootSpeed - the speed of lerping the root of the character towards the horizontal mid-point of the footsteps.
VRIK.solver.locomotion.stepSpeed - the speed of moving a foot to the next position.
VRIK.solver.locomotion.stepHeight - the height of the foot by normalized step progress (0 - 1).
VRIK.solver.locomotion.heelHeight - the height offset of the heel by normalized step progress (0 - 1).
VRIK.solver.locomotion.relaxLegTwistMinAngle - rotates the foot while the leg is not stepping to relax the twist rotation of the leg if ideal rotation is past this angle.
VRIK.solver.locomotion.relaxLegTwistSpeed - the speed of rotating the foot while the leg is not stepping to relax the twist rotation of the leg.
VRIK.solver.locomotion.stepInterpolation - tnterpolation mode of the step.
VRIK.solver.locomotion.offset - offset for the approximated center of mass.
VRIK.solver.locomotion.maxAnimationSpeed - maximum locomotion animation speed.
VRIK.solver.locomotion.animationSmoothTime - smoothing time for Vector3.SmoothDamping 'VRIK_Horizontal' and 'VRIK_Vertical' parameters. Larger values make animation smoother, but less responsive.
VRIK.solver.locomotion.standOffset - X and Z standing offset from the horizontal position of the HMD.
VRIK.solver.locomotion.rootLerpSpeedWhileMoving - lerp root towards the horizontal position of the HMD with this speed while moving.
VRIK.solver.locomotion.rootLerpSpeedWhileStopping - lerp root towards the horizontal position of the HMD with this speed while in transition from locomotion to idle state.
VRIK.solver.locomotion.rootLerpSpeedWhileTurning - lerp root towards the horizontal position of the HMD with this speed while turning on spot.
VRIK.solver.locomotion.maxRootOffset - max horizontal distance from the root to the HMD.
VRIK.solver.locomotion.maxRootAngleMoving - max root angle from head forward while moving (ik.solver.spine.maxRootAngle).
VRIK.solver.locomotion.maxRootAngleStanding - max root angle from head forward while standing (ik.solver.spine.maxRootAngle.