PDA

View Full Version : Skeletal Animation GLSL



DavidJr
11-22-2015, 12:20 AM
Hello, I've figured out how to do skeletal animation with deprecated function and now I'm planning to move on with GLSL.
This page (https://www.opengl.org/wiki/Skeletal_Animation) tells me that it can be achieved by sending matrix uniform to the shader.
The page also tells me that each matrix costs a lot register, so I decided to send the quaternion instead, what I want to ask is:
1. Is sending quaternion uniform to shader is better than sending the matrix?
2. Is it better to calculate the quaternion in the shader or in the CPU?
3. The way I declare the quaternion in shader, is it correct?

uniform vec4 vQuat[MAX_BONES];

Thank you very much

Dark Photon
11-22-2015, 04:25 PM
Hello, I've figured out how to do skeletal animation with deprecated function and now I'm planning to move on with GLSL.
This page (https://www.opengl.org/wiki/Skeletal_Animation) tells me that it can be achieved by sending matrix uniform to the shader.
The page also tells me that each matrix costs a lot register, so I decided to send the quaternion instead, what I want to ask is:

1. Is sending quaternion uniform to shader is better than sending the matrix?

These aren't really equivalent. Typically with joint final transforms (skinning transforms), each transform is a combination of at least a rotation and a translation. A 4x3 matrix can encode both. A single unit quaternion can only encode the rotation piece. So with a unit quaternion, you also need something to encode the translation.

Sometimes what is used instead is either:
1) a quaternion-translation pair (QT) (7 floats), OR better
2) a dual quaternion (DQ) (8 floats).

...versus 12 floats for a 4x3 matrix or 16 for a 4x4 matrix.

Both of these take up less space than a matrix, but require a bit more work in the shader to apply.

Interpolating matrices (which is mathematically nonsense) can yield joint collapse artifacts. QTs and DQs let you avoid these pretty easily. That's one of the main reasons to use them, besides the fact that passing them into the shader takes less storage.


2. Is it better to calculate the quaternion in the shader or in the CPU?

Generally, prefer precomputing your skinning transforms and uploading them to the GPU (in whatever form).


3. The way I declare the quaternion in shader, is it correct?

uniform vec4 vQuat[MAX_BONES];

That's just fine. There are other ways to pass skinning transform palettes into the GPU as well (e.g. textures).

And for a QT or a DQ, you can just use a mat2x4, among other options.

DavidJr
11-22-2015, 05:11 PM
I have some few questions.
Let say if I want to send QT/DQ, do I still need matrix? If so, the GLSL code will be like this:


mat4 BoneTransform[MAX_BONES];
uniform mat2x4 mQuat[MAX_BONES];

Doesn't it cost a lot register than the usual matrix? If that is like that, it is better to use this method (https://www.opengl.org/wiki/Skeletal_Animation), isn't it? Or I could be wrong.

I understand all what you said, thank you.

GClements
11-22-2015, 05:29 PM
Let say if I want to send QT/DQ, do I still need matrix?

No.

In this case, QT or DQ are just more compact representations of a matrix constructed from a rotation and a translation (note that the matrix can't have any scaling or skew, which is why you only need 7 values rather than 12 for a 3x4 matrix).

DavidJr
11-22-2015, 05:35 PM
Thanks, will try to search some reference about this

Dark Photon
11-23-2015, 09:07 AM
Here's are a few sources to consider:

* Skinning with Dual Quaternions (https://www.cis.upenn.edu/~ladislav/dq/index.html) (Web Page; Kavan)
* Skinning with Dual Quaternions (https://www.seas.upenn.edu/~ladislav/kavan07skinning/kavan07skinning.pdf) (Paper; Kavan et all)
* Dual Quaternions (http://www.xbdev.net/misc_demos/demos/dual_quaternions_beyond/paper.pdf) (Kenwright)

Here's a bit more info on why you might send your skinning transforms to your shaders in dual quaternions (DQ) form over quaternion-translation (QT) form:

If you have only 2-adjacent bone influences per vertex, you can use quaternion interpolation just fine. These have the same rotation center (translation), so no translation interpolation is required. This is termed Spherical Blend Skinning (SBS). For more detail on this method, see "Hardware Skinning with Quaternions" (Hejl 2004, Game Programming Gems 4) and Spherical Blend Skinning (Kavan 2005). However...

More often, you have 3+ bone influences per vertex around joints.

Using SBS with 3+ bone influences gets ugly. The issue is that you don't have a constant rotation center (translation), so you need to do some expensive SVD rotation center solves, and what you end up with is just an approximation.

This is where dual quaternion skinning (DQS) comes in: With dual quaternion skinning, interpolating between joint transforms where the rotation and translation may be different is handled cleanly as part of the method. This occurs more often in the case where you have 3 or more bone influences. See the diagrams and text in the dual quaternion papers for details. In particular, the Kavan paper above.

In any case, both of these skinning methods (DQS and SBS) result in higher quality joint skinning than standard Linear Blend Skinning (LBS), where you pass your joint transforms into the shader using matrices and just linearly-interpolate the result.

DavidJr
11-25-2015, 03:19 AM
Dark Photon, thank you for the reference you gave.
I have another problem, what is bone/joint influences? Are they bone/joint indices and weights? Because my model has a joint for each vertex.
I saw lot of tutorials using joint indices + vertex weights and for each vertex can have up to 4 joint indices and 4 vertex weights where the first joint indices is the real joint id.
I just ignored the joint indices and vertex weight in my previous code. Were they important?

For some clues I use Milkshape ASCII model and here's an example of code that uses vertex weight.

void msModel::FillJointIndicesAndWeights(const ms3d_vertex_t *vertex, int jointIndices[4], int jointWeights[4]) const
{
jointIndices[0] = vertex->boneId;
jointIndices[1] = vertex->boneIds[0];
jointIndices[2] = vertex->boneIds[1];
jointIndices[3] = vertex->boneIds[2];
jointWeights[0] = 100;
jointWeights[1] = 0;
jointWeights[2] = 0;
jointWeights[3] = 0;

if (vertex->weights[0] != 0 || vertex->weights[1] != 0 || vertex->weights[2] != 0)
{
jointWeights[0] = vertex->weights[0];
jointWeights[1] = vertex->weights[1];
jointWeights[2] = vertex->weights[2];
jointWeights[3] = 100 - (vertex->weights[0] + vertex->weights[1] + vertex->weights[2]);
}
}

This makes me confused, I want to try the DQ but then I realized that the model does not have joint indices and vertex weights.

GClements
11-25-2015, 03:50 AM
I have another problem, what is bone/joint influences? Are they bone/joint indices and weights?

Yes.


Because my model has a joint for each vertex.

When each vertex is only affected by a single bone, the calculation is straightforward. The vertex position is transformed by the bone transformation.

When vertices are affected by multiple bones, there are two ways that can be handled. Either
a) transform the vertex by each of the bones then blend the transformed positions according to the bone weights, or
b) blend the bone transformations according to the bone weights and transform the vertex by the blended transformation.

The latter produces better results, but blending transformations is somewhat more complex than blending positions.



I saw lot of tutorials using joint indices + vertex weights and for each vertex can have up to 4 joint indices and 4 vertex weights where the first joint indices is the real joint id.
I just ignored the joint indices and vertex weight in my previous code. Were they important?

Yes. Having multiple bones per vertex produces better results than having only a single bone per vertex. The difference becomes more noticeable with a more detailed mesh. It also becomes more noticeable when dealing with meshes where the attachment to the skeleton isn't rigid, e.g. loose clothing.



This makes me confused, I want to try the DQ but then I realized that the model does not have joint indices and vertex weights.
You can use QT/DQ with a one-bone-per-vertex model. With multiple bones per vertex, you can use QT/DQ and blend the transformed positions. But if you want to blend the transformations, it will be much simpler to do (or to do well) using DQ.

DavidJr
11-29-2015, 05:26 AM
GClements, thank you very much for your explanation.

However, my choice went for Quaternion Translation. I find myself is harder to understand Dual Quaternion, so I decided to rewrite my previous animation code to shader.
Here's pseudo of my code using.

// Load bones
Build joint localSkeleton matrix
Set joint translation to localSkeleton matrix
Copy localSkeleton matrix to globalSkeleton matrix
Multiply globalSkeleton parent matrix with globalSkeleton child matrix
Copy globalSkeleton to global matrix and local matrix

// Interpolation
Find two angle rotations and two joint position
Set two quaternions of the angle rotation
Do the quaternion SLERP
Convert the quaternion to quaternionMatrix
Set joint translation from joint position to quaternionMatrix
Multiply localSkeleton matrix with quaternionMatrix and store it in local matrix
Copy local matrix to global matrix
Multiply global parent matrix with global child matrix
Convert matrix to quaternion translation (matrix2x4)
Send the quaternion translation to the shader


My joints model has 3 axis angle and 3 position, and the rotation and translation are stored in globalSkeleton, global, localSkeleton, and local matrix (they all 4x4).
I got problem with transforming the vertex, in my previous code the way I transform the vertex is by multiplying it with global matrix.
I'm not really good in math, try to search around the web does not give me the answer.

If you look at the code, when transforming the vertex, I have to multiply the original vertex with globalSkeleton matrix, and after I got the result I multiply it again with global matrix then I can get the vertex I want.

Problem is, I just convert the global matrix to quaternion translation, later in the shader I need globalSkeleton matrix to transform the vertex. If I have to convert the globalSkeleton matrix to quaternion too, there's no difference with uploading a final matrix.

My point is how to get the final matrix, convert it to quaternion-translation, send it to shader, convert to matrix in shader, and then transform the vertex.
Is there any wrong or missing step I did?

Thank you very much.