private bool AddOuterUpBlendShape(string targetShapeName, float scaleAsFractionOfFace, bool keepLeft, bool keepRight, int onlyIncludeThisSubmesh) { if (mesh.GetBlendShapeIndex(targetShapeName) >= 0) { // Blend shape already exists. Don't try to add it again. return(false); } // The mesh extent is from center of face to edge, so multiply by two for total face size. float maxYOffset = scaleAsFractionOfFace * 2.0f * mesh.bounds.extents.magnitude; Vector3[] deltaVertices = new Vector3[mesh.vertexCount]; Vector3[] deltaNormals = new Vector3[mesh.vertexCount]; Vector3[] deltaTangents = new Vector3[mesh.vertexCount]; Vector3[] verteces = mesh.vertices; Bounds bounds = mesh.bounds; float minX = bounds.center.x - bounds.extents.x; float maxX = bounds.center.x + bounds.extents.x; UnityEngine.Rendering.SubMeshDescriptor smd = mesh.GetSubMesh(onlyIncludeThisSubmesh); for (int i = 0; i < mesh.vertexCount; i++) { Vector3 v = verteces[i]; float yOffset = 0.0f; if (i >= smd.firstVertex && i < smd.firstVertex + smd.vertexCount) { if ((v.x <= 0.0f && keepLeft) || (v.x >= 0.0f && keepRight)) { float localScale = v.x / (maxX / 2.0f); if (localScale < 0.0f) { localScale = -localScale; } if (localScale > 1.0f) { localScale = 1.0f; } yOffset = localScale * maxYOffset; } } deltaVertices[i] = new Vector3(0, yOffset, 0); deltaNormals[i] = Vector3.zero; deltaTangents[i] = Vector3.zero; } float frameWeight = 100.0f; mesh.AddBlendShapeFrame(targetShapeName, frameWeight, deltaVertices, deltaNormals, deltaTangents); return(true); }
// Find some key points on the face (center of eyes etc) // These don't have to be exact - they just help normalize the source mesh to the target mesh a bit as eyes, nose, mouth can be different sizes. private KeyPoints FindKeyPoints(Mesh mesh) { // TODO: This assumes sub meshes are at certain places - if VRoid changes, these assumptions may become incorrect. KeyPoints kp = new KeyPoints(); // Just pick any point in mesh as a relative value for Z so Z does not dominate below because the mouth is further forward in the head. float baseZ = mesh.vertices[0].z; // Assume subMesh 0 is the mouth. Find forward left and forward right most points as edges of mouth. UnityEngine.Rendering.SubMeshDescriptor mouthSmd = mesh.GetSubMesh(SM_MOUTH); for (int i = mouthSmd.firstVertex; i < mouthSmd.firstVertex + mouthSmd.vertexCount; i++) { Vector3 v = mesh.vertices[i]; // Left is +ve X. Forward is +ve Z. We want front left, so add the -X and Z offsets. if ((v.x + (v.z - baseZ) / 2.0f) > (kp.leftEdgeOfMouth.x + (kp.leftEdgeOfMouth.z - baseZ) / 2.0f)) { kp.leftEdgeOfMouth = v; } // Right is -ve X. Forward is +ve Z. We want front right, so add the X and Z offsets. if ((-v.x + (v.z - baseZ) / 2.0f) > (-kp.rightEdgeOfMouth.x + (kp.rightEdgeOfMouth.z - baseZ) / 2.0f)) { kp.rightEdgeOfMouth = v; } } // Just approximate the eye positions. // TODO: Could do a more accurate estimation by perhaps taking average of all vertices with +ve and -ve X values. UnityEngine.Rendering.SubMeshDescriptor eyeSmd = mesh.GetSubMesh(SM_EYEWHITES); kp.centerOfLeftEye = eyeSmd.bounds.center + new Vector3(eyeSmd.bounds.extents.x * 0.75f, 0, 0); kp.centerOfRightEye = eyeSmd.bounds.center - new Vector3(eyeSmd.bounds.extents.x * 0.75f, 0, 0); return(kp); }
private static SubMeshInfo[] LocateSubMeshes(Mesh mesh) { var subMeshes = new SubMeshInfo[9]; // For now this is a bit hacky - can improve over time if needed. // If VRoid character has 9 meshes, then the ears have been merged into the face already. // If 10 meshes, index 7 and 8 are face and ears - lets merge them together. // Some tools merge them, so let's work with those tools as well. int j = 0; for (int i = 0; i < mesh.subMeshCount; i++) { UnityEngine.Rendering.SubMeshDescriptor smd = mesh.GetSubMesh(i); SubMeshInfo smi = new SubMeshInfo(); smi.name = m_subMeshNames[j]; smi.startIndex = smd.firstVertex; // Or is it indexStart? smi.endIndex = smd.firstVertex + smd.vertexCount; smi.bounds = smd.bounds; // HACK: If size 10, do some magic at index 7 (face) to include following submesh (ears) // They share the same material and some tools merge them. // (If VRoid changes the order of subMeshes this will stuff things up big time!) if (i == 7 && mesh.subMeshCount == 10) { i++; smd = mesh.GetSubMesh(i); smi.bounds.Encapsulate(smd.bounds); smi.endIndex = smd.firstVertex + smd.vertexCount; } subMeshes[j++] = smi; } return(subMeshes); }
private void AdjustBlendShape( Vector3[] deltaVertices, Vector3[] deltaNormals, Vector3[] deltaTangents, float magnitudeScale, float yScale, bool keepLeft, bool keepRight, bool keepUpward, bool keepDownward, ScaleNormalization scaleNormalization, int onlyIncludeThisSubmesh, int onlyExcludeThisSubmesh) { Vector3[] vertices = mesh.vertices; Bounds bounds = mesh.bounds; float minX = bounds.center.x - bounds.extents.x; float maxX = bounds.center.x + bounds.extents.x; for (int i = 0; i < mesh.vertexCount; i++) { Vector3 v = vertices[i]; Debug.Log("v=" + V3ToString(v) + " dv=" + V3ToString(deltaVertices[i])); bool zeroIt = false; if (v.x > 0.0f && !keepRight) { zeroIt = true; Debug.Log("ZA"); } if (v.x < 0.0f && !keepLeft) { zeroIt = true; Debug.Log("ZB"); } if (deltaVertices[i].y > 0.0f && !keepUpward) { zeroIt = true; Debug.Log("ZC"); } if (deltaVertices[i].y < 0.0f && !keepDownward) { zeroIt = true; Debug.Log("ZD"); } if (zeroIt) { deltaVertices[i] = Vector3.zero; deltaNormals[i] = Vector3.zero; deltaTangents[i] = Vector3.zero; } else { Debug.Log("KEEP"); // Adjust the scaling factor based on position on face. float scaleFactor = magnitudeScale; switch (scaleNormalization) { case ScaleNormalization.LeftToRight: { float localScale = (v.x - minX) / (maxX - minX); Debug.Log("1.v.x=" + v.x + ", minX=" + minX + ", maxX=" + maxX + ", lscale=" + localScale); if (localScale < 0.0f) { localScale = 0.0f; } if (localScale > 1.0f) { localScale = 1.0f; } scaleFactor *= localScale; break; } case ScaleNormalization.RightToLeft: { float localScale = (v.x - minX) / (maxX - minX); Debug.Log("2.v.x=" + v.x + ", minX=" + minX + ", maxX=" + maxX + ", lscale=" + localScale); if (localScale < 0.0f) { localScale = 0.0f; } if (localScale > 1.0f) { localScale = 1.0f; } scaleFactor *= (1.0f - localScale); break; } case ScaleNormalization.ZeroAtCenter: { float localScale = v.x / (maxX / 4.0f); Debug.Log("localScale = " + localScale); Debug.Log("2.v.x=" + v.x + ", minX=" + minX + ", maxX=" + maxX + ", lscale=" + localScale); if (localScale < 0.0f) { localScale = -localScale; } if (localScale > 1.0f) { localScale = 1.0f; } Debug.Log("final localScale = " + localScale); scaleFactor *= localScale; Debug.Log("scaleFactor= " + scaleFactor); break; } } // Multiply in magnitude scale. deltaVertices[i] *= scaleFactor; deltaNormals[i] *= scaleFactor; deltaTangents[i] *= scaleFactor; // Multiple in Y scale (e.g. scale = -1 to invert) deltaVertices[i].y *= yScale; deltaNormals[i].y *= yScale; deltaTangents[i].y *= yScale; } } if (onlyIncludeThisSubmesh >= 0) { // Set everything to zero not in requested submesh UnityEngine.Rendering.SubMeshDescriptor smd = mesh.GetSubMesh(onlyIncludeThisSubmesh); for (int i = 0; i < smd.firstVertex; i++) { deltaVertices[i] = Vector3.zero; deltaNormals[i] = Vector3.zero; deltaTangents[i] = Vector3.zero; } for (int i = smd.firstVertex + smd.vertexCount; i < mesh.vertexCount; i++) { deltaVertices[i] = Vector3.zero; deltaNormals[i] = Vector3.zero; deltaTangents[i] = Vector3.zero; } } if (onlyExcludeThisSubmesh >= 0) { // Set everything to zero in requested submesh UnityEngine.Rendering.SubMeshDescriptor smd = mesh.GetSubMesh(onlyExcludeThisSubmesh); for (int i = smd.firstVertex; i < smd.firstVertex + smd.vertexCount; i++) { deltaVertices[i] = Vector3.zero; deltaNormals[i] = Vector3.zero; deltaTangents[i] = Vector3.zero; } } }