Building a Material Library per project


I am trying to organize my code and I would like to separate materials from the rest.

To do that, I’ve created a ES6 module, named matLib.js where I would define MeshStandardMaterial with textures and other parameters. I thought I could built an object (the library) containing many other objects (the materials) but I run into problems.

  • when I pass a map via the loader function, it turns dark grey in a THREE.MeshBasicMaterial shader and is hidden with a THREE.MeshStandardMaterial. THREE.MeshStandardMaterial is the shader I want to use.
  • I tried to load an object that is a function but when I return the shader, it doesn’t work. I had this idea because I wanted to lazy-load textures when once the material is called (not just 60 textures at once).

I have prepared a zip files that should work fine. In the folder, you need to “npm install three” and all dependencies should be installed and a new node_modules folder. The material library (matLib) is located in ./assets/matLib/. I am working with draco-compressed gltf files, if that matters, and try to apply the library on lines 94 to 96 (index.html)

If you have a question, I’ll try to be responsive at I am in vacation so I have time to work on this. As you might see, I have ideas but I lack knowledge on how to implement them correctly, so other implementations are welcome. Thank you for any help!

Capture (334.3 KB)

A look in the browser console reveals that your texture can’t be loaded (HTTP 404). You are using the wrong path. Try it with ./assets/matLib/wall.jpg. Besides, when you overwrite diffuse textures of a glTF asset, you have to set the following properties (see documentation).

var map = loader.load( './assets/matLib/wall.jpg' );
map.flipY = false;
map.encoding = THREE.sRGBEncoding;

matLib.mat03 represents a method so you can just assign it to material. Instead, you have to do this:

gltf.scene.children[ 3 ].material = matLib.mat03();

This method notation is of course bad. You should name it like a factory method. Somethine like createStandardMaterial().

Sorry for the loader path, I had it written but it got deleted when I was packing everything.

I’ve edited the object mat01 with the following:

mat01: new THREE.MeshBasicMaterial({
map: loader.load( ‘./assets/matLib/wall.jpg’ ),
flipY: false,
encoding: THREE.sRGBEncoding,

However, the properties are not recognized. I’m trying to pack it all in an object… to separate it from the init() function. Is that possible? Should I use something different than objects?

That does not work. You have to first define the texture and then assign the final map object to the constructor parameter of MeshBasicMaterial.

ok. got it! This works: (in matLib.js)

var loader = new THREE.TextureLoader()
var map = loader.load( ‘./assets/matLib/wall.jpg’ );
map.flipY = false;
map.encoding = THREE.sRGBEncoding;

export const matLib = {
mat01: new THREE.MeshStandardMaterial({
roughness: 1

With regard to the function object, I corrected what you showed me to call it, however it does not return anything useful (the shader is broken: pitch black). Do you have an idea?

Write your onLoad() callback like so:

      function (gltf) {

          gltf.scene.children[4].material = matLib.mat01;
          gltf.scene.children[5].material = matLib.mat02;
          gltf.scene.children[3].material = matLib.mat03();

          gltf.scene.traverse(function (node) {

            if (node instanceof THREE.Mesh) {
              node.material.envMap = hdrCubeRenderTarget.texture;
              node.material.envMapIntensity = 1.5 // darker than original HRDI. Maybe because of very low res.
              if (node.isMesh || node.isLight) node.castShadow = true;
              if (node.isMesh || node.isLight) node.receiveShadow = true;


Otherwise you change your materials multiple times.

1 Like

Thank you, @Mugen87!

This solve the second issue I had. I did not notice I was in the traverse function and as you say, mat03 might have been changed several times and break. I can even do something like this:

export const matLib = {
mat03: function () {
var map = loader.load( ‘./assets/matLib/wall.jpg’ );
map.flipY = false;
map.encoding = THREE.sRGBEncoding;
return new THREE.MeshStandardMaterial({ map, roughness: 1 })

… that can be invoked with: matLib.mat03()

That means everything is encapsulated and easy to use. Thanks!