Skip to content

Commit

Permalink
UpdateModelAnimation speedup (#4470)
Browse files Browse the repository at this point in the history
If we borrow from the GPU skinned animation code, we can just multiply the vertex by the matrix * weight and shave a chunk of CPU time.
  • Loading branch information
JettMonstersGoBoom authored Nov 8, 2024
1 parent 55a64f5 commit dc48978
Showing 1 changed file with 60 additions and 102 deletions.
162 changes: 60 additions & 102 deletions src/rmodels.c
Original file line number Diff line number Diff line change
Expand Up @@ -2262,108 +2262,6 @@ ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount)
return animations;
}

// Update model animated vertex data (positions and normals) for a given frame
// NOTE: Updated data is uploaded to GPU
void UpdateModelAnimation(Model model, ModelAnimation anim, int frame)
{
if ((anim.frameCount > 0) && (anim.bones != NULL) && (anim.framePoses != NULL))
{
if (frame >= anim.frameCount) frame = frame%anim.frameCount;

for (int m = 0; m < model.meshCount; m++)
{
Mesh mesh = model.meshes[m];

if (mesh.boneIds == NULL || mesh.boneWeights == NULL)
{
TRACELOG(LOG_WARNING, "MODEL: UpdateModelAnimation(): Mesh %i has no connection to bones", m);
continue;
}

bool updated = false; // Flag to check when anim vertex information is updated
Vector3 animVertex = { 0 };
Vector3 animNormal = { 0 };

Vector3 inTranslation = { 0 };
Quaternion inRotation = { 0 };
// Vector3 inScale = { 0 };

Vector3 outTranslation = { 0 };
Quaternion outRotation = { 0 };
Vector3 outScale = { 0 };

int boneId = 0;
int boneCounter = 0;
float boneWeight = 0.0;

const int vValues = mesh.vertexCount*3;
for (int vCounter = 0; vCounter < vValues; vCounter += 3)
{
mesh.animVertices[vCounter] = 0;
mesh.animVertices[vCounter + 1] = 0;
mesh.animVertices[vCounter + 2] = 0;

if (mesh.animNormals != NULL)
{
mesh.animNormals[vCounter] = 0;
mesh.animNormals[vCounter + 1] = 0;
mesh.animNormals[vCounter + 2] = 0;
}

// Iterates over 4 bones per vertex
for (int j = 0; j < 4; j++, boneCounter++)
{
boneWeight = mesh.boneWeights[boneCounter];

// Early stop when no transformation will be applied
if (boneWeight == 0.0f) continue;

boneId = mesh.boneIds[boneCounter];
//int boneIdParent = model.bones[boneId].parent;
inTranslation = model.bindPose[boneId].translation;
inRotation = model.bindPose[boneId].rotation;
//inScale = model.bindPose[boneId].scale;
outTranslation = anim.framePoses[frame][boneId].translation;
outRotation = anim.framePoses[frame][boneId].rotation;
outScale = anim.framePoses[frame][boneId].scale;

// Vertices processing
// NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position)
animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] };
animVertex = Vector3Subtract(animVertex, inTranslation);
animVertex = Vector3Multiply(animVertex, outScale);
animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
animVertex = Vector3Add(animVertex, outTranslation);
//animVertex = Vector3Transform(animVertex, model.transform);
mesh.animVertices[vCounter] += animVertex.x*boneWeight;
mesh.animVertices[vCounter + 1] += animVertex.y*boneWeight;
mesh.animVertices[vCounter + 2] += animVertex.z*boneWeight;
updated = true;

// Normals processing
// NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
if (mesh.normals != NULL)
{
animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] };
animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
mesh.animNormals[vCounter] += animNormal.x*boneWeight;
mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight;
mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight;
}
}
}

// Upload new vertex data to GPU for model drawing
// NOTE: Only update data when values changed
if (updated)
{
rlUpdateVertexBuffer(mesh.vboId[0], mesh.animVertices, mesh.vertexCount*3*sizeof(float), 0); // Update vertex position
rlUpdateVertexBuffer(mesh.vboId[2], mesh.animNormals, mesh.vertexCount*3*sizeof(float), 0); // Update vertex normals
}
}
}
}

// Update model animated bones transform matrices for a given frame
// NOTE: Updated data is not uploaded to GPU but kept at model.meshes[i].boneMatrices[boneId],
// to be uploaded to shader at drawing, in case GPU skinning is enabled
Expand Down Expand Up @@ -2411,6 +2309,66 @@ void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame)
}
}

// at least 2x speed up vs the old method
// Update model animated vertex data (positions and normals) for a given frame
// NOTE: Updated data is uploaded to GPU
void UpdateModelAnimation(Model model, ModelAnimation anim, int frame)
{
UpdateModelAnimationBones(model,anim,frame);
for (int m = 0; m < model.meshCount; m++)
{
Mesh mesh = model.meshes[m];
Vector3 animVertex = { 0 };
Vector3 animNormal = { 0 };
int boneId = 0;
int boneCounter = 0;
float boneWeight = 0.0;
bool updated = false; // Flag to check when anim vertex information is updated
const int vValues = mesh.vertexCount*3;
for (int vCounter = 0; vCounter < vValues; vCounter += 3)
{
mesh.animVertices[vCounter] = 0;
mesh.animVertices[vCounter + 1] = 0;
mesh.animVertices[vCounter + 2] = 0;
if (mesh.animNormals != NULL)
{
mesh.animNormals[vCounter] = 0;
mesh.animNormals[vCounter + 1] = 0;
mesh.animNormals[vCounter + 2] = 0;
}
// Iterates over 4 bones per vertex
for (int j = 0; j < 4; j++, boneCounter++)
{
boneWeight = mesh.boneWeights[boneCounter];
boneId = mesh.boneIds[boneCounter];
// Early stop when no transformation will be applied
if (boneWeight == 0.0f) continue;
animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] };
animVertex = Vector3Transform(animVertex,model.meshes[m].boneMatrices[boneId]);
mesh.animVertices[vCounter] += animVertex.x * boneWeight;
mesh.animVertices[vCounter+1] += animVertex.y * boneWeight;
mesh.animVertices[vCounter+2] += animVertex.z * boneWeight;
updated = true;
// Normals processing
// NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
if (mesh.normals != NULL)
{
animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] };
animNormal = Vector3Transform(animNormal,model.meshes[m].boneMatrices[boneId]);
mesh.animNormals[vCounter] += animNormal.x*boneWeight;
mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight;
mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight;
}
}
}
if (updated)
{
rlUpdateVertexBuffer(mesh.vboId[0], mesh.animVertices, mesh.vertexCount*3*sizeof(float), 0); // Update vertex position
rlUpdateVertexBuffer(mesh.vboId[2], mesh.animNormals, mesh.vertexCount*3*sizeof(float), 0); // Update vertex normals
}
}
}

// Unload animation array data
void UnloadModelAnimations(ModelAnimation *animations, int animCount)
{
Expand Down

0 comments on commit dc48978

Please sign in to comment.