Copying BlendShape property from a SkinnedMeshRenderer to Another

Hi

I don’t know if I’m posting my problem to the correct Community, but here it goes. My apologies if I’m doing wrong.

  1. I’m using Unity 2021.1.22f1

  2. My idea for a game is to create static models of spaceships parent of bones in Blender, and control those bones inside Unity, to generate space ships with pseudo-randomand symmetric shapes

  3. Inside Unity, there is a gameObject called Library, with SkinnedMeshRenderers components on GameObjects, which are the spaceships created on Blender

  4. I started with a simple prototype to change randomly the ORIGINAL SkinnedMeshRenderer objects, with success

  5. But when I copy the SkinnedMeshRenderer object to another GameObject through Instantiate() method, and try to change the bone transform which controls the SkinnedMeshRenderer shape in the new copyed GameObject, nothing happens

  6. My code is down below:

//-----------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;

public class CopyShips : MonoBehaviour
{
public Transform library;
Transform ship00T;
Transform ships;

Mesh copyMesh(SkinnedMeshRenderer smr)
{
Mesh mesh = smr.sharedMesh;

Mesh newmesh = new Mesh();
newmesh.name = mesh.name + “_copy”;

int i;

List vertices = new List();
for (i = 0; i < mesh.vertices.Length; i++)
{
vertices.Add(mesh.vertices*);*
}
newmesh.vertices = vertices.ToArray();
List triangles = new List();
for (i = 0; i < mesh.triangles.Length; i++)
{
triangles.Add(mesh.triangles*);*
}
newmesh.triangles = triangles.ToArray();
List uv = new List();
for (i = 0; i < mesh.uv.Length; i++)
{
uv.Add(mesh.uv*);*
}
newmesh.uv = uv.ToArray();
List normals = new List();
for (i = 0; i < mesh.normals.Length; i++)
{
normals.Add(mesh.normals*);*
}
newmesh.normals = normals.ToArray();
List colors = new List();
for (i = 0; i < mesh.colors.Length; i++)
{
colors.Add(mesh.colors*);*
}
newmesh.colors = colors.ToArray();
List tangents = new List();
for (i = 0; i < mesh.tangents.Length; i++)
{
tangents.Add(mesh.tangents*);*
}
newmesh.tangents = tangents.ToArray();
List bindposes = new List();
for (i = 0; i < mesh.bindposes.Length; i++)
{
bindposes.Add(mesh.bindposes*);*
}
newmesh.bindposes = bindposes.ToArray();
List boneWeights = new List();
for (i = 0; i < mesh.boneWeights.Length; i++)
{
boneWeights.Add(mesh.boneWeights*);*
}
newmesh.boneWeights = boneWeights.ToArray();

return newmesh;
}
void Start()
{
ship00T = library.Find(“Ship00T”);
ships = library.Find(“Ships”);
int i, t = ships.childCount / 2;
for (i = 0; i < t; i++)
{
Transform armature = ships.Find(“Armature_Ship” + i.ToString(“00”));
Transform ship = ships.Find(“Ship” + i.ToString(“00”));
SkinnedMeshRenderer smrShip = ship.gameObject.GetComponent();
Transform rootBone = smrShip.rootBone;
GameObject ship00T_copy = Instantiate(ship00T.gameObject);
ship00T_copy.transform.parent = transform;
ship00T_copy.transform.name = “ship” + i.ToString(“00”) + “T”;
GameObject armature_copy = Instantiate(armature.gameObject);
armature_copy.transform.parent = ship00T_copy.transform;
armature_copy.transform.name = armature.name;
GameObject ship_copy = Instantiate(ship.gameObject);
ship_copy.transform.parent = ship00T_copy.transform;
ship_copy.transform.name = ship.name;
SkinnedMeshRenderer smrCopy = ship_copy.gameObject.GetComponent();
smrCopy.rootBone = armature_copy.transform.Find(rootBone.name);
DeformShipBones dsb = ship00T_copy.GetComponent();
dsb.deformShips();
Mesh mesh = copyMesh(smrCopy);
smrCopy.BakeMesh(mesh, true);
smrCopy.sharedMesh = mesh;
}
}
}
//-----------------------------------------------------------------------------------------------
7) Executing the code, the copies are created, and when I click on the copyed GameObject with the SkinnedMeshRenderer in it, there is a message about the lack of the object blend shape, as written below:
The assigned mesh is missing either bone weights with bind pode, or blend shapes. This might cause the mesh no to render in the Player. If your mesh does not have either bone weights with bind pose, or blend shapes, use a mesh renderer instead of SkinnedMeshRenderer.
8) I also posted an image [UnityBlendShapesInSkinnedMeshRenderer.png] about the problem
9) The question I have is:
As we can program the copy of almost all properties of one mesh to another, as shown in the code above, like vertices, triangles, uvs, normals, colors, tangents, bindPoses and boneWeights, how can I copy BlendShapes, as this type of structure is not only a list of basic types, like Vector3 in vertices, or like int in triangles?
10) I also have look for some solution or thread on discussion forums, through Google, but with no success, and also tried to find the solution programming experimentally the copy of mesh.BlendShape objects (using auto-complete code from Visual Studio 2019), also with no success
11) Unity help on the subject Mesh (Unity - Scripting API: Mesh) present the list of methods about BlendShapes, but none of them have a complete example, as they - as told before - are complex objects
12) The first time I had programmed the deformation of SkinnedMeshRenderer was five years ago (2016), and I believe that Unity have make some structural changes into Mesh classes, since then.
13) Is it possible for someone to answer with a complete sample code about how to do that, please?
14) If you are interested into my prototype project, to help me on this matter, please let me know in your answer to this post, please. So, as this project is still at its start, it is very small, and I can put it exported and zipped in here, as an answer.
Cordially, and thanks in advance
César R. K. Stradiotto
Biguaçú - SC - Brazil

Sorry, I forgot the DeformShipBones code:

//-----------------------------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BonePair
{
public Transform a, l, r;
}

public class DeformShipBones : MonoBehaviour
{
[Range(0, 15)]
public float rotationStrenght = 7;

[Range(-0.05f, 0.05f)]
public float positionStrenght = .04f;

[Range(-.2f, .2f)]
public float minScaleStrenght = -.1f;
[Range(-.2f, .2f)]
public float maxScaleStrenght = .1f;

Dictionary<string, Transform> bones = new Dictionary<string, Transform>();
List bonePairs = new List();

void recursiveBoneFiller(Transform origin, Dictionary<string, Transform> bones)
{
if(
origin.name.StartsWith(“a”) ||
origin.name.StartsWith(“l”) ||
origin.name.StartsWith(“r”)
)
{
bones.Add(origin.name, origin);
}

int i, t = origin.childCount;
for (i = 0; i < t; i++)
{
Transform c = origin.GetChild(i);
recursiveBoneFiller(c, bones);
}
}

void constructPairList()
{
foreach (KeyValuePair<string, Transform> item in bones)
{
if (
item.Key.StartsWith(“a”)
)
{
BonePair bonePair = new BonePair { a = item.Value};
bonePairs.Add(bonePair);
}
else
if (
item.Key.StartsWith(“l”)
)
{
BonePair bonePair = new BonePair { l = item.Value };
string r = item.Key.Replace(“l”, “r”);
bonePair.r = bones[r];
bonePairs.Add(bonePair);
}
else
if (
item.Key.StartsWith(“r”)
)
{
BonePair bonePair = new BonePair { r = item.Value };
string l = item.Key.Replace(“r”, “l”);
bonePair.l = bones[l];
bonePairs.Add(bonePair);
}
}
}

bool isDuplicate(BonePair i, BonePair j)
{
if (i.a || j.a) return false;

if (i.l == j.l && i.r == j.r)
{
return true;
}

return false;
}

void removeDuplicates()
{
int i, j;
for (i = 0; i < bonePairs.Count - 1; i++)
{
BonePair bpI = bonePairs*;*
for (j = i + 1; j < bonePairs.Count; j++)
{
BonePair bpJ = bonePairs[j];
if (isDuplicate(bpI, bpJ))
{
bonePairs.RemoveAt(j–);
}
}
}
}
void deformBones()
{
int i;
for (i = 0; i < bonePairs.Count; i++)
{
BonePair bp = bonePairs*;*
if (bp.a) continue;
float rx = Random.Range(-rotationStrenght, rotationStrenght);
float ry = Random.Range(-rotationStrenght, rotationStrenght);
float rz = Random.Range(-rotationStrenght, rotationStrenght);
bp.l.localRotation *= Quaternion.Euler( rx, ry, rz);
bp.r.localRotation *= Quaternion.Euler( rx,-ry,-rz);
float px = Random.Range(-positionStrenght, positionStrenght);
float py = Random.Range(-positionStrenght, positionStrenght);
float pz = Random.Range(-positionStrenght, positionStrenght);
bp.l.localPosition += new Vector3( px, py, pz);
bp.r.localPosition += new Vector3(-px, py, pz);
float minS = Mathf.Min(minScaleStrenght, maxScaleStrenght);
float maxS = Mathf.Max(minScaleStrenght, maxScaleStrenght);
float sx = Random.Range(minS, maxS);
float sy = Random.Range(minS, maxS);
float sz = Random.Range(minS, maxS);
bp.l.localScale += new Vector3(sx, sy, sz);
bp.r.localScale += new Vector3(sx, sy, sz);
}
}
public void deformShips()
{
recursiveBoneFiller(transform, bones);
Debug.Log("recursiveBoneFiller done for " + transform.name);
constructPairList();
Debug.Log("constructPairList done for " + transform.name);
removeDuplicates();
Debug.Log("removeDuplicates done for " + transform.name);
deformBones();
Debug.Log("deformBones done for " + transform.name);
}
void Start()
{

}
void Update()
{
}
}
//-----------------------------------------------------------------------------------------------
My bad…

Finally:

  1. Using the following keywords on Google: unity 2021 skinnedmeshrenderer copy does not deform

I could find the solution from two sources:

1.1) Unity Documentation - Version 2020.3 - Working with blend shapes - Unity - Manual: Work with blend shapes

1.2) Youtube - Granger GameDev - Oct 3, 2016 - Blender Blend Shapes and Morph Targets -

  1. So I had to learn how to make Blend Shapes on Blender, and when one import Blender file into Unity, the model resulted from the blending already has a SkinnedMeshRenderer component attached to it. The other models have no SkinnedMeshRenderer, only the one which is blended (see video on 1.2 above, to see who is blended, and who is not)

  2. So, one can make a copy of the blended object simply using GameObject.Instantiate(…), can capture the SkinnedMeshRenderer component of it:

SkinnedMeshRenderer smr = ship_copy.gameObject.GetComponent();

and do:

smr.SetBlendShapeWeight(0, 75f);
smr.SetBlendShapeWeight(1, 0f);
smr.SetBlendShapeWeight(2, 25f);

In this example, as my blender file has six models to blend the main model, the method

smr.SetBlendShapeWeight(i, p);

can have values from 0 to 5

and p can variate from 0 to 100 float. (Not 0 to 1).

Uff! finally…

Now I must replan this part of the game… may be in the future I’m gonna use bones again.