How do I load textures for an FBX model (PBR based)?

I have been buying animated 3D models from TurboSquid and they fall into two starkly different categories:

  1. Ready to use - I just load the FBX file and it looks fine in my ThreeJS scene
  2. Missing textures - The loaded FBX model looks geometrically fine and the animations work, but all the surfaces are white

I want to know how to load the provided textures using the FBXLoader module. I have noticed that all of the FBX models, which I believe are PBR material based, have this set of files:

*002_BaseColor.png
*002_Emissive.png
*002_Height.png
*002_Metallic.png
*002_Roughness.png
*002_Normal.png

I have these questions:

  • How would I use the FBXLoader module to load the textures “in the right places”? In other words, how do I make the calls so that the BaseColor texture goes to the right place, the Emissive texture so it goes where it should, etc?

  • Do I need to pass it a custom material loader to the FBXLoader and if so, which one and how?

UPDATE: I trace through the ThreeJS FBX loader code I discovered that the FBX model that is not showing any textures does not have a Texture node at all. The FBX model that does show the textures does, as you can see in this screenshot of the debugger:

I’ll keep tracing. If worse comes to worse I’ll write custom code to add a Textures node with the right fields since I don’t know how to use Blender to add the textures there. My concern is of course that even if I create a Textures node at run-time, they still won’t end up in the proper place on the model.

This is the code I am currently using to load FBX models:

    this.initializeModel = function (
        parentAnimManagerObj,
        initModelArgs= null,
        funcCallWhenInitialized = null,
    ) {
        const methodName = self.constructor.name + '::' + `initializeModel`;
        const errPrefix = '(' + methodName + ') ';

        self.CAC.parentAnimManagerObj = parentAnimManagerObj;
        self.CAC.modelLoader = new FBXLoader();
        self.CAC.modelLoader.funcTranslateUrl =
            ((url) => {
                // Fix up the texture file names.
                const useRobotColor = misc_shared_lib.uppercaseFirstLetter(robotColor);

                let useUrl =
                    url.replace('low_Purple_TarologistRobot', `Tarologist${useRobotColor}`);

                return useUrl;
            });

        // PARAMETERS: url, onLoad, onProgress, onError
        self.CAC.modelLoader.load(
            MODEL_FILE_NAME_TAROLOGIST_ROBOT_1,
            (fbxObj) => {
                CommonAnimationCode.allChildrenCastAndReceiveShadows(fbxObj);

                fbxObj.scale.x = initModelArgs.theScale;
                fbxObj.scale.y = initModelArgs.theScale;
                fbxObj.scale.z = initModelArgs.theScale;

                self.CAC.modelInstance = fbxObj;

                // Set the initial position.
                self.CAC.modelInstance.position.x = initModelArgs.initialPos_X;
                self.CAC.modelInstance.position.y = initModelArgs.initialPos_Y;
                self.CAC.modelInstance.position.z = initModelArgs.initialPos_Z;

                // Create an animation mixer for this model.
                self.CAC.modelAnimationMixer = new THREE.AnimationMixer(self.CAC.modelInstance);

                // Speed
                self.CAC.modelAnimationMixer.timeScale = initModelArgs.theTimeScale;

                // Add the model to the scene.
                g_ThreeJsScene.add(self.CAC.modelInstance);

                // Don't let this part of the code crash the entire
                //  load call.  We may have just added a new model and
                //  don't know certain values yet.
                try {
                    // Play the active action.
                    self.CAC.activeAction = self.CAC.modelActions[DEFAULT_IDLE_ANIMATION_NAME_FOR_TAROLOGIST_ROBOT_1];
                    self.CAC.activeAction.play();

                    // Execute the desired callback function, if any.
                    if (funcCallWhenInitialized)
                        funcCallWhenInitialized(self);
                } catch (err) {
                    const errMsg =
                        errPrefix + misc_shared_lib.conformErrorObjectMsg(err);

                    console.error(`${errMsg} - try`);
                }
            },
            // onProgress
            undefined,
            // onError
            (err) => {
                // Log the error message from the loader.
                console.error(errPrefix + misc_shared_lib.conformErrorObjectMsg(err));
            }
        );

    }
```.

I think if anyone knew of a way to put these textures in the right place in a way that wasn’t model-specific, FBXLoader would be implemented to do that. The underlying problem is that FBX is a proprietary format, and comes in a lot of varieties that are hard to reverse-engineer. Currently FBXLoader does not support PBR materials, and won’t be able to do anything with fields like roughness and metalness. You’ll have to switch any materials created by FBXLoader to THREE.MeshStandardMaterial to use those.

If importing to Blender looks correct, you might be able to export to glTF. This is the format we recommend using when possible, and it supports PBR materials.

Hi Don,

I was afraid of that. Is OBJ format any better?

Also, I did try importing the model to Blender. Unfortunately, I am a complete dunce at Blender/animation-editors. My current guess is that the artists that created these problematic models expected the buyer to be an animator who knows how to open the target editor and make those assignments. (While other sellers thankfully did make those assignments with one of the texture sets, thus making that texture set the “default” with file references embedded in the exported FBX file).

I opened the model in Blender and I could see the textures in the resources panel, but the model itself was not showing the textures. If I knew how to use Blender to take those textures and assign them to the parts of the model they are intended for, I could then export it to gLTF as you suggest.

I am new to three and blender too and have also encountered similar problems.

To my (limited) understanding:

  1. OBJ cannnot handle transformations
  2. It is sort of luck if an object is shown correctly in blender (some files work, others do not)
  3. You may have to redo the nodes in blender. There are a number of tutorials out there, e.g. How to Fix Missing Textures in downloaded FBX/OBJ/BLENDER Models-Unity 2020 - YouTube

Hope this helps

1 Like

Much worse, actually. :sweat_smile: I think the only common format with portable support for PBR textures is glTF.

Keep in mind that you need to change the default shading option in Blender to see textures. There is a button in the upper-right of the viewport. If the textures are connected to a Principled BSDF node in Blender, they should export. I would ignore any tutorials that are not talking about export to glTF, specifically, they won’t work for PBR.

Screen Shot 2022-10-07 at 12.18.01 PM

The documentation here is good:

https://docs.blender.org/manual/en/latest/addons/import_export/scene_gltf2.html

1 Like