Recommended setup for separate gltf models and animations?

I have a bunch of characters that all share the same set of animations - walk, run, etc. Each character is in a separate .glb file, exported from blender. The animations and the armature are in a separate .blend file which each character links to. I’m exporting each blender file to an individual .glb file, so that means I have N+1 .glb files, one for each character and then an additional one for the shared animations.

Also, to make things more interesting, the characters use multiple materials, so what gets exported is a group with a bunch of individual skinned meshes as its children.

Is there anything special I need to do to get these characters to animate in three.js? Right now when I load the character I just see a pile of random polygons, which I’m guessing is because the bones are missing. Loading in the animation into a mixer and calling play seems to have no effect.

I know a few users have done this workflow, but I haven’t seen a real writeup of how it’s done unfortunately. I’d suggest starting by trying to import the animation .glb — which will need to contain the armature and some sort of mesh attached, even if it’s just a cube or a single vertex — and try to get the animation to display with THREE.SkeletonHelper.

Once that is working it should be easier to transfer the animation or the armature to your character files. Note that it’s important for both the animation and character files to contain identical armatures.

OK I’ve got that bit working. Here’s what I had to do:

  1. Two .blend files, one with the armature + actions, the other with the character mesh.
  2. Very important: Make sure that the armature is in a blender collection - it is much easier to link to a collection than to the raw resource nodes.
  3. Also, make sure to add an NLA track for each action you want to export, even if you are not using NLAs, it’s needed to ensure that the exporter will actually include the action. Otherwise, your GLB file only gets the actions that are visible in the current scene. (I tried using fake users but it was insufficient.)
  4. The blender file with the character should “link” to the collection inside the blender armature file.
  5. Click on the linked armature, and go to Object -> relations -> create library override.
  6. Now you can parent the skinned mesh to the armature, and run various actions to see how well they look.
  7. Export both blender files to GLB.
  8. Now, in the three.js program, load the two .glb files using the GLTFLoader. Grab the skinned mesh, the armature, and the animation clip from the gltf object. Note that the animation clips will be named based on the name of the NLA track, not on the name of the action (if you don’t use NLAs then the name of the action is used).
  9. Re-parent the skinned mesh as a child of the armature.
  10. Create an animation mixer for the armature and feed the clip into it.
  11. Add the armature to the scene.

OK, so all that being done, there are a few more things I need to figure out. What I have described is more of a proof of concept than it is a production-ready solution. In an environment where you have dozens of characters running around on screen, I’m not sure that cloning the armature for each individual character is going to be efficient, but I don’t know whether or not that can be avoided.

Also, all of the objects in my scene have outlines - people think of them as “toon outlines” but actually this is more of an Alphonse Mucha / Art Nouveau look. The outlines are done by drawing the back-facing polygons with a shader that displaces along the normal direction. (This requires that every model be a seamless mesh, or at least any seams in the mesh must be concave - that’s easy to do by adding a bevel modifier with a bevel width of 0 to every model, so that when the vertices are displaced outwards the bevels inflate).

For non-skinned objects this is pretty simple, just clone the mesh (the buffer geometry is shared between them) and overwrite the material slot with the outline material. You can make the outline mesh a child of the original mesh if you wipe the local transform.

However, this doesn’t work for skinned meshes and I am not sure exactly what the right recipe is yet. For performance reasons I’d like to combine all of the various bits and pieces of the character into a single geometry buffer for the outlines, since the outlines always draw the same material. But that means understanding the structure of a skinned mesh well enough to take one apart and re-assemble it, with all the vertex weights intact.

Got the outlines working, I just needed to add the skinning chunks in my outline shader.

BTW, the way I am doing shader code is using a custom TypeScript template tag function:

const outlineVert = glsl`
#define STANDARD
uniform float outlineThickness;

${ShaderChunk.skinning_pars_vertex}

void main() {
  ${ShaderChunk.beginnormal_vertex}
  ${ShaderChunk.skinbase_vertex}
  ${ShaderChunk.skinnormal_vertex}
  ${ShaderChunk.begin_vertex}
  transformed += objectNormal * outlineThickness;
  ${ShaderChunk.skinning_vertex}
  ${ShaderChunk.project_vertex}
}`;

The glsl tag function is just an identity function, but the VSCode syntax highlighter for GLSL code can be set up to recognize this. It means that I can embed my GLSL code directly in JS strings and get full syntax highlighting, without having to make separate .glsl files.

2 Likes

It would take a lot of work to avoid this, and probably requires changing the default vertex shaders. I think cloning the armature is reasonable unless you have quite a lot of characters onscreen. If you decide to try it, see https://github.com/mrdoob/three.js/issues/9606.