Skip to content

Instantly share code, notes, and snippets.

@valentinwinkelmann
Last active February 10, 2023 12:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save valentinwinkelmann/4864b81a4210f8600ab70886d536c897 to your computer and use it in GitHub Desktop.
Save valentinwinkelmann/4864b81a4210f8600ab70886d536c897 to your computer and use it in GitHub Desktop.
Allows to control blendshapes based on eye movements. Comes pre-configured with the configuration for CC4 blendshapes. Simply attach it to an object, add the mesh renderer and link the two eyeballs, then you need the remaining positions of the two eyes, these are the rotations where the eyes are looking absolutely straight ahead, their zero posi…
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EyeBlendShapes : MonoBehaviour
{
public skinnedMeshRendererContainer[] meshRenderers;
public Quaternion LeftEye_RestPosition;
public Transform LeftEye;
public Quaternion RightEye_RestPosition;
public Transform RightEye;
public float Range_Horizontal = 30f;
public float Range_Vertical = 15f;
//[Range(-1f, 1f)]
private float lookHorizontal_Left = 0f;
//[Range(-1f, 1f)]
private float lookHorizontal_Right = 0f;
//[Range(-1f, 1f)]
private float lookVertical_Left = 0f;
//[Range(-1f, 1f)]
private float lookVertical_Right = 0f;
public axisEnum HorizontalAxis = axisEnum.Z;
public axisEnum VerticalAxis = axisEnum.X;
private Vector3 rotationLeft;
private Vector3 rotationRight;
public blendShapeControll[] blendShapes = new blendShapeControll[]
{
new blendShapeControll
{
BlendShapeName = "Eye_L_Look_Up",
eye = eyeSide.Left,
range = new Vector2(0, 1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Eye_L_Look_Down",
eye = eyeSide.Left,
range = new Vector2(0, -1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Eye_L_Look_L",
eye = eyeSide.Left,
range = new Vector2(0, 1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Eye_L_Look_R",
eye = eyeSide.Left,
range = new Vector2(0, -1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Brow_Raise_Outer_L",
eye = eyeSide.Left,
range = new Vector2(0, 1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Eye_R_Look_Up",
eye = eyeSide.Right,
range = new Vector2(0, 1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Eye_R_Look_Down",
eye = eyeSide.Right,
range = new Vector2(0, -1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Eye_R_Look_L",
eye = eyeSide.Right,
range = new Vector2(0, 1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Eye_R_Look_R",
eye = eyeSide.Right,
range = new Vector2(0, -1),
direction = directionEnum.Vertical
},
new blendShapeControll
{
BlendShapeName = "Brow_Raise_Outer_R",
eye = eyeSide.Right,
range = new Vector2(0, 1),
direction = directionEnum.Vertical
},
};
public AnimationCurve eyeCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(1, 1));
public void LateUpdate()
{
CalculateEyes();
SetBlendShapes();
}
private void CalculateEyes()
{
rotationLeft = EyeRotation(LeftEye, LeftEye_RestPosition, eyeSide.Left);
lookHorizontal_Left = Mathf.Lerp(-1, 1, Mathf.InverseLerp(-Range_Horizontal, Range_Horizontal, GetAxis(rotationLeft, HorizontalAxis)));
lookVertical_Left = Mathf.Lerp(-1, 1, Mathf.InverseLerp(-Range_Vertical, Range_Vertical, GetAxis(rotationLeft, VerticalAxis)));
rotationRight = EyeRotation(RightEye, RightEye_RestPosition, eyeSide.Right);
lookHorizontal_Right = Mathf.Lerp(-1, 1, Mathf.InverseLerp(-Range_Horizontal, Range_Horizontal, GetAxis(rotationRight, HorizontalAxis)));
lookVertical_Right = Mathf.Lerp(-1, 1, Mathf.InverseLerp(-Range_Vertical, Range_Vertical, GetAxis(rotationRight, VerticalAxis)));
}
private void OnDrawGizmos()
{
//CalculateEyes();
//SetBlendShapes();
}
private void OnValidate()
{
for (int i = 0; i < meshRenderers.Length; i++)
{
if(meshRenderers[i].skinnedMesh != null)
{
meshRenderers[i].GetBlendShapeNames();
}
}
}
private void SetBlendShapes()
{
for (int bI = 0; bI < blendShapes.Length; bI++)
{
float look = blendShapes[bI].direction == directionEnum.Horizontal ? blendShapes[bI].eye == eyeSide.Right ? lookHorizontal_Right : lookHorizontal_Left : blendShapes[bI].eye == eyeSide.Right ? lookVertical_Right : lookVertical_Left;
float value = Mathf.InverseLerp(blendShapes[bI].range.x, blendShapes[bI].range.y, look);
for (int mI = 0; mI < meshRenderers.Length; mI++)
{
meshRenderers[mI].SetBlendShape(blendShapes[bI].BlendShapeName, eyeCurve.Evaluate(value) * 100f);
}
}
}
private Vector3 EyeRotation(Transform eye, Quaternion RestPosition, eyeSide eyeSide)
{
Vector3 restEuler = eyeSide == eyeSide.Left ? LeftEye_RestPosition.eulerAngles : RightEye_RestPosition.eulerAngles;
Vector3 currentEuler = eyeSide == eyeSide.Left ? LeftEye.localRotation.eulerAngles : RightEye.localRotation.eulerAngles;
float xDiff = (currentEuler.x - restEuler.x + 180) % 360 - 180;
float yDiff = (currentEuler.y - restEuler.y + 180) % 360 - 180;
float zDiff = (currentEuler.z - restEuler.z + 180) % 360 - 180;
xDiff = Mathf.Round(xDiff * 100f) / 100f;
yDiff = Mathf.Round(yDiff * 100f) / 100f;
zDiff = Mathf.Round(zDiff * 100f) / 100f;
return new Vector3(xDiff, yDiff, zDiff);
}
[ContextMenu("Get Eye Rotations")]
private void GetEyeRotations()
{
LeftEye_RestPosition = LeftEye.localRotation;
RightEye_RestPosition = RightEye.localRotation;
}
private float GetAxis(Vector3 vector3, axisEnum axis)
{
switch (axis)
{
case axisEnum.X:
return vector3.x;
case axisEnum.Y:
return vector3.y;
case axisEnum.Z:
return vector3.z;
default:
return vector3.x;
}
}
public enum axisEnum
{
X,Y,Z
}
public enum eyeSide
{
Left,Right
}
public enum directionEnum
{
Horizontal,Vertical
}
[System.Serializable]
public class blendShapeControll
{
public string BlendShapeName;
public eyeSide eye;
public Vector2 range;
public directionEnum direction;
}
[System.Serializable]
public class skinnedMeshRendererContainer
{
public SkinnedMeshRenderer skinnedMesh;
public Dictionary<string,int> blendShapeNames;
public void SetBlendShape(string name, float value)
{
if(blendShapeNames.TryGetValue(name, out int id))
{
skinnedMesh.SetBlendShapeWeight(id, value);
}
}
public void GetBlendShapeNames()
{
Mesh m = skinnedMesh.sharedMesh;
string[] arr;
arr = new string[m.blendShapeCount];
blendShapeNames = new Dictionary<string, int>();
for (int i = 0; i < m.blendShapeCount; i++)
{
string s = m.GetBlendShapeName(i);
arr[i] = s;
blendShapeNames.Add(s, i);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment