Fixing a GLTF avatar model after export from Blender

Our content team exports human figure models from Blender and the result looks good except for some unexplained “seams” in the elbow area. These artifacts are not present when rendered in Blender.

Screenshot 2024-05-15 at 4.02.12 PM

My best guess is that model is imported in the REST (or is it T?) pose and the normals are calculated by the loader. Then, when I apply the animation that is also present in the GLTF, the model deforms to the pose we use in the application and the seams appear.

In fact, if I add normal/tangent helpers to the model, the helper geometry appears in the REST/T pose position even though the model geometry is deformed as expected.

I’ve tried running over the meshes present in the model and using tools from BufferGeometryUtils to recreate the normals/tangents but that does not appear to have made a difference.

I’ve been unable to secure permission to share the model along with a fragment of code to illustrate the problem so I realize how hard it is to make suggestions but if there is anything I should be looking at, pointers would be much appreciated.

(three.js v164)

Your screenshot looks like the normals are inverted?

Not sure what’s going on there… are you running your gltf through a compressor or other tools?

Can you check in blender that the elbow vertices are not bound to more than 4 bones? I believe there is a 4 bone weight per vertex limit for gltf export so you might be hitting that.

Do you see the same problem when viewing the exported glTF in online viewers?

I understand it’s the client or employer’s decision, but … sometimes it’s possible to just slice out a small chunk of the mesh (say, one elbow), export, check that this reproduces the issue, and get permission to share just that chunk.

It might also be worth running the exported model through glTF Validator, that might give you some clues.

Not sure what’s going on there… are you running your gltf through a compressor or other tools?

No not yet although that is something I’ll enable once we figure this out.

Can you check in blender that the elbow vertices are not bound to more than 4 bones? I believe there is a 4 bone weight per vertex limit for gltf export so you might be hitting that.

Oh - interesting - I’ll pass that on to the content creator.

Thank you.

Do you see the same problem when viewing the exported glTF in online viewers?

I do - I tried several including yours:

donmccurdy

playvanvas

khronos

I understand it’s the client or employer’s decision, but … sometimes it’s possible to just slice out a small chunk of the mesh (say, one elbow), export, check that this reproduces the issue, and get permission to share just that chunk.

Absolutely and that was an approach I suggested without success. I have asked the content creator to try to make a new, example that has the same issue but is okay to be shared. I’ll update the post if they’re able to do it.

It might also be worth running the exported model through glTF Validator, that might give you some clues.

Oh - good idea - appears to be valid:

The Khronos Group
glTF Validator

The asset is valid.
{
    "uri": "REDACTED.glb",
    "mimeType": "model/gltf-binary",
    "validatorVersion": "2.0.0-dev.3.8",
    "validatedAt": "2024-05-16T01:50:08.541Z",
    "issues": {
        "numErrors": 0,
        "numWarnings": 1,
        "numInfos": 8,
        "numHints": 0,
        "messages": [
            {
                "code": "UNUSED_OBJECT",
                "message": "This object may be unused.",
                "severity": 2,
                "pointer": "/meshes/0/primitives/0/attributes/TEXCOORD_0"
            },
            {
                "code": "UNUSED_OBJECT",
                "message": "This object may be unused.",
                "severity": 2,
                "pointer": "/meshes/0/primitives/1/attributes/TEXCOORD_0"
            },
            {
                "code": "UNUSED_OBJECT",
                "message": "This object may be unused.",
                "severity": 2,
                "pointer": "/meshes/0/primitives/2/attributes/TEXCOORD_0"
            },
            {
                "code": "UNUSED_OBJECT",
                "message": "This object may be unused.",
                "severity": 2,
                "pointer": "/meshes/0/primitives/3/attributes/TEXCOORD_0"
            },
            {
                "code": "UNUSED_OBJECT",
                "message": "This object may be unused.",
                "severity": 2,
                "pointer": "/meshes/0/primitives/4/attributes/TEXCOORD_0"
            },
            {
                "code": "UNUSED_OBJECT",
                "message": "This object may be unused.",
                "severity": 2,
                "pointer": "/meshes/0/primitives/5/attributes/TEXCOORD_0"
            },
            {
                "code": "UNUSED_OBJECT",
                "message": "This object may be unused.",
                "severity": 2,
                "pointer": "/meshes/0/primitives/6/attributes/TEXCOORD_0"
            },
            {
                "code": "UNUSED_OBJECT",
                "message": "This object may be unused.",
                "severity": 2,
                "pointer": "/meshes/0/primitives/7/attributes/TEXCOORD_0"
            },
            {
                "code": "NODE_SKINNED_MESH_NON_ROOT",
                "message": "Node with a skinned mesh is not root. Parent transforms will not affect a skinned mesh.",
                "severity": 1,
                "pointer": "/nodes/418"
            }
        ],
        "truncated": false
    },
    "info": {
        "version": "2.0",
        "generator": "Khronos glTF Blender I/O v3.6.28",
        "resources": [
            {
                "pointer": "/buffers/0",
                "mimeType": "application/gltf-buffer",
                "storage": "glb",
                "byteLength": 3404740
            }
        ],
        "animationCount": 1,
        "materialCount": 8,
        "hasMorphTargets": false,
        "hasSkins": true,
        "hasTextures": false,
        "hasDefaultScene": true,
        "drawCallCount": 8,
        "totalVertexCount": 29443,
        "totalTriangleCount": 54106,
        "maxUVs": 1,
        "maxInfluences": 4,
        "maxAttributes": 6
    }
}

Thank you.

It looks like a bone has an extra 360 degree rotation in it somehow?!

There’s also an option to increase the limit in the Blender export options. Probably worth testing with that enabled, to see what happens, but files should ideally use fewer weights per vertex when possible.

I’ll pass both of those observations on to the content creator. They’re in a different time zone so I’ll find out tomorrow.

Thanks both.

1 Like

Oh hmmm. Blender has to convert Euler rotations to quaternions at export. Eulers can represent rotations >180º but quaternions can’t, maybe one of these bones has a larger rotation and that’s causing export bugs? :thinking:

1 Like

The content creator explained that they were able to rework the order of steps they take (it’s apparently very complex) and the result is an exported model I can use that doesn’t have the artifacts.

Having said that, if it seems to be a bug in three.js, I’d be more than happy to dig a bit deeper and try to debug it.

1 Like

Given the same result in other viewers, I don’t think it’s a bug in three.js. It might be a bug in the glTF exporter for Blender, or it might be a situation where Blender addon devs would need to say “the armature must not be set up like that”, I’m not sure…

Understood.

Thank you (both) for your help again - much appreciated.

1 Like