- Home /
How to scale Mesh.bindPose correctly
Hello everyone,
I'm stuck with a mathematical problem. I want to set all bones of a model to their bind poses. This works OK for most of the models but there is one where the scale in the bind pose is 40 (I guess it was created using that scale, and after importing in Unity it got scaled down automatically to 1). So I want to scale the whole bindPose[] array down so that every bone transform has a scale of 1 (to match my default scale). But this doesn't work as expected (the whole mesh get's messed up).
Matrix4x4 bindPose = skinnedMeshRenderer.sharedMesh.bindposes[boneIndex];
// Scale by 1 / 40 so that the end result is 1
Vector3 scale = new Vector3(1/40, 1/40, 1/40);
Matrix4x4 normalizeScale = Matrix4x4.Scale(scale);
// Apply the resulting matrix afterwards
Matrix4x4 result = transform.localToWorldMatrix * normalizeScale * bindPose.inverse;
So I guess either the order in which I multiply the matrices is not correct, or I'm doing the scale in the wrong space? Thanks in advance.
Answer by Bunny83 · Sep 15, 2016 at 09:35 PM
Your calculation doesn't make much sense ^^. The bindpose matrix is a matrix that should transform a point from the mesh's local space into the localspace of the bone. Once in that space Unity's animation system would then transform the position from the bones local space into world space using the current localToWorld matrix of the bone.
Next thing is this won't work:
Vector3 scale = new Vector3(1/40, 1/40, 1/40);
this will give you a (0, 0, 0) vector because 1 and 40 are both integer values.
Matrix4x4 normalizeScale = Matrix4x4.Scale(Vector3.one / 40f);
Since it's the bones local space which is wrongly scaled you want the scaling to happen after all the other transformations. So you have to put your matrix infront of your original bindpose:
bindPose = normalizeScale * bindPose;
Next thing is it'S not clear what you do with your "result". Keep in mind that sharedMesh.bindposes
is a property, so you can't assign seperate matrices to that array. You have to use a temp array, modify your matrices and finally assign the whole array back to sharedMesh.bindposes.
Final Note: Messing with bindposes of imported models isn't a good idea. It will most likely mess up all animations you have. If there is an issue in the model itself, it should be fixed in the modelling tool.
If you just want to change the scale factor of the model, check out the model inspector of the imported model. You can specify a scale factor there. It will reimport the model and will guarantee that everything will still work (as long as the model doesn't have an error already ^^).
edit
Ahh, thanks to your comment i think we can solve that problem ^^. Your mistake is that you used the Amature transform. You have to use the mesh transform.
The mesh is actually rendered by the SkinnedMeshRenderer. So the transform of the object with the SkinnedMeshRenderer is the one you have to use when you revert the bindpose.
So the original bindpose matrix converts the local vertex coordinates from the local mesh space into the local space of the bone. When you invert the matrix you convert from bone to local mesh space. So then using the localToWorld matrix of the SkinnedMeshRenderer you would get back the bone matrix.
Thanks for your answer @Bunny83. Yes you are right 1/40 won't work. In the real code the factor is calculated and not hard coded as in the sample above so this was a mistake when I've written the question. Sorry for that...
$$anonymous$$y goal is to reset a model back to it's initial bind pose position at runtime. So I'm extracting the position, rotation and scale out of the resulting matrix and assign those to the corresponding bone transform. This works great with some models but not with all because of the mentioned scaling problem.
Here is my code that works resetting the bones of most of my models (but has the scaling issue):
$$anonymous$$atrix4x4 bindPose = skinnedRenderer.shared$$anonymous$$esh.bindposes[boneIndex];
// armatureTransform is the transform named "Armature" which is the parent transform of all bones
// everytime you drag and drop an imported model into the scene.
$$anonymous$$atrix4x4 invertBindPose = armatureTransform.localToWorld$$anonymous$$atrix * bindPose.inverse;
boneTransform.position = matrix.GetColumn(3);
boneTransform.rotation = Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1));
boneTransform.localScale = new Vector3( matrix.GetColumn(0).magnitude,
matrix.GetColumn(1).magnitude,
matrix.GetColumn(2).magnitude);
Trying to reset this free spider model from the asset store for example doesn't work with this code as bindPose.inverse has a scale of 40 for this model (I checked the magnitudes of column 0 - 2 with the debugger --> scale = 40) and my other models that work have 1. So I wanted to know how to scale down the bindPose.inverse to also have a scale of 1.
Ahh, i got your problem now ^^. I'll edit my answer...
@Bunny83: Thanks for the hint with the mesh. The scaling is correct when using the skinned$$anonymous$$eshRenderer.localToWorld$$anonymous$$atrix, but the rotation and the position is wrong. Further investigations showed me that the "armature" transform still plays a roll. So I'm using now:
$$anonymous$$atrix4x4 invertBindPose = armatureTransform.localToWorld$$anonymous$$atrix * skinned$$anonymous$$eshRenderer.localToWorld$$anonymous$$atrix * bindPose.inverse;
With this formular, the rotation and the scaling seems to be correct but the position is still wrong (all bones get a world space offset). I don't understand how the Skinned$$anonymous$$eshRenderer calculates it's end result based on the different matrices (what is multiplied in which order)...
Also, if you change the transform parameters of the Skinned$$anonymous$$eshRenderer in the inspector nothing happens. So those values don't seem to be used by the "Skinned$$anonymous$$eshRenderer" directly???
Answer by theANMATOR2b · Sep 15, 2016 at 12:54 PM
This forum post by hippocoder seems to be relevant.