How do Bones Work?

Started by gudenau, June 13, 2016, 01:00:21

Previous topic - Next topic

gudenau

I would like to implement a model animation system at some point. I get the basic idea behind them, link the vertexes to various bones in the model. But I would like to know more about how you would implement this, I feel that there is a way to offload most of that onto the GPU and just send a few bytes per changed bone instead of resending all the vertexes. All the info I am finding is about making materials or models in blender, but I would like to create the backend that can render this.

orange451

I am at work now. When I return home in 6 hours, if no one else has replied, I will give you all the information you need! :)

[Edit] Sitting at work again today. Realised I forgot to help you out! I am sorry!
I will make a note in my phone! :)

[Edit 2]
Hi again!

In my current project I have my skeletal animation system written with a child-parent relationship. There is one root bone, and every other bone is a child of the root bone ( child bones can have more child bones in them as well ).

Each bone has three variables:
- Offset Matrix
- Bind Matrix
- Absolute Matrix

The Offset Matrix is a matrix that defines the transformation to get from the parent bone to the current bones orientation. For example, is the parent bone is at ( 2, 0 ), and the child bone is at ( 8, 1 ), then the offset matrix would be ( 6, 1 ). This is the matrix that you most likely start with when you load your model data into RAM. You have to construct the next two yourself.

The Absolute Matrix is a matrix that expresses the absolute ( not relative ) matrix of the bones transformation. Unlike the Offset Matrix, the absolute matrix is not dependent on the bone before it. To calculate this matrix, I recursively loop through each bone, and store the current temporary absolute matrix. It starts as an identity matrix with the root bone, then for each child bone I multiply it by the offset matrix and store it to that bone as its absolute matrix. Then I pass it to the next child, and repeat the process. The absolute matrix always changes throughout the animation, because it is linked to the current bone positions/rotations.

The Bind Matrix is a matrix that is used to reconstruct the correct position of the vertex around a bone when doing GPU skinning. The bind matrix is constructed like this:
- Loop through each bone after calculating absolute matrices
- Take absolute matrix for current bone, store it to a new matrix-variable, and invert.
The bind matrix does not change after being set. It represents the inverted T-Pose of your model.


Now for the GPU skinning!

You need to calculate the weights/influences. Most skeletal animation stuff that I've come across have 4 of these, which is very useful to have more realistic animations!
For the animated model, I normally have two Vector4f arrays with a max length of 128. 128, because I don't plan on having more than 128 bones. This is up to you though.
I loop through each bone from the source animation data, and store the weights and influences to the vector arrays.
The influence is normally just the pointer id used to locate the bone. So if the first bone is an influence, it would be 0.
The x component represents bone 1.
The y component represents bone 2.
The z component represents bone 3.
The w component represents bone 4.

You also need to have your own vertex format for the mesh. In my engine it is:
0 - Position
1 - Normal
2 - Texture
3 - Color
4 - Influences
5 - Weight amount

You attach the influence and weight data the same way you do with the other data using: glVertexAttribPointer().



Before you render your animated model, you need to upload a Bone Buffer to the shader.
To construct this, loop through each bone, take the absolute matrix and bind matrix, multiply them together, and store to buffer.


Here's a shader I wrote to skin the mesh:
#version 330

uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;

uniform mat4 worldMatrix;
uniform mat3 normalWorldMatrix;
uniform vec4 modelColor;

uniform mat4 boneMat[64];

layout (location = 0) in vec3 in_Position;
layout (location = 1) in vec3 in_Normal;
layout (location = 2) in vec2 in_TexCoords;
layout (location = 3) in vec4 in_Color;
layout (location = 4) in vec4 blendIndices;
layout (location = 5) in vec4 blendWeights;

out vec3 vNormal;
out vec3 vViewSpacePos;
out vec2 vTexCoords;
out vec4 vColor;

void main(){
	vec4 newVertex = vec4(0.0);
	vec4 newNormal = vec4(0.0);
	int index = 0;
	
	index = int(blendIndices.x);
	newVertex += boneMat[index] * vec4(in_Position, 1.0) * blendWeights.x;
	newNormal += boneMat[index] * vec4(in_Normal, 0.0) * blendWeights.x;
	
	index = int(blendIndices.y);
	newVertex += boneMat[index] * vec4(in_Position, 1.0) * blendWeights.y;
	newNormal += boneMat[index] * vec4(in_Normal, 0.0) * blendWeights.y;
	
	index = int(blendIndices.z);
	newVertex += boneMat[index] * vec4(in_Position, 1.0) * blendWeights.z;
	newNormal += boneMat[index] * vec4(in_Normal, 0.0) * blendWeights.z;
	
	index = int(blendIndices.w);
	newVertex += boneMat[index] * vec4(in_Position, 1.0) * blendWeights.w;
	newNormal += boneMat[index] * vec4(in_Normal, 0.0) * blendWeights.w;
	
	vec4 viewPos = viewMatrix * worldMatrix * vec4(newVertex.xyz, 1.0);
	gl_Position = projectionMatrix * viewPos;
	vViewSpacePos = viewPos.xyz;
	
	vNormal = normalMatrix * normalWorldMatrix * newNormal.xyz;
	vTexCoords = in_TexCoords;
	vColor = in_Color * modelColor;
}




[edit 3]
http://ruh.li/AnimationVertexSkinning.html
Good read if you're interested

gudenau

Quote from: orange451 on June 13, 2016, 04:35:13
I am at work now. When I return home in 6 hours, if no one else has replied, I will give you all the information you need! :)

[Edit] Sitting at work again today. Realised I forgot to help you out! I am sorry!
I will make a note in my phone! :)

[Edit 2]
Hi again!

In my current project I have my skeletal animation system written with a child-parent relationship. There is one root bone, and every other bone is a child of the root bone ( child bones can have more child bones in them as well ).

Each bone has three variables:
- Offset Matrix
- Bind Matrix
- Absolute Matrix

The Offset Matrix is a matrix that defines the transformation to get from the parent bone to the current bones orientation. For example, is the parent bone is at ( 2, 0 ), and the child bone is at ( 8, 1 ), then the offset matrix would be ( 6, 1 ). This is the matrix that you most likely start with when you load your model data into RAM. You have to construct the next two yourself.

The Absolute Matrix is a matrix that expresses the absolute ( not relative ) matrix of the bones transformation. Unlike the Offset Matrix, the absolute matrix is not dependent on the bone before it. To calculate this matrix, I recursively loop through each bone, and store the current temporary absolute matrix. It starts as an identity matrix with the root bone, then for each child bone I multiply it by the offset matrix and store it to that bone as its absolute matrix. Then I pass it to the next child, and repeat the process. The absolute matrix always changes throughout the animation, because it is linked to the current bone positions/rotations.

The Bind Matrix is a matrix that is used to reconstruct the correct position of the vertex around a bone when doing GPU skinning. The bind matrix is constructed like this:
- Loop through each bone after calculating absolute matrices
- Take absolute matrix for current bone, store it to a new matrix-variable, and invert.
The bind matrix does not change after being set. It represents the inverted T-Pose of your model.


Now for the GPU skinning!

You need to calculate the weights/influences. Most skeletal animation stuff that I've come across have 4 of these, which is very useful to have more realistic animations!
For the animated model, I normally have two Vector4f arrays with a max length of 128. 128, because I don't plan on having more than 128 bones. This is up to you though.
I loop through each bone from the source animation data, and store the weights and influences to the vector arrays.
The influence is normally just the pointer id used to locate the bone. So if the first bone is an influence, it would be 0.
The x component represents bone 1.
The y component represents bone 2.
The z component represents bone 3.
The w component represents bone 4.

You also need to have your own vertex format for the mesh. In my engine it is:
0 - Position
1 - Normal
2 - Texture
3 - Color
4 - Influences
5 - Weight amount

You attach the influence and weight data the same way you do with the other data using: glVertexAttribPointer().



Before you render your animated model, you need to upload a Bone Buffer to the shader.
To construct this, loop through each bone, take the absolute matrix and bind matrix, multiply them together, and store to buffer.


Here's a shader I wrote to skin the mesh:
#version 330

uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;

uniform mat4 worldMatrix;
uniform mat3 normalWorldMatrix;
uniform vec4 modelColor;

uniform mat4 boneMat[64];

layout (location = 0) in vec3 in_Position;
layout (location = 1) in vec3 in_Normal;
layout (location = 2) in vec2 in_TexCoords;
layout (location = 3) in vec4 in_Color;
layout (location = 4) in vec4 blendIndices;
layout (location = 5) in vec4 blendWeights;

out vec3 vNormal;
out vec3 vViewSpacePos;
out vec2 vTexCoords;
out vec4 vColor;

void main(){
	vec4 newVertex = vec4(0.0);
	vec4 newNormal = vec4(0.0);
	int index = 0;
	
	index = int(blendIndices.x);
	newVertex += boneMat[index] * vec4(in_Position, 1.0) * blendWeights.x;
	newNormal += boneMat[index] * vec4(in_Normal, 0.0) * blendWeights.x;
	
	index = int(blendIndices.y);
	newVertex += boneMat[index] * vec4(in_Position, 1.0) * blendWeights.y;
	newNormal += boneMat[index] * vec4(in_Normal, 0.0) * blendWeights.y;
	
	index = int(blendIndices.z);
	newVertex += boneMat[index] * vec4(in_Position, 1.0) * blendWeights.z;
	newNormal += boneMat[index] * vec4(in_Normal, 0.0) * blendWeights.z;
	
	index = int(blendIndices.w);
	newVertex += boneMat[index] * vec4(in_Position, 1.0) * blendWeights.w;
	newNormal += boneMat[index] * vec4(in_Normal, 0.0) * blendWeights.w;
	
	vec4 viewPos = viewMatrix * worldMatrix * vec4(newVertex.xyz, 1.0);
	gl_Position = projectionMatrix * viewPos;
	vViewSpacePos = viewPos.xyz;
	
	vNormal = normalMatrix * normalWorldMatrix * newNormal.xyz;
	vTexCoords = in_TexCoords;
	vColor = in_Color * modelColor;
}




[edit 3]
http://ruh.li/AnimationVertexSkinning.html
Good read if you're interested

Thanks for the answer, I am sure this will help a lot when I get to this point!