From dc489786b022bc7bc5de7a3fa4cadc0881916dce Mon Sep 17 00:00:00 2001 From: Jett <30197659+JettMonstersGoBoom@users.noreply.github.com> Date: Fri, 8 Nov 2024 08:28:39 -0500 Subject: [PATCH] UpdateModelAnimation speedup (#4470) 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. --- src/rmodels.c | 162 +++++++++++++++++++------------------------------- 1 file changed, 60 insertions(+), 102 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index daabbe20..6ddb53ae 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -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 @@ -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) {