From 7f7815e3a2cfcd5e5c524ebed9861f1e1c50b006 Mon Sep 17 00:00:00 2001 From: Kirandeep-Singh-Khehra Date: Wed, 11 Dec 2024 22:40:39 +0530 Subject: [PATCH] [rmodels] Updated blend function signature and added function to update verts from bones Signed-off-by: Kirandeep-Singh-Khehra --- examples/models/models_animation_blending.c | 18 +++++- src/raylib.h | 3 +- src/rmodels.c | 71 +++++++++++++++++++-- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/examples/models/models_animation_blending.c b/examples/models/models_animation_blending.c index 43a772756..f6985e32c 100644 --- a/examples/models/models_animation_blending.c +++ b/examples/models/models_animation_blending.c @@ -48,6 +48,7 @@ int main(void) // Load gltf model Model characterModel = LoadModel("resources/models/gltf/robot.glb"); // Load character model +/* INFO: Uncomment this to use GPU skinning // Load skinning shader Shader skinningShader = LoadShader(TextFormat("resources/shaders/glsl%i/skinning.vs", GLSL_VERSION), TextFormat("resources/shaders/glsl%i/skinning.fs", GLSL_VERSION)); @@ -56,6 +57,7 @@ int main(void) { characterModel.materials[i].shader = skinningShader; } +*/ // Load gltf model animations int animsCount = 0; @@ -93,7 +95,10 @@ int main(void) // Update bones // Note: Same animation frame index is used below. By default it loops both animations - UpdateModelAnimationBonesWithBlending(characterModel, modelAnimations[animIndex0], animCurrentFrame, modelAnimations[animIndex1], animCurrentFrame, blendFactor); + UpdateModelAnimationBonesLerp(characterModel, modelAnimations[animIndex0], animCurrentFrame, modelAnimations[animIndex1], animCurrentFrame, blendFactor); + +// INFO: Comment the following line to use GPU skinning + UpdateModelVertsToCurrentBones(characterModel); //---------------------------------------------------------------------------------- // Draw @@ -103,12 +108,17 @@ int main(void) ClearBackground(RAYWHITE); BeginMode3D(camera); - + +/* INFO: Uncomment this to use GPU skinning // Draw character mesh, pose calculation is done in shader (GPU skinning) for (int i = 0; i < characterModel.meshCount; i++) { DrawMesh(characterModel.meshes[i], characterModel.materials[characterModel.meshMaterial[i]], characterModel.transform); } +*/ + +// INFO: Comment the following line to use GPU skinning + DrawModel(characterModel, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); DrawGrid(10, 1.0f); @@ -129,7 +139,9 @@ int main(void) //-------------------------------------------------------------------------------------- UnloadModelAnimations(modelAnimations, animsCount); // Unload model animation UnloadModel(characterModel); // Unload model and meshes/material - UnloadShader(skinningShader); // Unload GPU skinning shader + +// INFO: Uncomment the following line to use GPU skinning + // UnloadShader(skinningShader); // Unload GPU skinning shader CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/src/raylib.h b/src/raylib.h index bb25ea942..56f752c0e 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1605,7 +1605,8 @@ RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) -RLAPI void UpdateModelAnimationBonesWithBlending(Model model, ModelAnimation animA, int frameA, ModelAnimation animB, int frameB, float blendFactor); // Update model animation mesh bone matrices with blending between two poses(GPU skinning) +RLAPI void UpdateModelAnimationBonesLerp(Model model, ModelAnimation animA, int frameA, ModelAnimation animB, int frameB, float value); // Update model animation mesh bone matrices with interpolation between two poses(GPU skinning) +RLAPI void UpdateModelVertsToCurrentBones(Model model); // Update model vertices according to mesh bone matrices (CPU) RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match diff --git a/src/rmodels.c b/src/rmodels.c index a215bcc8c..c74feb6c8 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -2311,11 +2311,11 @@ void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame) // Update model animated bones transform matrices by blendin between two different given frames of different ModelAnimation(could be same too) // 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 -void UpdateModelAnimationBonesWithBlending(Model model, ModelAnimation animA, int frameA, ModelAnimation animB, int frameB, float blendFactor) +void UpdateModelAnimationBonesLerp(Model model, ModelAnimation animA, int frameA, ModelAnimation animB, int frameB, float value) { if ((animA.frameCount > 0) && (animA.bones != NULL) && (animA.framePoses != NULL) && (animB.frameCount > 0) && (animB.bones != NULL) && (animB.framePoses != NULL) && - (blendFactor >= 0.0f) && (blendFactor <= 1.0f)) + (value >= 0.0f) && (value <= 1.0f)) { frameA = frameA % animA.frameCount; frameB = frameB % animB.frameCount; @@ -2341,9 +2341,9 @@ void UpdateModelAnimationBonesWithBlending(Model model, ModelAnimation animA, in Quaternion outBRotation = animB.framePoses[frameB][boneId].rotation; Vector3 outBScale = animB.framePoses[frameB][boneId].scale; - Vector3 outTranslation = Vector3Lerp(outATranslation, outBTranslation, blendFactor); - Quaternion outRotation = QuaternionSlerp(outARotation, outBRotation, blendFactor); - Vector3 outScale = Vector3Lerp(outAScale, outBScale, blendFactor); + Vector3 outTranslation = Vector3Lerp(outATranslation, outBTranslation, value); + Quaternion outRotation = QuaternionSlerp(outARotation, outBRotation, value); + Vector3 outScale = Vector3Lerp(outAScale, outBScale, value); Vector3 invTranslation = Vector3RotateByQuaternion(Vector3Negate(inTranslation), QuaternionInvert(inRotation)); Quaternion invRotation = QuaternionInvert(inRotation); @@ -2367,6 +2367,67 @@ void UpdateModelAnimationBonesWithBlending(Model model, ModelAnimation animA, in } } +// Update model vertex data (positions and normals) from mesh bone data +// NOTE: Updated data is uploaded to GPU +void UpdateModelVertsToCurrentBones(Model model) +{ + 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 + } + } +} + // 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