├── FacialExpressions.cs ├── FacialExpressions.jpg ├── FacialExpressionsInterface.cs └── README.md /FacialExpressions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class FacialExpressions : MonoBehaviour { 6 | 7 | [Tooltip("Manually drag skin (H_DDS_HighRes) here.")] 8 | public Transform skin; 9 | SkinnedMeshRenderer skinnedMeshRenderer; 10 | 11 | [Tooltip("Manually drag lower teeth (h_TeethDown) here.")] 12 | public Transform teeth; 13 | SkinnedMeshRenderer skinnedMeshRendererTeeth; 14 | 15 | // to be controlled from outside this script: 16 | //int expression; // expressions are listet: 0 (neutral), 1 (happy), 2 (sad), 3 (angry), 4 (fearful), 5 (surprised) 17 | //float intensity = 1; 18 | //int blinkmax = 200; // maximum number of frames between blinks 19 | //int blinkmin = 50; // minimum number of frames between blinks 20 | //float lerpSpeed = 0.12f; 21 | 22 | int BlinkTicker = 0; // counts time since last blink 23 | int expressionLastFrame = 0; // is set to expression after each frame, so that change can be detected at start of each frame 24 | float intensityLastFrame = 1; 25 | float[] BlendshapesCurrent = new float[67]; //current facial expression, smoothly lerps into... 26 | float[] BlendshapesGoal = new float[67];// goal facial expression, which is instantly set to one of the lists below. 27 | 28 | // List of blendshapes for several facial expressions. 29 | // each value represents one facial blendshape (~ facial muscle) which can be controlled individually. 30 | // zero-based array, so last value is Neutral[66]. Value 65 describes opening of mouth. Value 66 describes closing of eyes needed for blinking from this expression 31 | // (this varies b/c a sad expression requires more closing of the eye to reach a closed position than a surprised expression etc.) 32 | float[] Neutral = new float[67] { 33 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0 to 9 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //10 to 19 35 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20 to 29 36 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //30 to 39 37 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40 to 49 38 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50 to 59 39 | 0, 0, 0, 0, 0, 0, 90 //60 to 67. 40 | }; 41 | 42 | float[] Happy = new float[67] { 43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0 to 9 44 | 0, 0, 0, 0, 0, 20, 20, 0, 0, 0, //10 to 19 45 | 0, 0, 0, 0, 0, 0, 30, 0, 80, 80, //20 to 29 46 | 80, 80, 0, 0, 0, 0, 0, 0, 0, 0, //30 to 39 47 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40 to 49 48 | 0, 50, 50, 0, 0, 0, 0, 80, 80, 80, //50 to 59 49 | 80, 0, 0, 0, 0, 20, 80 //60 to 67 50 | }; 51 | 52 | float[] Sad = new float[67] { 53 | 0, 40, 0, 20, 0, 0, 0, 0, 0, 0, //0 to 9 54 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //10 to 19 55 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20 to 29 56 | 0, 0, 80, 80, 0, 0, 0, 0, 0, 0, //30 to 39 57 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, //40 to 49 58 | 40, 0, 0, 80, 80, 20, 20, 50, 50, 50, //50 to 59 59 | 50, 0, 0, 0, 0, 0, 50 //60 to 67 60 | }; 61 | 62 | float[] Angry = new float[67] { 63 | 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, //0 to 9 64 | 0, 0, 0, 20, 20, 0, 0, 0, 0, 0, //10 to 19 65 | 0, 0, 0, 0, 20, 0, 0, 20, 0, 0, //20 to 29 66 | 0, 0, 20, 20, 80, 80, 0, 0, 0, 0, //30 to 39 67 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40 to 49 68 | 0, 60, 60, 0, 0, 0, 0, 0, 0, 0, //50 to 59 69 | 0,100,100,100,100, 40, 70 //60 to 67 70 | }; 71 | 72 | float[] Fearful = new float[67] { 73 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0 to 9 74 | 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, //10 to 19 75 | 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, //20 to 29 76 | 0, 0, 40, 40, 0, 0, 0, 0, 0,100, //30 to 39 77 | 0, 0, 0,100,100, 0, 0,100,100, 0, //40 to 49 78 | 0, 0, 0, 0, 0,100,100,100,100,100, //50 to 59 79 | 100, 0, 0, 0, 0, 0,100 //60 to 67 80 | }; 81 | 82 | float[] Surprise = new float[67] { 83 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0 to 9 84 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //10 to 19 85 | 0, 0, 0, 0, 0, 0,100, 0, 0, 0, //20 to 29 86 | 100,100, 0, 0, 0, 0, 0, 0, 0, 0, //30 to 39 87 | 0, 0, 0, 0, 0, 0, 0,100,100, 0, //40 to 49 88 | 0, 20, 20,100,100, 20, 20,100,100,100, //50 to 59 89 | 100, 0, 0, 0, 0,100,100 //60 to 67 90 | }; 91 | 92 | // Use this for initialization 93 | void Start () { 94 | skinnedMeshRenderer = skin.GetComponent(); 95 | skinnedMeshRendererTeeth = teeth.GetComponent(); 96 | } 97 | 98 | /// 99 | /// The actual expression function, which can be controlled from outside this script. 100 | /// The function is typically called every frame in the Update()-Loop. When calling is stopped, face freezes at current expression and blinking stops. 101 | /// expression index: 0 (neutral), 1 (happy), 2 (sad), 3 (angry), 4 (fearful), 5 (surprised). 102 | /// value from 0 to 1, stating intensity of expression. 103 | /// value from 0 to 1, stating speed of lerping from one expression to another. Close to 0: very small. Larger than 0.2: very fast. 104 | /// minimum number of frames between eye blinks. 105 | /// maximum number of frames between eye blinks. 106 | /// 107 | public void Expression(int expression, float intensity, float lerpSpeed, int blinkmin, int blinkmax) 108 | { 109 | // STEP 1: set BlendshapesGoal when new Expression is selected, or when intensity is changed 110 | if ((expressionLastFrame != expression) || (intensityLastFrame != intensity)) 111 | { 112 | if (intensity < 0) { intensity = 0; } // clamp intensity value 113 | if (intensity > 1) { intensity = 1; } 114 | 115 | expressionLastFrame = expression; // value is tracked to determine changes from frame to frame 116 | intensityLastFrame = intensity; 117 | 118 | 119 | //if (Expression == 1) { Ziel = Happy; } // setting entire array w/o looping does not work robustly 120 | if (expression == 0) { for (int i = 0; i < 67; i++) { BlendshapesGoal[i] = Neutral[i]; } } //up to value 67, the BlinkValue 121 | if (expression == 1) { for (int i = 0; i < 67; i++) { BlendshapesGoal[i] = Happy[i]; } } 122 | if (expression == 2) { for (int i = 0; i < 67; i++) { BlendshapesGoal[i] = Sad[i]; } } 123 | if (expression == 3) { for (int i = 0; i < 67; i++) { BlendshapesGoal[i] = Angry[i]; } } 124 | if (expression == 4) { for (int i = 0; i < 67; i++) { BlendshapesGoal[i] = Fearful[i]; } } 125 | if (expression == 5) { for (int i = 0; i < 67; i++) { BlendshapesGoal[i] = Surprise[i]; } } 126 | 127 | //for (int i = 0; i < BlendshapesGoal.GetLength(0); i++) { BlendshapesGoal[i] = BlendshapesGoal[i] * intensity; } 128 | for (int i = 0; i < 66; i++) { BlendshapesGoal[i] = BlendshapesGoal[i] * intensity; } // reduce if intesity < 1, but not for blinking value 129 | } 130 | 131 | 132 | // STEP 2: lerp current blendshapes towards goal 133 | for (int i = 0; i < 66; i++) { BlendshapesCurrent[i] = (BlendshapesCurrent[i] + (BlendshapesGoal[i] - BlendshapesCurrent[i]) * lerpSpeed); } // lerp expression in limited growth model (looks best here, imho). The larger lerpSpeed (currently .12), the faster the lerping. 134 | for (int i = 0; i < 65; i++) { skinnedMeshRenderer.SetBlendShapeWeight(i, (int)BlendshapesCurrent[i]); } //... and actually run blendshapes (not number 65, that's for the mouth) 135 | skinnedMeshRendererTeeth.SetBlendShapeWeight(29, (int)BlendshapesCurrent[65]); //run teeth blendshape. Number 29 of teeth blendshapes ist Teeth_mouthopen 136 | 137 | // STEP 3: Add blinking every once in a while 138 | BlinkTicker++; 139 | if ((BlinkTicker == 0) || (BlinkTicker == 2)) 140 | { 141 | skinnedMeshRenderer.SetBlendShapeWeight(45, BlendshapesGoal[66] * 0.6f); 142 | skinnedMeshRenderer.SetBlendShapeWeight(46, BlendshapesGoal[66] * 0.6f); 143 | } 144 | if (BlinkTicker == 1) 145 | { 146 | skinnedMeshRenderer.SetBlendShapeWeight(45, BlendshapesGoal[66]); 147 | skinnedMeshRenderer.SetBlendShapeWeight(46, BlendshapesGoal[66]); 148 | } 149 | if (BlinkTicker >= 3) { BlinkTicker = UnityEngine.Random.Range(-blinkmax, -blinkmin); } //Start counting for blinker from randomized point 150 | 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /FacialExpressions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariusrubo/Unity-Humanoid-FacialExpressions/a714631eb0c7236fdbb7fea9c83638fb170e8efd/FacialExpressions.jpg -------------------------------------------------------------------------------- /FacialExpressionsInterface.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class FacialExpressionsInterface : MonoBehaviour { 6 | 7 | // Character 8 | [Tooltip("Manually drag your character here.")] 9 | public Transform Character; 10 | private FacialExpressions CharacterFacialExpressions = null; 11 | int expression = 0; 12 | 13 | // Use this for initialization 14 | void Start () { 15 | CharacterFacialExpressions = Character.GetComponent(); 16 | } 17 | 18 | void OnGUI() 19 | { 20 | GUILayout.BeginArea(new Rect(10, 10, 100, 200)); // You can change position of Interface here. This is designed so that all my interface scripts can run together. 21 | if (GUILayout.Button("Neutral")) { expression = 0; } 22 | if (GUILayout.Button("Happy")) { expression = 1; } 23 | if (GUILayout.Button("Sad")) { expression = 2; } 24 | if (GUILayout.Button("Angry")) { expression = 3; } 25 | if (GUILayout.Button("Fearful")) { expression = 4; } 26 | if (GUILayout.Button("Surprised")) { expression = 5; } 27 | GUILayout.EndArea(); 28 | } 29 | 30 | // Update is called once per frame 31 | void Update () { 32 | //Expression(int expression, float intensity, float lerpSpeed, int blinkmin, int blinkmax) 33 | CharacterFacialExpressions.Expression(expression, 1, 0.12f, 40, 200); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | These scripts allow you to make a character created with Autodesk Character Generator look happy, sad, angry, 3 | fearful or surprised. 4 | 5 | ![Alt text](https://github.com/mariusrubo/Unity-Humanoid-FacialExpressions/blob/master/FacialExpressions.jpg) 6 | 7 | # Installation 8 | * The character must have a Skinned Mesh Renderer with facial blendshapes. The script should work plug-and-play for characters from 9 | Autodesk Character Generator. For other characters, some adjustments may be needed. 10 | * Attach "FacialExpressions.cs" to your character. Drag the character's skin and its teeth to the corresponding transforms in the 11 | inspector's view on the script. 12 | * Attach "FacialExpressionsInterface.cs" to any object in your scene (possibly, but not necessarily the character). Drag your character 13 | to the corresponding transform in the inspector's view on this script. 14 | * Press play, click on GUI buttons in the game view. 15 | 16 | # Extend 17 | * It is possible to adjust the intensity and speed of change of facial expression as well as the frequency of eye blinks. See comments 18 | in scripts for further information. 19 | * The facial expressions do not yet comform to standards defined in the Facial Action Coding System. Moreover, disgust is stil missing. 20 | This will be corrected in the future. 21 | 22 | # License 23 | These scripts run under the GPLv3 license. 24 | --------------------------------------------------------------------------------