OBJLoader + MTlLoader: How to render obj + mtl + jpeg/png or obj + mtl correctly?

Need to render .obj + mtl 3d asset from zip archive. I’m using as examples examples/models/obj/male02 (obj + mtl + jpeg) and my example obj + mtl only
My loader setup looks like:

 const objFile = Object.keys(zip.files).find(fileName => fileName.endsWith('.obj'));
        const mtlFile = Object.keys(zip.files).find(fileName => fileName.endsWith('.mtl'));

        if (objFile && mtlFile) {
            const mtlContent = await zip.file(mtlFile).async('string');
            const manager = new THREE.LoadingManager();
            manager.onStart = function ( url, itemsLoaded, itemsTotal ) {
                console.log( 'Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' );

            manager.onLoad = function ( ) {
                console.log( 'Loading complete!');

            manager.onProgress = function ( url, itemsLoaded, itemsTotal ) {
                console.log( 'Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' );

            manager.onError = function ( ...rest ) {
                console.log( 'There was an error loading ',  rest );
            const mtlLoader = new MTLLoader(manager).setPath(path);
            const materials = mtlLoader.parse(mtlContent);
            console.log('mtls', materials);

            // Extract and load .obj file
            const objContent = await zip.file(objFile).async('string');
            const objLoader = new OBJLoader();
            const object = objLoader.parse(objContent);
            object.traverse((child) => {
                if (child instanceof THREE.Mesh) {
                    child.material.side = THREE.DoubleSide;
            console.log('async obj res', object);
            return object;

I’m getting darks textures preview in both cases:

Original view by macos:

What could be wrong here?
Mine example:
objectmtlzip.zip (1.9 MB)
when I’m trying to render it via http://localhost:8083/examples/?q=obj#webgl_loader_obj_mtl it doesn’t work at all

The screenshots you shared show there are errors when loading your textures, second one shows this is because it’s loading them from URLs that are incorrect. Have you looked into this?

Have no idea where these URLs come from. All files are placed in one folder.
Do you know how to override loading textures?

@MadMax888 those paths are specified in your MTL file. You should edit it and change those 3 lines that include texture files (RandomDirt01.jpg is listed in 2 different lines), from:

map_Kd /Volumes/MATTS BACK/Tor Fick texture tut/Textures/RandomDirt01.jpg
map_Ks /Library/Application Support/Luxology/Content/Assets/Images/Metal/Metal_Scratched_01.png


map_Kd RandomDirt01.jpg
map_Ks Metal_Scratched_01.png

This way you are specifying that those texture files are in the same folder.

By the way, the zip file you attached did not have any textures and if you want to disable them all then just remove those lines mentioned above.

MTL files are text files so any text editor can open them, like Notepad in Windows for example.

1 Like

If you need this to work without modifying the source .obj+.mtl

You can use the THREE.LoadingManager to intercept those URLs and return the assets that you want…



1 Like

Thanks for the mention. I have resolved the issue for my case. The three.js docs is fine for me, but just modifying the URL is not working. I’m using function callback for returning base64 encoded image URL.

  // ... 
  async function imgLoad(zip: JSZip): Promise<Map<string, string>> {
        const imgFileKeys = Object.keys(zip.files).filter(fileName => fileName.match(/\.(jpg|png)$/i));
        const imgMap = new Map();

        await Promise.all(imgFileKeys.map(async (key) => {
            const content = await zip.file(key).async('base64');
            imgMap.set(key, content);

        return imgMap;
  // ... 
  const manager = new THREE.LoadingManager();
  const images = await this.imgLoad(zip);

  manager.setURLModifier((fileUrl) => {
      const fkey = data.split('/').slice(-1).pop();
      return `data:image/jpeg;base64,${images.get(fkey)}`;
1 Like