PBRCustomMaterial support in GLTF loader

At Babylon.js/pbrCustomMaterial.ts at master · BabylonJS/Babylon.js · GitHub it is shown how you can make a custom pbr-material. It derives from the PBRMaterial.

I want to be able to take a gltf-file I find on the internet and load it, but have the material objects be created from my custom-pbr-material instead of the standard PBRMaterial.

I access the GLTF-loader via BABYLON.SceneLoader.LoadAssetContainerAsync.

Is this possible? If so how?

I did my own little investigation and I think that it is not possible. I can register a new extension but that is not what I am after. The choice to create a PBRMaterial seems to be done in

loaders/src/glTF/2.0/glTFLoader.ts :: _createDefaultMaterial. (a private function)

My thinking is that a factory function that is used instead of the constructor for the PBRMaterial would solve my problem.

Any thoughts?

ping @bghgary

1 Like

I think my blog about this has exactly what you want?


https://www.babylonjs-playground.com/#20XT9A#4

You can override createMaterial to create whatever material you want.

5 Likes

That worked wonderfully! I had to change it into typescript but it worked (even with babylon in NPM). Beautiful.

The new material has to extend the base PBRMaterial since the loader checks the type (using instanceof) so I can’t supply my own material that has all the properties but has a separate implementation. But that is ok for me so I am super happy. Thanks!

Here is my code for reference. Note that I try to stay close to the default method whereas you wanted to do a special one so I have other attributes on the material compared to your code.

// See https://babylonjs.medium.com/extending-the-gltf-loader-in-babylon-js-588e48fb692b for more information
class CustomLoader implements GLTF2.IGLTFLoaderExtension, BABYLON.IDisposable {
  public name: string = 'custom'
  public enabled = true
  private loader: GLTF2.GLTFLoader

  constructor (loader: GLTF2.GLTFLoader) {
    this.loader = loader
  }

  // @ts-ignore
  // tslint:disable-next-line: naming-convention
  public createMaterial (context: string, material: IMaterial, babylonDrawMode: number): BABYLON.Material {
    this.loader.babylonScene._blockEntityCollection = true // Since we are loading into asset container
    const babylon_material = new MYPBRMaterial(material.name || 'material' + material.index, this.loader.babylonScene)
    this.loader.babylonScene._blockEntityCollection = false
    // Moved to mesh so user can change materials on gltf meshes: babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? Material.CounterClockWiseSideOrientation : Material.ClockWiseSideOrientation;
    babylon_material.fillMode = babylonDrawMode
    babylon_material.enableSpecularAntiAliasing = true
    // babylonMaterial.useRadianceOverAlpha = !this._parent.transparencyAsCoverage
    // baylonMaterial.useSpecularOverAlpha = !this._parent.transparencyAsCoverage
    babylon_material.transparencyMode = PBRMaterial.PBRMATERIAL_OPAQUE
    babylon_material.metallic = 1
    babylon_material.roughness = 1
    return babylon_material
  }

  public dispose () {
    // TODO: Are we supposed to dispose this.loader? Is it ours? I think not!
  }

  public static factory_function (loader: GLTF2.GLTFLoader): GLTF2.IGLTFLoaderExtension {
    return new CustomLoader(loader)
  }
}
1 Like

You are free to use your own material not derived from PBRBaseMaterial if you also override loadMaterialPropertiesAsync.

Glad it works though!

Source:

Babylon.js/glTFLoader.ts at d25bc29091d47f51bd2f0f98fb0f16d25517675f · BabylonJS/Babylon.js (github.com)

Hi @bghgary,

I am trying to do the same thing with ES6 modules but I can’t make it work.

Here is my code, model is loaded but I do not have the console message and the model is not using PBRCustomMaterial class. I tried several things but I can’t find what I am doing wrong.

import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader';
import { GLTFLoader } from '@babylonjs/loaders/glTF/2.0/glTFLoader';
import { PBRCustomMaterial } from '@babylonjs/materials/';

//* Replace material loader
function Custom(loader) {
    this.name = 'custom';
    this.enabled = true;

    this.createMaterial = function (context, material, babylonDrawMode) {
        console.log('use custom');

        const custom = new PBRCustomMaterial(`custom${material.index}`, loader.babylonScene);
        return custom;
    };
}

GLTFLoader.RegisterExtension('custom', (loader) => {
    return new Custom(loader);
});

SceneLoader.ImportMesh('', folder, file, scene, (meshes) => {
        console.log(meshes[1].material);
    }, null, (scene, message) => {
        console.error(`Error loading model: ${url}`, message);
    });

Thanks a lot for your help :wink:

This may help

Hello @MarianG, thanks for your answer and this playground.
This is a good idea to put the Fragment_Before_FragColor function directly in createMaterial!

My issue is not in using these functions but to make it work with ES6 modules which is not replicable in the playground, unfortunately.

es-6 :muscle:

3 Likes

Ok, I finally understood what was happening.

The model I was loading had no material :sweat_smile:

Thanks a lot @MarianG for helping me!

3 Likes

Hello I am re-opening this topic because I need your help in order to go further.

I am trying to play with the material alpha of an imported model using PBRCustomMaterial.
I manage to do it on a simple plane as you can see in this playground:

But when I do the same thing with a model imported I can’t use the varying vAlbedoUV or vUV even if my model does have UVMapping: https://playground.babylonjs.com/#6T8SBT#1

Can’t you enlighten me on how to correctly use UV in PBRCustomMaterial?

I’m not totally sure what you are trying to do because you put some invalid shader code in the second PG link, but I think this is what you want?

PG: https://playground.babylonjs.com/debug.html#6T8SBT#4

1 Like

Yes, this was exactly what I was looking for @bghgary!

Now to go forward, I then need to clone the material in order to be able to change it on each mesh independently. But after cloning, the onBind function is not called anymore as you can see in this playground:

Is it a bug or what should I do to fix that? :pray: :pray: :pray:

Not sure. @Evgeni_Popov Any ideas?

clone is not implemented on CustomMaterial / PBRCustomMaterial, see:

There’s a workaround in a PG linked by @nasimiasl in this thread.

Also, there’s is an error in your PG, m.material can be null, so you should test for it.

3 Likes

Thanks @Evgeni_Popov, updating the clone function is indeed one way to get PBRCustomMaterial working in my use case!

Eventually, I need to see meshes behind the one with the alpha effect like in this playground where I see the HDR behind the plane: https://www.babylonjs-playground.com/#0GG6MR#72

But with this custom code, I only get white color: https://playground.babylonjs.com/debug.html#6T8SBT#12
I tried to add needAlphaBlending or needDepthPrePass and also tried to use Fragment_Before_FragColor instead of Fragment_Custom_Alpha but none have worked.

What should I do? :hugs:

You must override needAlphaBlendingForMesh instead of needAlphaBlending:

Note that you won’t see the hdr background because you have other meshes under the mesh you create line 57.

3 Likes

Awesome! Thanks a lot, @Evgeni_Popov!

Have a great day :hugs:

Hello, I’m back with new questions about using PBRCustomMaterial.

There’s a problem because of the use of the shader variable vAlbedoUV. As you can see in this playground, if I remove the albedoTexture from the material, I get the following shader error 'vAlbedoUV': undeclared identifier.
First question: What other variable could I use to make the effect work even without an albedoTexture? I’m used to seeing vUV used in most BabylonJS shader examples, but it seems we can’t use it with PBRCustomMaterial.
Second question: Is there somewhere in the documentation a list of BJS Shader variables created ? So that we know how to add glsl cutom code more easily?

Thanks guys!