Quick answer: Compute boneMatrices[i] = bones[i].localToWorldMatrix * mesh.bindposes[i]. Pass the result to the job. Schedule from LateUpdate after the SkinnedMeshRenderer’s own update. Vertex transform is then in world space.
You write a custom skinning job to apply effects on top of normal animation. Output is a tangled mesh because bone-local positions land in the wrong space.
The Symptom
Custom skinned mesh deformer renders verts in wrong positions. Bones look right; mesh is shifted/rotated wildly.
The Fix
var bindPoses = mesh.bindposes;
var boneMatrices = new NativeArray<float4x4>(bones.Length, Allocator.TempJob);
for (int i = 0; i < bones.Length; i++)
boneMatrices[i] = bones[i].localToWorldMatrix * bindPoses[i];
var job = new SkinJob {
boneMatrices = boneMatrices,
boneIndices = _boneIndicesNative,
boneWeights = _boneWeightsNative,
inVerts = _inVerts,
outVerts = _outVerts
};
var handle = job.Schedule(_inVerts.Length, 64);
handle.Complete();
boneMatrices.Dispose();
The bindpose multiply is the key. Verts get transformed: bind-pose-inverse * world-bone = world position.
Inside the Job
[BurstCompile]
struct SkinJob : IJobParallelFor
{
[ReadOnly] public NativeArray<float4x4> boneMatrices;
[ReadOnly] public NativeArray<int4> boneIndices;
[ReadOnly] public NativeArray<float4> boneWeights;
[ReadOnly] public NativeArray<float3> inVerts;
public NativeArray<float3> outVerts;
public void Execute(int i)
{
var p = new float4(inVerts[i], 1);
var idx = boneIndices[i];
var w = boneWeights[i];
var result = math.mul(boneMatrices[idx.x], p) * w.x
+ math.mul(boneMatrices[idx.y], p) * w.y
+ math.mul(boneMatrices[idx.z], p) * w.z
+ math.mul(boneMatrices[idx.w], p) * w.w;
outVerts[i] = result.xyz;
}
}
Timing
SkinnedMeshRenderer updates in its own LateUpdate. Schedule after, or via PlayerLoop hook in PostLateUpdate. From script, LateUpdate works for most cases.
Verifying
Compare your output to a stock SkinnedMeshRenderer. Should match exactly when no extra deformation is applied. If skewed, the bindpose multiply is missing or order is reversed.
“localToWorld * bindpose. World-space verts. Job in LateUpdate.”
Related Issues
For SMR bounds, see SMR bounds. For 2D Animation rig, see 2D rig.
Bind pose multiply. Right space. Mesh deforms.