assimp nodes?

Started by
9 comments, last by Waaayoff 11 years, 9 months ago
I have my own scene graph that i have set up. I am using asimp to read in my geometry but i am currently just reading in meshes. I wish to read in the animation as well. as it is i am reading in the meshes into my program at the begining and copying the meshes into a mesh class i created and then i am discarding the assimp library after that. i wish to continue this with the animation process. I dont want to port my program with assimp in it i just want to use assimp library in a different program just to collect the info and store it into my program. the problem i am having is trying to understand assimps node structure. I mean I understand the heirarchy of the library node ssytem but i dont understand how to translate a node and channels and the three keys with each channel ROT,SCALE, and POS. I just want to create a nice simple class that will hold the data associated with this assimp structure with out having vectors filled with <aiVectorKeys> or any assimp type really. i hope this makes sense. i just want to store all of the values assimp can give me in my own types void of assimp types.
J-GREEN

Greenpanoply
Advertisement
In my application, I have split the animation into three steps; each doing its own part of the math.

  1. Using Assimp to load an animation definition from a file and pre-compute as much as possible. This is done once, in the initialization.
  2. The drawing function computes the interpolation matrices (bones).
  3. The shader transforms all skins, using the bones matrices.

In your case, I suppose you want to do step 1 "off line", creating a custom format file. I think that is a good idea, and you would no longer need to link your main application with Assimp.

There are some complications regarding the keys in the aiNodeAnim. You can see it as a frame in a movie. However, there isn't 24 frames per second. Instead, there may be fewer, or more, as needed, at irregular time intervals. When you need a frame in between, you have to interpolate.

Another problem here is that the number of frames (called keys in Assimp) are not the same for rotation, scaling and position. Ift he scaling never change, there may be 0 or one of them. It depends on how much detail is needed to make a good animation. This makes the data structure a little more complicated.

Another complication is how to use interpolation. It is possible to create a transformation matrix from the rotation, location and scaling, but interpolating rotation using transformation matrices are not good. If you interpolate an object moved in an arch defined by two keys 90 degrees rotated to each other, the result will be a straight line (at best). It actually looks quite fun, but is not good. Therefore, you usually should do the interpolation on the quaternions first, then create the transformation matrix.

I use the glm math library. It supports matrices, quaternions, interpolation and all possible matrix math.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/
Find my answer in this thread. I explain how to do the animation, rendering and how to retrieve data from assimp. It also shows how you can overcome the problems mentioned in the above post :)

If you still have questions after reading it, i can answer them here.
"Spending your life waiting for the messiah to come save the world is like waiting around for the straight piece to come in Tetris...even if it comes, by that time you've accumulated a mountain of shit so high that you're fucked no matter what you do. "
ok I understand the theory behind bones indices and bone weights being passed to the shader and how that informs the shader of how much a set of up to 4 bones can have an affect on that vertex, but i dont understand logically how the bone indices works. I feel embarased but i just need its spelled out slowly i guess lol. its the whole getting the bone info storing it and using it to inform the vertex where to be. like i understant the bind pose and the offset transformation taking the vertex from bind pose space and placing that into bone space and then using the combined transfrom of that bone to move the vertex into it correct position i just am not understanding this bone indices and how it works code wis i guess i should say.
J-GREEN

Greenpanoply
oh yeah thanks for the artical very very helpful as well as your laying out of the whole process.
J-GREEN

Greenpanoply
now from what i understand of vertex shaders the main runs for every vertex. I am just not understanding what is happening here for some reason its like a snake i cant see that is sitting on my face. could some one explain what is happening here just a little bit perhaps?
[source lang="cpp"]cbuffer c_buffer
{
float4x4 World;
float4x4 WorldViewProj;
float4x4 FinalTransforms[32];
}

struct VS_OUT
{
float4 position : SV_POSITION;
};

VS_OUT VShader(float4 position : POSITION, float4 weights : BLENDWEIGHT, int4 boneIndices : BLENDINDICES)
{
VS_OUT output;

float4 p = float4(0.0f, 0.0f, 0.0f, 1.0f);
float lastWeight = 0.0f;
int n = 3;

for(int i = n; i > 0; i--)
{
lastWeight += weights;
p += weights * mul(FinalTransforms[boneIndices], position);
}

lastWeight = 1.0f - lastWeight;
p += lastWeight * mul(FinalTransforms[boneIndices[0]], position);
p.w = 1.0f;

output.position = mul(WorldViewProj, p);
return output;
}[/source]


I should also add that i am using opengl and am used to using GLSL when it comes to shader so i am also a little puzzled by the way your reading in uniform variables. I assume that this "float4 position : POSITION, float4 weights : BLENDWEIGHT, int4 boneIndices : BLENDINDICES" is HLSL's way of reading in those variables?
J-GREEN

Greenpanoply
In OpenGL, I am doing as follows (support 3 bones per vertex):
uniform mat4 projectionMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 bonesMatrix[64];
in vec4 vertex;
in vec3 weights;
in vec3 joints;
void main(void){
mat4 animationMatrix =
weights[0] * bonesMatrix[int(joints[0])] + weights[1] * bonesMatrix[int(joints[1])] + weights[2] * bonesMatrix[int(joints[2])];
gl_Position = projectionMatrix*viewMatrix*modelMatrix*animationMatrix*vertex;
}


Notice which ones are uniforms. These are updated every draw(), while the others are the same every time from a VBO.

[font=courier new,courier,monospace]bonesMatrix[/font]: Up to 64 joints can be used in a model. It is a uniform, as the same list of bones are used for all vertices.
[font=courier new,courier,monospace]vertex[/font]: This is a vertex from the mesh that is going to be animated by 0 to 3 bones.
[font=courier new,courier,monospace]joints[/font]: The index of three joints for the current vertex.
[font=courier new,courier,monospace]weights[/font]: The weights to be used for the three joints. There is one set of weights for each vertex.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/
Bone indices indicate which bones affect a certain vertex the most. For example, assume you're processing a vertex on your player's elbow. That vertex will probably only have 2 bone indices (and hence 2 weights, since #indices = #weights). One bone index will be for the upper arm bone, and one for the lower arm bone. Now these indices are used to retrieve bone matrices from the entire skeleton, which is defined as FinalTransforms in my shader, and as bonesMatrix in larspensjo's.

Simply put, bone indices are just numbers that let you know where a certain bone's transformation is in the matrix array. That way you can retrieve the required matrix and multiply it by the weight.

As for my shader, you can ignore the loop. It just ensures that the total weight is unity if the vertex is affected by less than 4 bones. I have no idea why i didn't do that in the preprocessing. Just make sure the total bone weight for a vertex is equal to one when you're importing the data from assimp, and use larspensjo's shader :)
"Spending your life waiting for the messiah to come save the world is like waiting around for the straight piece to come in Tetris...even if it comes, by that time you've accumulated a mountain of shit so high that you're fucked no matter what you do. "
larspensjo, I do appreciet your code and bettween it and Waaayoff's links and explanation i am very much drawing a very clear picture of how this is done. I just have three more questions. in your GLSL vertex shader I assume your "joints = bones"? and if i wanted to add another bones influence on the vertex i just add in another

"weights[3] * bonesMatrix[int(joints[3])]"

also could you possibly provide the small vertex struct you use to store these values. for example

struct Vertex {
vec3 VertsPos;
vec3 Norms;
vec3 texCoords;
vec3 weights;
vec3 joints;
};

as well as the way you are setting up the VBO to receive joints and weights?

I have everything else ready to go i just want to cross check how one sect up a vbo to receive these other values. if not that is fine you have done more then enough to help. but please let me know about the joint bone equivilant if you can. thanks
J-GREEN

Greenpanoply
I did it like this (using source tag, but not working):
[source lang="java"]
const int AREA1 = vertexCount*sizeof (struct vertexData);
const int AREA2 = vertexCount*sizeof (struct weight);
const int AREA3 = vertexCount*3*sizeof (float); // 3 bones per vertex, max

glGenBuffers(1, &fBufferId);
glBindBuffer(GL_ARRAY_BUFFER, fBufferId);
glBufferData(GL_ARRAY_BUFFER, AREA1+AREA2+AREA3, 0, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, AREA1, vertexData);
glBufferSubData(GL_ARRAY_BUFFER, AREA1, AREA2, weights);
glBufferSubData(GL_ARRAY_BUFFER, AREA1+AREA2, AREA3, joints);
glGenBuffers(1, &fIndexBufferId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fIndexBufferId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize*sizeof indexData[0], indexData, GL_STATIC_DRAW);
glGenVertexArrays(1, &fVao);
glBindVertexArray(fVao);

glEnableVertexAttribArray(StageOneShader::Normal); // Using pre defined enums
glEnableVertexAttribArray(StageOneShader::Vertex);
glEnableVertexAttribArray(StageOneShader::Texture);
glEnableVertexAttribArray(StageOneShader::SkinWeights);
glEnableVertexAttribArray(StageOneShader::Joints);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fIndexBufferId); // Will be remembered in the VAO state

glVertexAttribPointer(StageOneShader::Normal, 3, GL_FLOAT, GL_FALSE, sizeof (struct vertexData), &p->fNormal[0]);
glVertexAttribPointer(StageOneShader::Texture, 2, GL_FLOAT, GL_FALSE, sizeof (struct vertexData), &p->fTexture[0]);
glVertexAttribPointer(StageOneShader::Vertex, 3, GL_FLOAT, GL_FALSE, sizeof (struct vertexData), &p->fVertex[0]);
glVertexAttribPointer(StageOneShader::SkinWeights, 3, GL_FLOAT, GL_FALSE, sizeof (glm::vec3), (void *)skinOffset);
glVertexAttribPointer(StageOneShader::Joints, 3, GL_FLOAT, GL_FALSE, 3*sizeof (float), (void *)jointOffset);

glBindVertexArray(0);
[/source]
Some comment about this:

  • My vertex data doesn't include the weights and bones index. Your way of having all data in one struct is probably more efficient. I did it this way because of historical reasons (there was no weights or bones index in the first attempt).
  • The generic vertex attribute index is a predefined constant, to enable me to use the same VAO on more than one shader.
  • I am thinking of using GL_BYTE for the bones index, but I didn't get that to work.

To debug your animation, you can do some tests:

  • Change the shader so as to use the identity matrix instead of bones matrix. That should draw the mesh in bind pose.
  • Do the same thing, but use bone indices to make a color. That way, you can verify that the right bones are selected by the indices.
  • Instead, use weight information to make a color, that way you can test that the weights are correctly transferred.

I created a blog entry with many details: http://ephenationope...-in-opengl.html.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/

This topic is closed to new replies.

Advertisement