Using BBMOD for animations only

Intermediate Lightweight BBMOD

The initial version of BBMOD started off as a small library designed for loading and rendering 3D animated models in GameMaker. However, since then it's been slowly growing into a comprehensive framework for creating 3D games. We recognize that not everyone may be interested in utilizing BBMOD's material system or other features and would prefer to leverage it solely for 3D animations. We have made this specific use case feasible once again with the release 3.20.1, where this part of the library was separated into a tiny subfolder. This tutorial is a quick rundown of BBMOD for intermediate users of GameMaker who wish to add 3D animated models into their projects and are not interested in other parts of the library.

Contents

Converting models

First of all, we will need to convert our model from one of the many support file formats (see full list here) to the BBMOD file format. This can be done using BBMOD CLI, which is a tool run from the command line. It is free and always included in all releases of BBMOD. It can be found in the Included Files/Data/BBMOD folder. To convert a model to BBMOD, simply run BBMOD.exe C:\Path\To\Model.fbx. Once the model is converted, we can put it into the Included Files folder to be able to load it in-game.

Tip: Run BBMOD.exe -h to see a full list of available arguments.

Our Patrons with sufficient tier also have access to BBMOD GUI, which is a windowed application with live preview of converted models, animations and materials.

Importing BBMOD library

To be able to load BBMOD models and animations in-game, we will need to import the BBMOD library into our GameMaker project. In this case, all we need to import is the BBMOD/Core/Base folder, which contains only the absolute basics required for rendering 3D animated models. No material system and no shaders are included in this folder, which means we will need to make our own ones!

Loading models

Normally, every loaded model uses material BBMOD_MATERIAL_DEFAULT, but since we haven't imported the material system, we don't have its definition. Therefore we will need to add the following line somewhere into our project, before we start loading models. This will make all loaded models use the null (white) texture.

#macro BBMOD_MATERIAL_DEFAULT -1

After that is set up, we can start loading models easily using the BBMOD_Model struct like so:

/// @desc Create event
model = new BBMOD_Model("Data/Character.bbmod").freeze();

Models can be also freezed so they are submitted faster. Materials of loaded models are configured using the Materials property, which is an array of BBMOD materials or, like in our case, regular textures:

model.Materials[@ 0] = sprite_get_texture(SprPlayer, 0);

Tip: Instead of assigning the textures directly to array indices, we can also use method set_material with material names as strings. Material names can be found in the _log.txt file created during model conversion.

Finally, to submit the model to the GPU, call its method submit:

/// @desc Draw event
model.submit();

Note: BBMOD's material system would do this automatically, but here we need to call functions like gpu_set_zwriteenable, gpu_set_ztestenable and shader_set ourselves!

Depending on arguments we pass to BBMOD.exe, our model's vertex format may or may not be compatible with GameMaker's built-in shaders. Following is an example of a pass-through vertex shader for non-animated BBMOD models:

attribute vec3 in_Position;
attribute vec3 in_Normal;
attribute vec2 in_TextureCoord0;
//attribute vec2 in_TextureCoord1;
//attribute vec4 in_Colour;
attribute vec4 in_TangentW; // (tangent.xyz, bitangentSign)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position, 1.0);
    v_vColour = vec4(1.0); //in_Colour;
    v_vTexcoord = in_TextureCoord0;
    //vec3 normal = in_Normal;
    //vec3 tangent = in_TangentW.xyz;
    //vec3 bitangent = cross(normal, tangent) * in_TangentW.w;
}

Adding animations

As we can see below, loading animations using the BBMOD_Animation struct is just as easy as loading models:

/// @desc Create event
animIdle = new BBMOD_Animation("Data/CharacterIdle.bbanim");
animWalk = new BBMOD_Animation("Data/CharacterWalk.bbanim");

Model and animation data can be loaded once and shared between many instances, but every drawn animated model needs to have its own BBMOD_AnimationPlayer:

/// @desc Create event
animationPlayer = new BBMOD_AnimationPlayer(model);

We need to call its update method each frame to compute animation frames. To change between animations played, we can use the change method:

/// @desc Step event
var _anim = keyboard_check(ord("W")) ? animWalk : animIdle;
animationPlayer.change(_anim, true); // true = loop the animation
animationPlayer.update(delta_time);

To submit an animated model, we will use method submit of the animation player instead of the model's submit:

/// @desc Draw event
shader_set(ShAnimated);
animationPlayer.submit();
shader_reset();

This method automatically passes the bone transformation data to uniform bbmod_Bones. Following is an example of a pass-through vertex shader that accepts this data and uses it to transform the model drawn:

attribute vec3 in_Position;
attribute vec3 in_Normal;
attribute vec2 in_TextureCoord0;
//attribute vec2 in_TextureCoord1;
//attribute vec4 in_Colour;
attribute vec4 in_TangentW; // (tangent.xyz, bitangentSign)
attribute vec4 in_BoneIndex;
attribute vec4 in_BoneWeight;

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

// The maximum number of bones. Must be the same as defined in BBMOD_AnimationPlayer!
#define BBMOD_MAX_BONES 128
// Bone transforms are passed to this uniform when animationPlayer.submit() is called.
uniform vec4 bbmod_Bones[2 * BBMOD_MAX_BONES];

vec3 QuaternionRotate(vec4 q, vec3 v)
{
    return (v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v));
}

vec3 DualQuaternionTransform(vec4 real, vec4 dual, vec3 v)
{
    return (QuaternionRotate(real, v)
        + 2.0 * (real.w * dual.xyz - dual.w * real.xyz + cross(real.xyz, dual.xyz)));
}

void GetBoneTransform(out vec4 real, out vec4 dual)
{
    // Source:
    // https://www.cs.utah.edu/~ladislav/kavan07skinning/kavan07skinning.pdf
    // https://www.cs.utah.edu/~ladislav/dq/dqs.cg
    ivec4 i = ivec4(in_BoneIndex) * 2, j = i + 1;

    vec4 real0 = bbmod_Bones[i.x], real1 = bbmod_Bones[i.y],
        real2 = bbmod_Bones[i.z], real3 = bbmod_Bones[i.w],
        dual0 = bbmod_Bones[j.x], dual1 = bbmod_Bones[j.y],
        dual2 = bbmod_Bones[j.z], dual3 = bbmod_Bones[j.w];

    float sign1 = (dot(real0, real1) < 0.0) ? -1.0 : 1.0,
        sign2 = (dot(real0, real2) < 0.0) ? -1.0 : 1.0,
        sign3 = (dot(real0, real3) < 0.0) ? -1.0 : 1.0;

    real1 *= sign1; dual1 *= sign1;
    real2 *= sign2; dual2 *= sign2;
    real3 *= sign3; dual3 *= sign3;

    real = real0 * in_BoneWeight.x + real1 * in_BoneWeight.y
        + real2 * in_BoneWeight.z + real3 * in_BoneWeight.w;

    dual = dual0 * in_BoneWeight.x + dual1 * in_BoneWeight.y
        + dual2 * in_BoneWeight.z + dual3 * in_BoneWeight.w;

    float norm = 1.0 / length(real);
    real *= norm;
    dual *= norm;
}

void main()
{
    vec4 boneReal, boneDual;
    GetBoneTransform(boneReal, boneDual);
    vec3 position = DualQuaternionTransform(boneReal, boneDual, in_Position);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(position, 1.0);
    v_vColour = vec4(1.0); //in_Colour;
    v_vTexcoord = in_TextureCoord0;
    vec3 normal = QuaternionRotate(boneReal, in_Normal);
    vec3 tangent = QuaternionRotate(boneReal, in_TangentW.xyz);
    vec3 bitangent = cross(normal, tangent) * in_TangentW.w;
}

Freeing memory

When a model is no longer needed, it needs to be freed from memory using its destroy method. Animations and animation players do not allocate any memory that would need to be freed manually, so they do not have this method.

/// @desc Clean Up event
model = model.destroy();

Common issues

One of the most common problems BBMOD users encounter is that their animated models are distorted in weird ways. Usually this happens because their models have scaling, which cannot be represented by dual quaternions (which BBMOD uses for animations instead of matrices). To fix this issue, simply open your model in Blender, select it and use "Object > Apply > All Transforms To Deltas" from the top menu inside of the main viewport, then export and convert the model again.


And that's it! These are the basics of using BBMOD just for rendering of 3D animated models in your GameMaker projects. Please have a look at the tutorials page, where you can find other tutorials like Drawing animated models, Transforming bones or Adding bone attachments, that will help you utilize BBMOD's animation system to the fullest potential. Happy game making!

Could not find what you were looking for?

We are still working on more tutorials. If you need additional help with BBMOD, please have a look at the documentation or join our Discord community. Thank you for your patience.

Support the development

Support us in developing BBMOD, get priority assistance and more of our amazing tools as a reward!

Become Patron

Don't miss out on a thing!

Create an account, subscribe to newsletter and get notified on new releases, tutorials and special offers.

Register Log in