Hello everyone!
I am trying to implement instanced animated 3D model rendering.
Before making instance of mesh, I need to animate 3D model in shader by using matrix which I get from bone/weight transformation info via assimp.
The idea is based on
http://http.developer.nvidia.com/GPUGems3/gpugems3_ch02.html
http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html
and much helpful discussion on forum
Then I failed like this. Seems the astroBoy is animating but like monster
Am I wrong with matrix multiplication order? Or getting wrong born matrix?
basic steps are below
- load model
- get boneID & weight and set them to attribute
- gather bone matrices of every animation
- make bone matrix texture on each udpate and send it to shader
- shader make transform on mesh using bone matrix
the main code part is like this.
#include "Model.h"
void Model::setup()
{
nInstance = 1;
for (int i = 0; i < nInstance; i++)
{
animPcts.push_back(ofRandom(1.0));
}
shader.load("shader/modelShader");
loader.loadModel("model/astroBoy_walk.dae", true);
loader.setPosition(0, 0, 0);
loader.setScale(0.5, 0.5, 0.5);
loader.setRotation(0, 180, 1, 0, 0);
loader.setLoopStateForAllAnimations(OF_LOOP_NORMAL);
loader.playAllAnimations();
loader.setPausedForAllAnimations(true);
ofLoadImage(diffuseTex, "model/boy_10.tga");
modelMatrix = loader.getModelMatrix();
genMeshPieces();
bindBoneIDAndWeightToAttribute();
populateEveryAnimationMatrix();
modelTransTexture.allocate(nInstance * 4, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT);
}
void Model::update()
{
float t = Globals::ELAPSED_TIME;
// model transform
vector<float> modelMats;
for(int i = 0; i < nInstance; i++)
{
ofVec3f pos = ofVec3f::zero();
pos.y = -20;
float scale = 4.0;
ofMatrix4x4 mat = ofMatrix4x4::newTranslationMatrix(ofVec3f::zero());
mat.scale(scale, scale, scale);
mat.translate(pos);
float* ptr = mat.getPtr();
for(int j = 0; j < matCell; j++)
{
modelMats.push_back(ptr[j]);
}
}
modelTransTexture.loadData(&modelMats[0], nInstance * 4, 1, GL_RGBA);
// piece transform
for (auto &ap : animPcts)
{
ap += 0.025;
if (ap > 1.0)
ap = 0.0;
}
for (auto &piece : pieces)
{
vector<float> animMat;
BoneMatricesPerFrame bmpf = piece.bmfs.at(ofMap(animPcts.at(0),
0.0, 1.0,
0, piece.bmfs.size(),
true));
for (auto mat : bmpf.matrices)
{
float* ptr = mat.getPtr();
for(int j = 0; j < matCell; j++)
{
animMat.push_back(ptr[j]);
}
}
piece.instancedAnimTextre.loadData(&animMat[0], nInstance * 4 * maxNBone, 1, GL_RGBA);
}
}
void Model::draw(ofMatrix4x4 camMvpMatrix)
{
diffuseTex.bind();
shader.begin();
ofPushStyle();
GLuint matLoc = glGetUniformLocation(shader.getProgram(), "camMvpMatrix");
if (matLoc != -1)
{
glUniformMatrix4fv(matLoc, 1, GL_FALSE, camMvpMatrix.getPtr());
}
shader.setUniformTexture("modelTransTexture", modelTransTexture, 1);
for (auto p : pieces)
{
shader.setUniformTexture("animationTexture", p.instancedAnimTextre, 2);
shader.setUniformTexture("diffuseTex", diffuseTex, 3);
p.material.begin();
ofEnableBlendMode(p.blendmode);
p.meshHelper->vbo.drawElements(GL_TRIANGLES,
p.meshHelper->indices.size());
p.material.end();
}
ofPopStyle();
shader.end();
diffuseTex.unbind();
}
void Model::genMeshPieces()
{
loader.setPositionForAllAnimations(0.0);
loader.update();
for (int i = 0; i < loader.getNumMeshes(); i++)
{
MeshPiece mp;
mp.meshHelper = &loader.getMeshHelper(i);
ofMaterial material = mp.meshHelper->material;
mp.material = material;
ofBlendMode blend = mp.meshHelper->blendMode;
mp.blendmode = blend;
mp.instancedAnimTextre.allocate(nInstance * 4 * maxNBone, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT);
pieces.push_back(mp);
}
}
void Model::bindBoneIDAndWeightToAttribute()
{
int maxBone = 4;
for (auto &piece : pieces)
{
bool bFoundBone = true;
aiMesh* aimesh = piece.meshHelper->mesh;
// init bone info
piece.boneInfos.resize(aimesh->mNumVertices);
for (int i = 0; i < aimesh->mNumVertices; i++)
{
piece.boneInfos.at(i).bornID.resize(maxBone);
piece.boneInfos.at(i).weight.resize(maxBone);
for (int j = 0; j < maxBone; j++)
{
piece.boneInfos.at(i).bornID.at(j) = 0;
piece.boneInfos.at(i).weight.at(j) = 0.0;
}
}
// no bone, set weight to 1.0.
if (aimesh->mNumBones == 0)
{
for (auto &bi : piece.boneInfos)
{
bi.bornID.at(0) = 0;
bi.weight.at(0) = 1.0;
}
bFoundBone = false;
}
if (bFoundBone)
{
// lets populate
for (int boneID = 0; boneID < aimesh->mNumBones; boneID++)
{
for (int weightID = 0; weightID < aimesh->mBones[boneID]->mNumWeights; weightID++)
{
int vertexID = aimesh->mBones[boneID]->mWeights[weightID].mVertexId;
float weight = aimesh->mBones[boneID]->mWeights[weightID].mWeight;
// Select the 4 largest weights
for (int slotID = 0; slotID < maxBone; slotID++)
{
if (piece.boneInfos.at(vertexID).weight.at(slotID) < weight)
{
for (int shuff = 3; shuff > slotID; shuff--)
{
piece.boneInfos.at(vertexID).weight.at(shuff) = piece.boneInfos.at(vertexID).weight.at(shuff-1);
piece.boneInfos.at(vertexID).bornID.at(shuff) = piece.boneInfos.at(vertexID).bornID.at(shuff-1);
}
piece.boneInfos.at(vertexID).weight.at(slotID) = weight;
piece.boneInfos.at(vertexID).bornID.at(slotID) = boneID;
break;
}
}
}
}
}
// bind attribute
vector<float> boneIDs;
vector<float> weights;
for (auto bi : piece.boneInfos)
{
for (int i = 0; i < maxBone; i++)
{
boneIDs.push_back(bi.bornID.at(i));
weights.push_back(bi.weight.at(i));
}
}
int boneIDLoc = shader.getAttributeLocation("boneIDs");
piece.meshHelper->vbo.setAttributeData(boneIDLoc,
&boneIDs[0],
4,
boneIDs.size(),
GL_STATIC_DRAW,
sizeof(ofVec4f));
int weightLoc = shader.getAttributeLocation("weights");
piece.meshHelper->vbo.setAttributeData(weightLoc,
&weights[0],
4,
weights.size(),
GL_STATIC_DRAW,
sizeof(ofVec4f));
// for (auto bi : piece.boneInfos)
// {
// bi.dump();
// }
}
}
void Model::populateEveryAnimationMatrix()
{
float step = 0.05;
for (float i = 0; i < 1.0; i += step)
{
loader.setPositionForAllAnimations(i);
loader.update();
for (auto &piece : pieces)
{
aiMesh* aimesh = piece.meshHelper->mesh;
vector<aiMatrix4x4> aiBoneMatrices(aimesh->mNumBones);
for (int a = 0; a < aimesh->mNumBones; a++)
{
aiBone* bone = aimesh->mBones[a];
aiNode* node = loader.getAssimpScene()->mRootNode->FindNode(bone->mName);
aiBoneMatrices[a] = bone->mOffsetMatrix;
aiNode* tempNode = node;
while (tempNode)
{
aiBoneMatrices[a] = tempNode->mTransformation * aiBoneMatrices[a];
tempNode = tempNode->mParent;
}
}
vector<ofMatrix4x4> boneMatrices;
for (auto aim : aiBoneMatrices)
{
ofMatrix4x4 ofmat = ofMatrix4x4::newTranslationMatrix(ofVec3f::zero());
aiMatrix4x4 temp = aim;
temp.Transpose();
ofmat.set(temp.a1, temp.a2, temp.a3, temp.a4,
temp.b1, temp.b2, temp.b3, temp.b4,
temp.c1, temp.c2, temp.c3, temp.c4,
temp.d1, temp.d2, temp.d3, temp.d4);
boneMatrices.push_back(ofmat);
}
while (boneMatrices.size() < 4)
{
ofMatrix4x4 ofmat = ofMatrix4x4::newTranslationMatrix(ofVec3f::zero());
boneMatrices.push_back(ofmat);
}
BoneMatricesPerFrame bmf;
bmf.matrices = boneMatrices;
piece.bmfs.push_back(bmf);
}
}
loader.setPositionForAllAnimations(0);
loader.update();
}
and vert shader
#version 150
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 textureMatrix;
uniform mat4 camMvpMatrix;
uniform sampler2DRect modelTransTexture;
uniform sampler2DRect animationTexture;
uniform vec3 light_pos = vec3(100.0, 100.0, 100.0);
in vec4 position;
in vec2 texcoord;
in vec3 normal;
in vec4 boneIDs;
in vec4 weights;
out VSOUT
{
vec2 texCoordVarying;
vec3 N;
vec3 L;
vec3 V;
vec4 weightCol;
} vsout;
mat4 getMat(sampler2DRect tex, float step)
{
float y = 0;
float x = step * 4.0;
mat4 mat = mat4(texture(tex, vec2((x+0.5), y+0.5)),
texture(tex, vec2((x+1.5), y+0.5)),
texture(tex, vec2((x+2.5), y+0.5)),
texture(tex, vec2((x+3.5), y+0.5)));
return mat;
}
void main()
{
mat4 transformMatrix = getMat(modelTransTexture, 0.0);
vec4 mPos = position;
mPos += weights[0] * (getMat(animationTexture, boneIDs[0]) * position);
mPos += weights[1] * (getMat(animationTexture, boneIDs[1]) * position);
mPos += weights[2] * (getMat(animationTexture, boneIDs[2]) * position);
mPos += weights[3] * (getMat(animationTexture, boneIDs[3]) * position);
vec4 vPos = transformMatrix * mPos;
vsout.N = mat3(modelViewMatrix) * normal;
vsout.L = light_pos - vPos.xyz;
vsout.V = -vPos.xyz;
vsout.texCoordVarying = (textureMatrix * vec4(texcoord.x, texcoord.y, 0, 1)).xy;
vsout.weightCol = weights;
gl_Position = camMvpMatrix * vPos;
}
Any help is appreciated!
Thank you.
A