This section provides comprehensive information on integrating and handling facial expressions and lipsync within your web applications using the convai-web-sdk.
Initialization
To kickstart facial expression functionality, initialize the ConvaiClient with the necessary parameters. The enableFacialData flag must be set to true to enable facial expression data.
faceModel : 3 is standard and actively maintained.
Receiving Viseme Data
Retrieve viseme data by incorporating the provided callback. The example code demonstrates how to handle and update facial data.
const [facialData,setFacialData] =useState([]);constfacialRef=useRef([]);convaiClient.current.setResponseCallback((response) => {if (response.hasAudioResponse()) {let audioResponse =response?.getAudioResponse();if (audioResponse?.getVisemesData()?.array[0]) {//Viseme datalet faceData =audioResponse?.getVisemesData().array[0];//faceData[0] implies sil value. Which is -2 if new chunk of audio is recieve.if (faceData[0] !==-2) {facialRef.current.push(faceData);setFacialData(facialRef.current); } }}
Modulating Morph Targets
Utilize the useFrame hook from react-three-fiber to modulate morph targets based on the received facial data.
import {OvrToMorph} from'convai-web-sdk'constblendShapeRef=useRef([]);constcurrentBlendFrame=useRef(0);useFrame((state, _delta) => {// Initiate blendshapesif(client?.facialData.length>0){// OvrToMorph is required for mapping reallusion morphsOvrToMorph(client?.facialData[currentBlendFrame.current],blendShapeRef); }if (currentBlendFrame.current <=blendShapeRef?.current?.length) {// Logic to adjust morph targets based on facial datacurrentBlendFrame.current +=1; }});
In addition to facial expressions, the convai-web-sdk allows developers to modulate bone adjustments for specific facial features. Receive bone adjustments for "Open_Jaw," "Tongue," and "V_Tongue_Out" and apply them to your character as demonstrated below:
// If current viseme value is Open_JawcharacterRef.current.getObjectByName("CC_Base_JawRoot").setRotationFromEuler(jawRotation);// The jaw rotation value can be modulated using THREE.lerp().// exampleconstjawRotation=newTHREE.Euler(0,0,1.57);// Here 1.57 is the base value for closed Jaw.jawRotation.z = THREE.MathUtils.lerp(jawRotation.z,1.57 + blendShapeRef.current[currentBlendFrame.current-1][blend]*0.2,0.8);
These code examples are specific to reallusion characters.
Handling 100fps Animation
Implement throttling using lodash to ensure smooth animations at 100fps. The provided example demonstrates how to maintain a consistent animation frame rate.
constthrottledUpdate=_.throttle(updateAnimation,10);const [tick,setTick] =useState(true);constupdateAnimation= () => {setTick((tick) => {if (tick) {return tick; }returntrue; });requestAnimationFrame(throttledUpdate);};// Start the animation loop when the component mountsuseEffect(() => {requestAnimationFrame(throttledUpdate);// Clean up the animation loop when the component unmountsreturn () => {cancelAnimationFrame(throttledUpdate); };}, []);// Use the frame hook to update animationsuseFrame((state, _delta) => {if (tick) {// Logic to get viseme data and alter morph targets accordinglysetTick(false); }});
Note: Throttle function is not 100% accurate
Handling 100fps Edge Cases
Throttle accuracy may lead to edge cases. Implement the clock setup and handle both above and below 100fps scenarios using the elapsed time.