Dual Quaternion Skinning

5000fdaaeeeb9f038be93cf67eb3b130
0
David_Gallagher 102 Apr 30, 2012 at 01:54

I just had a question about dual quaternin skinning. I’ve just implemented it based on a few examples i found over the net and one thing i don’t understand is it seems that I can only get a total 180 or -180 degrees on z for a whole bone chain. if the total bones twist (on z) adds up to greater than 180 i seem to get either gimble lock on some bones or verts at the joint get mashed up a bit. is this normal? I really like the quaternion skinning for it’s natural bending but wasn’t aware of any limitations or things to be aware of like this.

below are some pics and code if anyone want’s to chime in with some advice, or knows why this is happening or what i can do to prevent it.

thanks.

some pics of the issue (sorry if they are too big)

default pose

default\_pose.jpg

rotating the upper arm 120 degrees on z and the forearm 60 on z making a total of 180

quat\_blend\_less\_180\_total.jpg

and rotating the upper arm 120 degrees on z and the forearm 100 on z making a total greater than 180

quat\_blend\_greater\_180\_total.jpg

the dx side converting bone transforms to dual quaternions

void iUQTtoUDQ(vec4* dual, D3DXQUATERNION q, D3DXVECTOR3& tran )
{
    dual[0].x = q.x ;
    dual[0].y = q.y ;
    dual[0].z = q.z ;
    dual[0].w = q.w ;
    dual[1].x = 0.5f *  ( tran[0] * q.w + tran[1] * q.z - tran[2] * q.y ) ;
    dual[1].y = 0.5f *  (-tran[0] * q.z + tran[1] * q.w + tran[2] * q.x ) ;
    dual[1].z = 0.5f *  ( tran[0] * q.y - tran[1] * q.x + tran[2] * q.w ) ;
    dual[1].w = -0.5f * ( tran[0] * q.x + tran[1] * q.y + tran[2] * q.z ) ;
return;
}

//---
for(int j = 0; j<MAX_BONE_COUNT; j++)
{
    if(j < (int)transforms.size())
    {
  //-------------------------------------
  //matrix
  /*D3DXMATRIX dxmatTransform = transforms[j];
  D3DXMatrixTranspose(&dxmatTransform,&dxmatTransform);
  pMesh[i].cbBones.m_constBoneWorld[j] = dxmatTransform;*/
  //-------------------------------------
  //dual quaternion skinning test 2
  D3DXQUATERNION q1;
  D3DXVECTOR3 v1,v2;

  D3DXMatrixDecompose(&v1,&q1,&v2,&transforms[j]);
  vec4 dual[2] ;
  iUQTtoUDQ( dual, q1, v2);
  pMesh[i].cbBones.m_constBoneWorld_A[j] = D3DXVECTOR4(dual[0].x,dual[0].y,dual[0].z,dual[0].w);
  pMesh[i].cbBones.m_constBoneWorld_B[j] = D3DXVECTOR4(dual[1].x,dual[1].y,dual[1].z,dual[1].w);
  //-------------------------------------
    }
    else
    {
  //dual quaternion
  pMesh[i].cbBones.m_constBoneWorld_A[j] = D3DXVECTOR4(0.0f,0.0f,0.0f,0.0f);
  pMesh[i].cbBones.m_constBoneWorld_B[j] = D3DXVECTOR4(0.0f,0.0f,0.0f,0.0f);
  //matrix
  /*D3DXMATRIX empty;
  D3DXMatrixIdentity(&empty);
  D3DXMatrixTranspose(&empty,&empty);
        pMesh[i].cbBones.m_constBoneWorld[j] = empty;*/
    }
}

and the relevant hlsl side…

#define MAX_BONE_COUNT 128
cbuffer animationvars  : register(b1)
{
//float4x4 g_matrices[MAX_BONE_COUNT]; // the bone transforms
float4 g_dualquat_A[MAX_BONE_COUNT];
float4 g_dualquat_B[MAX_BONE_COUNT];
};

matrix UDQtoRM(float2x4 dual)
{
    matrix m ;
    float length = dot(dual[0], dual[0]);
    float x = dual[0].x, y = dual[0].y, z = dual[0].z, w = dual[0].w;
    float t1 = dual[1].x, t2 = dual[1].y, t3 = dual[1].z, t0 = dual[1].w;
      
    m[0][0] = w*w + x*x - y*y - z*z;
    m[1][0] = 2*x*y - 2*w*z;
    m[2][0] = 2*x*z + 2*w*y;
    m[0][1] = 2*x*y + 2*w*z;
    m[1][1] = w*w + y*y - x*x - z*z;
    m[2][1] = 2*y*z - 2*w*x;
    m[0][2] = 2*x*z - 2*w*y;
    m[1][2] = 2*y*z + 2*w*x;
    m[2][2] = w*w + z*z - x*x - y*y;
  
    m[3][0] = -2*t0*x + 2*t1*w - 2*t2*z + 2*t3*y ;
    m[3][1] = -2*t0*y + 2*t1*z + 2*t2*w - 2*t3*x ;
    m[3][2] = -2*t0*z - 2*t1*y + 2*t2*x + 2*t3*w ;
  
    m[0][3] = 0.0 ;
    m[1][3] = 0.0 ;
    m[2][3] = 0.0 ;
    m[3][3] = 1.0 ;       
  
    m /= length ;
  
    return m ;  
}

VS_SKIN_OUTPUT VS_Skin(VS_INPUT_NOSKIN IN)
{
//-------------------------------------------------------------
    VS_SKIN_OUTPUT OUT = (VS_SKIN_OUTPUT)0;
//-------------------------------------------------------------
//dual quaternion skinning test
float w = 12.0f;
float lastWeight = dot(IN.BoneWeights.xyz,float3(1.0f,1.0f,1.0f));

float2x4 dq0 = float2x4(g_dualquat_A[IN.BoneIndices.x],g_dualquat_B[IN.BoneIndices.x]);
float2x4 dq1 = float2x4(g_dualquat_A[IN.BoneIndices.y],g_dualquat_B[IN.BoneIndices.y]);
float2x4 dq2 = float2x4(g_dualquat_A[IN.BoneIndices.z],g_dualquat_B[IN.BoneIndices.z]);
float2x4 dq3 = float2x4(g_dualquat_A[IN.BoneIndices.w],g_dualquat_B[IN.BoneIndices.w]);
if (dot(dq0[0], dq1[0]) < 0.0) dq1 *= -1.0;
if (dot(dq0[0], dq2[0]) < 0.0) dq2 *= -1.0;
if (dot(dq0[0], dq3[0]) < 0.0) dq3 *= -1.0;

float2x4 blendDQ = IN.BoneWeights.x*dq0;
blendDQ += IN.BoneWeights.y*dq1;
blendDQ += IN.BoneWeights.z*dq2;
blendDQ += (lastWeight/w)*dq3;
matrix m = UDQtoRM(blendDQ);
//-------------------------------------------------------------
//matrix skinning
/*matrix m;
m = IN.BoneWeights.x * g_matrices[IN.BoneIndices.x];
m += IN.BoneWeights.y * g_matrices[IN.BoneIndices.y];
m += IN.BoneWeights.z * g_matrices[IN.BoneIndices.z];
m += IN.BoneWeights.w * g_matrices[IN.BoneIndices.w];*/
    OUT.Position = mul (IN.Position,m);
OUT.Position.w = 1.0f;//comment out w if using matrix skinning
    OUT.Normal = mul (IN.Normal,(float3x3)m);
    OUT.T = mul (IN.T,m);
OUT.B = mul (IN.B,(float3x3)m);
    return OUT;
//-------------------------------------------------------------
}

1 Reply

Please log in or register to post a reply.

5000fdaaeeeb9f038be93cf67eb3b130
0
David_Gallagher 102 Apr 30, 2012 at 02:49

Edit: no worries now it all turned out ok… rigged the whole character and seems to be fine now.

if interested here is a video comparing matrix skinning and dual quaternion skinning: Video