Relative/absolute paths to texture images

I’m struggling with referencing texture images from a .GLB file using either relative or absolute URIs.

Given glTF content of:

...
images: [
  {
    name: 'copper_wires',
    mimeType: 'image/jpeg',
    uri: '../textures/copper_wires.jpg'
  },
]
...

I receive the console error
Unable to load from /3D/assemblies/5/optimized_model.glb: /images/0/uri: '../textures/copper_wires.jpg' is invalid
In this case the library doesn’t even attempt to fetch the file.

If instead I attempt an absolute URL:

...
images: [
  {
    name: 'copper_wires',
    mimeType: 'image/jpeg',
    uri: "https://server.com/3D/textures/copper_wires.jpg"
  },
]
...

Then the SceneLoader tries to fetch /3D/assemblies/5/https://server.com/3D/textures/copper_wires.jpg
and fails.

It looks like the loader tries to just append the uri to the base path of the current .GLB file.

I found this forum post where @Deltakosh advises against using relative URLs.

If I want to put image files somewhere other than below the GLB directory, what options do I have?

By way of background - I’d like to pull out textures from models and store them in a single /textures directory so that they can be cached between model loads. I’m already using a material library for comment textures across ALL models, but this will handle a situation where there’s a texture that’s only used in a handful.

I do not actually use my own .babylon / json exporter for Blender, but thought I could use a ‘…/…’ as an argument for FileLoader.Append().

I do use .json files for many other things though, and have had to take precautions to KEEP it from being cached & not showing my changes. Are you sure a .glb is not being cached?

Would defer to someone who actually uses FileLoader.

I’m sure I can pass relative URLs such as SceneLoader.Append('../model.glb') - the issue is with …/ URLs within the GLB file itself, referring to textures.

Caching the GLB is great but let’s say I’ve got three .GLB files which all refer to a 60kb texture image eg wires.png. If I embed it in the .GLB, each one will include its own copy. What I ideally want to do is to refer to a single /3D/textures/wires.png from each GLB and then the texture file itself will also be cached between the model loads.

As stated in the thread you pointed to, the use of “…” in the uri is not valid, so you can’t use such urls in a glb/gltf file.

cc @bghgary to find out if your use case is simply not supported or if there are other ways to achieve it.

[…]

What comes to mind is to use .gltf assets instead of .glb. That way you can have multiple .gltf files in the same directory and a textures/ subdirectory to which the uri of each .gltf file can point.

1 Like

The glTF loader explicitly disallows “…” in the URI for security/privacy reasons. Perhaps we can add a flag to allow this.

If you want to use absolute URL in a glTF, the rootUrl passed to the SceneLoader must be empty string. We can perhaps update the loader to support absolute URLs even when rootUrl is not empty.

Another option is to structure the files such that the assets always access files below the current location (e.g., all the shared textures are in a texture folder with the glTF/glb in a root folder).

Thanks! Your comment pointed me in the direction of a hacky monkey patch that gets things working for me. I do think we could perhaps either improve the docs or add some support for this. This is my understanding:

According to the glTF spec a .glb file can contain resources (texture images) - these can be either Base64 encoded URIs, or relative or absolute URLs to the resource file.

For security/privacy reasons, BabylonJS only supports URIs below the rootUrl of the file being loaded. Absolute and relative URIs are not supported.

Given a file at https://example.com/3D/models/model.glb

...
images: [
  {
    name: 'copper_wires',
    mimeType: 'image/jpeg',
    uri: 'textures/copper_wires.jpg' 
  },
]
...

:white_check_mark: this works

...
images: [
  {
    name: 'copper_wires',
    mimeType: 'image/jpeg',
    uri: '../textures/copper_wires.jpg' 
  },
]
...

:x: This fails with an invalid URL error

...
images: [
  {
    name: 'copper_wires',
    mimeType: 'image/jpeg',
    uri: 'https://example.com/textures/copper_wires.jpg' 
  },
]
...

:x: This attempts to fetch the file at https://example.com/models/https://example.com/textures/copper_wires.jpg
and fails.

I have got this working with a ludicrously hacky solution that rewrites the URLs in the loader:

const loader = BABYLON.SceneLoader.Append("/3D/models", "model.glb", this.scene, (s) => {
  // Do something with loaded scene  
})! as any;

  
// Once the JSON in the loaded file has been parsed, rewrite the relative URLs to 
// something that will pass Babylon's URL validation and can later be intercepted 
// by the below preprocessUrlAsync function.
loader.onParsed = (data:{json:{images:{uri:string}[]}}) => {
  data.json.images = data.json.images.map( (image) => {
    return {
      ...image,
      uri: `${image.uri.replace('../../textures','//3D/textures')}`,
    }
  });
}

// Before loading a resource, take the previously rewritten URL that Babylon 
// is requesting and rewrite it.
loader.preprocessUrlAsync = (url:string):Promise<string> => {
  return Promise.resolve(`${url.split('//')[1]}`);
}

I think if you do this, you will be able to pass absolute urls. I haven’t tried though.

const loader = BABYLON.SceneLoader.Append("", "/3D/modelsmodel.glb", this.scene, (s) => {
  // Do something with loaded scene  
})! as any;

We don’t currently officially support absolute urls or .. in urls. If you want support for either of these, please file a feature request on GitHub or better yet contribute a change. :wink:

1 Like

Just to close this off, I have it working perfectly with this little bit of hackery. I tried digging into the loader code in the hope of seeing a quick patch but it’s quite a complex execution path. I’ll continue working on this project and then come back to it and hopefully contribute a feature when I clean up the code. Thanks all!

2 Likes

Sorry for the necro, but I ran into this today when trying to have a single texture directory shared for all the gltf models. Each model was in a separate directory with a parallel directory for textures, so the path to the image was “…/textures/foo.png”.

I’ll see your hacked solution and raise (lower?) you one:

import {GLTFLoader} from "@babylonjs/loaders/glTF/2.0/glTFLoader"
//@ts-ignore
GLTFLoader._ValidateUri = function(uri: string) { return true; }