Search code examples
javascript3dthree.jswebgltextures

Applying texture to 3D-models in WebGL, using external links


I have just learnt how to apply texture to my 3D-models in WebGL using .mtl files (and .obj files). Applying texture works great when the image is saved on my computer. Here is an example of what my .mtl file looks like:

newmtl Earth_MATERIAL
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 1

map_Kd Earth.png

This works very well. However I want to publish my simulation and therefore I have to refer to the image somehow. My first thought was to upload the image to DropBox and use that link, but this did not work:

...
map_Kd https://dl.dropbox.com/s/t4cm3vzsbx21crc/Earth.png?dl=0

The error I get when I run this code is:

Error: WebGL warning: texImage2D: Element is write-only, thus cannot be uploaded.

To load the texture and model I use an MTLloader and OBJloader. Here are the loaders I am using:

MTLloader: link

OBJloader: link

I also use ThreeJS Library:

ThreeJS: link

EDIT: Problem solved thanks to Jave! Here is the result for those who want to see: https://code.sololearn.com/WWY9cXN6OVBX/


Solution

  • The MTLLoader has a method named setTexturePath which you can use to set the base path from which to load your textures. Since you are using an external source for the files (dropbox) you might also have to call setCrossOrigin(true). I have attached a commented example of using these methods to load your texture.

    Some things you might want to consider:

    • Usually you would put your textures in the same directory as the mtl-file, or a subdirectory so you can reference them directly (as just Earth.png or textures/Earth.png).
    • If you are using mipmaps the texture dimensions will have to be power of two (2048 x 1024) for example. Your image is not and is automatically resized when loaded. It would be more efficient to manually resize it beforehand.
    • Your texture is 5689 x 2844 pixels. This is very large for a texture and you should probably reduce it. Some mobile devices might not even be able to use textures larger than 2048 x 2048.

    const canvas = document.getElementById("canvas");
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(45, 1, 1, 10);
    const renderer = new THREE.WebGLRenderer({ canvas });
    const light = new THREE.AmbientLight();
    scene.add(light);
    camera.position.set(0, 0, 6);
    
    const mesh = new THREE.Mesh(new THREE.SphereBufferGeometry( 1, 32, 32 ));
    scene.add(mesh);
    
    
    function update(){
      scene.rotation.y = 0.001 * performance.now();
      renderer.render(scene, camera);
    
      requestAnimationFrame(update);
    }
    
    update();
    
    //This is just creating a data-url from the div containing the mtl-data, it is not really neccessary, but it will make the example more similiar to loading a file.
    const data = document.getElementById("mtlplaceholder").textContent;
    const dataurl = "data:text/plain;base64," + btoa(data);
    
    const loader = new THREE.MTLLoader();
    
    //To load texture files not in the same folder as the mtl-file, we need to call setTexurePath with the proper base path. Note that every texture will have to be available at this same base url, which might not be the case when shared from dropbox.
    //The complete URL is  https://dl.dropbox.com/s/t4cm3vzsbx21crc/Earth.png
    loader.setTexturePath("https://dl.dropbox.com/s/t4cm3vzsbx21crc/");
    //To use cross-origin loading of textures, call setCrossOrigin(true):
    loader.setCrossOrigin(true);
    //Finally. load the mtl file (in this case the "file" is the dataurl created above):
    loader.load(dataurl, res => {
      //MTLLoader.load creates a MTLLoader.MaterialCreator, to get the actual material we have to call create with the name of the material we want.
      //If you are using the OBJLoader, use preload instead and then objloader.setMaterials.
      const loadedMat = res.create("Earth_MATERIAL");
      //Finally, we set the material on the mesh.
      mesh.material = loadedMat;
    }, e => console.log("error", e));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
    <script src="https://rawgit.com/mrdoob/three.js/53838a2/examples/js/loaders/MTLLoader.js"></script>
    
    <canvas id="canvas" width="400" height="400"></canvas>
    
    <div id="mtlplaceholder" style:"white-space:pre">
    newmtl Earth_MATERIAL
    Ns 96.078431
    Ka 1.000000 1.000000 1.000000
    Kd 0.640000 0.640000 0.640000
    Ks 0.000000 0.000000 0.000000
    Ke 0.000000 0.000000 0.000000
    Ni 1.000000
    d 1.000000
    illum 1
    
    map_Kd Earth.png
    </div>