Recent Material Pipeline Changes

There seems to have been some major material pipeline (PBR Classes) changes and i cant find what they are.

I had tons of custom pbr material classes that i have been developing for over a year now.

Everything from Custom GLSL Shader, Custom PBR Shaders and Multi Layer Splatmap Terrain Shaders…

Now as of the latest 4.1.0 Beta 1… They are all shot to shit… Nothing works anymore… No errors… Just dont work anymore.

Can someone please (Especially The Person Working On Material Classes)… What changed…

Now i have to track back thru TONS of shader material sub classes… Looking for GOD ONLY KNOWS WHAT CHANGED


Any info would help me track down WTF so i can fix all my material code.

Shit… I found my issues…

Small changes made to the Base PushMaterial


The big ones… Someone cleaned up the PBR Materials… Is used to be LINES of code that setup various little shader material properties and now most have been moved to MATERIAL HELPER type functions… I was overriding some functions and that screwed things up for me

1 Like

PBR Changes again… This just KILLS my my whole custom PBR shaders.

I had a whole shader system built in unity… For the Babylon Toolkit. It has been working great for wuite some time. But recently … SOMEONE is making breaking changes to PBRMaterial and all the Custom shader define blocks…

I can tell what the issues, but now NONE of my Unity based shaders work as of 4.15 ALPHA.

I think these breaking changes were introduced with all this new Node Material Editor stuff… My question is then… Why would you not make a separate new material class for all this new stuff and leave what was working in PBRMaterial be.

Anyways… I need to fix whatever changed that killed the way my PBRMaterial (Which i call UniversalMaterials…)

Here is my source for one of UniversalAlbedoMaterial.ts class and it supportted custom shaders from Unity… That now no longer work… I think the text replace code for some of the define chunks no longer work

     * Babylon universal albedo material pro class
     * @class UniversalAlbedoMaterial - All rights reserved (c) 2020 Mackey Kinard
    export class UniversalAlbedoMaterial extends BABYLON.PBRMaterial {
        protected universalMaterial:boolean = true;
        protected locals:BABYLON.UniversalShaderDefines = null;
        protected terrainInfo:any = null;
        private _defines: BABYLON.PBRMaterialDefines = null;
        private _uniforms:string[] = [];
        private _samplers:string[] = [];
        private _textures: { [name: string]: BABYLON.Texture } = {};
        private _vectors4: { [name: string]: BABYLON.Vector4 } = {};
        private _floats: { [name: string]: number } = {};
        private _isCreatedShader: boolean;
        private _createdShaderName: string;
        protected enableShaderChunks:boolean;
        protected materialShaderChunks: BABYLON.UniversalAlbedoChunks;
        protected updateShaderChunks():void {}
        public constructor(name: string, scene: Scene) {
            super(name, scene);
            this.locals = new BABYLON.UniversalShaderDefines();
            this._defines = null;
            this.enableShaderChunks = false;
            this.materialShaderChunks = new BABYLON.UniversalAlbedoChunks();
            this.customShaderNameResolve = this._buildCustomShader;
        public getClassName(): string {
            return "UniversalAlbedoMaterial";
        public getShaderName(): string {
            return "pbr";
        public getShaderChunk(): string {
            return null;
        public getShaderDefines(): BABYLON.PBRMaterialDefines {
            return this._defines;

        /* Shader Material Property Accessor Functions */

        public getTexture(name:string): BABYLON.Texture {
            return this._textures[name];
        public getVector4(name:string): BABYLON.Vector4 {
            return this._vectors4[name];
        public getFloat(name:string): number {
            return this._floats[name];
        public setTexture(name: string, texture: BABYLON.Texture, initialize:boolean = false): BABYLON.UniversalAlbedoMaterial {
            if (initialize === true) this.checkSampler(name);
            this._textures[name] = texture;
            return this;
        public setVector4(name: string, value: BABYLON.Vector4, initialize:boolean = false): BABYLON.UniversalAlbedoMaterial {
            if (initialize === true) this.checkUniform(name);
            this._vectors4[name] = value;
            return this;
        public setFloat(name: string, value: number, initialize:boolean = false): BABYLON.UniversalAlbedoMaterial {
            if (initialize === true) this.checkUniform(name);
            this._floats[name] = value;
            return this;
        public checkUniform(uniformName:string): void {
            if (this._uniforms.indexOf(uniformName) === -1) {
        public checkSampler(samplerName:string): void {
            if (this._samplers.indexOf(samplerName) === -1) {
                this.checkUniform(samplerName + "Infos");
                this.checkUniform(samplerName + "Matrix");

        /* Shader Material Base Worker Functions */

        public getAnimatables(): IAnimatable[] {
            const results = super.getAnimatables();
            for (const name in this._textures) {
                const texture:BABYLON.Texture = this._textures[name];
                if (texture && texture.animations && texture.animations.length > 0) results.push(texture);
            return results;
        public getActiveTextures(): BaseTexture[] {
            const results = super.getActiveTextures();
            for (const name in this._textures) {
                const texture:BABYLON.Texture = this._textures[name];
                if (texture) results.push(texture);
            return results;
        public hasTexture(texture: BaseTexture): boolean {
            if (super.hasTexture(texture)) {
                return true;
            let found:boolean = false;
            for (const name in this._textures) {
                const texture:BABYLON.Texture = this._textures[name];
                if (texture === texture) {
                    found = true;
            return found;    

        /* Shader Material Factory Class Functions */

        public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void {
            if (forceDisposeTextures) {
                for (const name in this._textures) {
                    const texture:BABYLON.Texture = this._textures[name];
                    if (texture) texture.dispose();
                    this._textures[name] = null;
            this._textures = {};
            super.dispose(forceDisposeEffect, forceDisposeTextures);
        public clone(cloneName: string): BABYLON.UniversalAlbedoMaterial {
            let name: string;
            const destination = BABYLON.SerializationHelper.Clone<BABYLON.UniversalAlbedoMaterial>(() => new BABYLON.UniversalAlbedoMaterial(cloneName, this.getScene()), this);
            destination._textures = {};
            for (name in this._textures) {
                const texture:BABYLON.Texture = this._textures[name];
                if (texture) destination.setTexture(name, texture.clone(), true);
            destination._vectors4 = {};
            for (name in this._vectors4) {
                destination.setVector4(name, this._vectors4[name].clone(), true);
            destination._floats = {};
            for (name in this._floats) {
                destination.setFloat(name, this._floats[name], true);
            return destination;
        public serialize(): any {
            let name: string;
            const serializationObject = BABYLON.SerializationHelper.Serialize(this);
            serializationObject.customType = "BABYLON.UniversalAlbedoMaterial";
            serializationObject.textures = {};
            for (name in this._textures) {
                const texture:BABYLON.Texture = this._textures[name];
                if (texture) serializationObject.textures[name] = texture.serialize();
            serializationObject.vectors4 = {};
            for (name in this._vectors4) {
                serializationObject.vectors4[name] = this._vectors4[name].asArray();
            serializationObject.floats = {};
            for (name in this._floats) {
                serializationObject.floats[name] = this._floats[name];
            return serializationObject;
        public static Parse(source: any, scene: BABYLON.Scene, rootUrl: string): BABYLON.UniversalAlbedoMaterial {
            let name: string;
            const material =  BABYLON.SerializationHelper.Parse(() => new BABYLON.UniversalAlbedoMaterial(, scene), source, scene, rootUrl);
            for (name in source.textures) {
                const texture:BABYLON.Texture = source.textures[name];
                if (texture) material.setTexture(name, <BABYLON.Texture>Texture.Parse(texture, scene, rootUrl), true);
            for (name in source.vectors4) {
                material.setVector4(name, BABYLON.Vector4.FromArray(source.vectors4[name]), true);
            for (name in source.floats) {
                material.setFloat(name, source.floats[name], true);
            return material;

        ///////////////////////////////////////////  Protected Worker Funtions  //////////////////////////////////////

        protected customShaderChunkResolve():void {
            const chunkName:string = this.getShaderChunk();
            if (chunkName != null && chunkName !== "") {
                const shaderChunkBase = chunkName + "ShaderChunks";
                if (BABYLON.Effect.ShadersStore[shaderChunkBase] != null) {
                    const shaderChunks:any = BABYLON.Effect.ShadersStore[shaderChunkBase];
                    const vertexBegin:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "VertexBegin")) ? shaderChunks["VertexBegin"] : null;
                    if (vertexBegin != null && vertexBegin !== "") {
                        this.materialShaderChunks.Vertex_Begin = vertexBegin;
                    const vertexDefinitions:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "VertexDefinitions")) ? shaderChunks["VertexDefinitions"] : null;
                    if (vertexDefinitions != null && vertexDefinitions !== "") {
                        this.materialShaderChunks.Vertex_Definitions = vertexDefinitions;
                    const vertexMainBegin:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "VertexMainBegin")) ? shaderChunks["VertexMainBegin"] : null;
                    if (vertexMainBegin != null && vertexMainBegin !== "") {
                        this.materialShaderChunks.Vertex_MainBegin = vertexMainBegin;
                    const vertexUpdatePosition:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "VertexUpdatePosition")) ? shaderChunks["VertexUpdatePosition"] : null;
                    if (vertexUpdatePosition != null && vertexUpdatePosition !== "") {
                        this.materialShaderChunks.Vertex_Before_PositionUpdated = vertexUpdatePosition.replace("result", "positionUpdated");
                    const vertexUpdateNormal:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "VertexUpdateNormal")) ? shaderChunks["VertexUpdateNormal"] : null;
                    if (vertexUpdateNormal != null && vertexUpdateNormal !== "") {
                        this.materialShaderChunks.Vertex_Before_NormalUpdated = vertexUpdateNormal.replace("result", "normalUpdated");
                    const vertexMainEnd:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "VertexMainEnd")) ? shaderChunks["VertexMainEnd"] : null;
                    if (vertexMainEnd != null && vertexMainEnd !== "") {
                        this.materialShaderChunks.Vertex_MainEnd = vertexMainEnd;

                    const fragmentBegin:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentBegin")) ? shaderChunks["FragmentBegin"] : null;
                    if (fragmentBegin != null && fragmentBegin !== "") {
                        this.materialShaderChunks.Fragment_Begin = fragmentBegin;
                    const fragmentDefinitions:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentDefinitions")) ? shaderChunks["FragmentDefinitions"] : null;
                    if (fragmentDefinitions != null && fragmentDefinitions !== "") {
                        this.materialShaderChunks.Fragment_Definitions = fragmentDefinitions;
                    const fragmentMainBegin:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentMainBegin")) ? shaderChunks["FragmentMainBegin"] : null;
                    if (fragmentMainBegin != null && fragmentMainBegin !== "") {
                        this.materialShaderChunks.Fragment_MainBegin = fragmentMainBegin;
                    const fragmentUpdateAlbedo:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentUpdateAlbedo")) ? shaderChunks["FragmentUpdateAlbedo"] : null;
                    if (fragmentUpdateAlbedo != null && fragmentUpdateAlbedo !== "") {
                        this.materialShaderChunks.Fragment_Custom_Albedo = fragmentUpdateAlbedo.replace("result", "surfaceAlbedo");
                    const fragmentUpdateAlpha:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentUpdateAlpha")) ? shaderChunks["FragmentUpdateAlpha"] : null;
                    if (fragmentUpdateAlpha != null && fragmentUpdateAlpha !== "") {
                        this.materialShaderChunks.Fragment_Custom_Alpha = fragmentUpdateAlpha.replace("result", "alpha");
                    const fragmentMetallicRoughness:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentMetallicRoughness")) ? shaderChunks["FragmentMetallicRoughness"] : null;
                    if (fragmentMetallicRoughness != null && fragmentMetallicRoughness !== "") {
                        this.materialShaderChunks.Fragment_MetallicRoughness = fragmentMetallicRoughness;
                    const fragmentMicroSurface:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentMicroSurface")) ? shaderChunks["FragmentMicroSurface"] : null;
                    if (fragmentMicroSurface != null && fragmentMicroSurface !== "") {
                        this.materialShaderChunks.Fragment_MicroSurface = fragmentMicroSurface;
                    const fragmentBeforeLights:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentBeforeLights")) ? shaderChunks["FragmentBeforeLights"] : null;
                    if (fragmentBeforeLights != null && fragmentBeforeLights !== "") {
                        this.materialShaderChunks.Fragment_Before_Lights = fragmentBeforeLights;
                    const fragmentBeforeFog:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentBeforeFog")) ? shaderChunks["FragmentBeforeFog"] : null;
                    if (fragmentBeforeFog != null && fragmentBeforeFog !== "") {
                        this.materialShaderChunks.Fragment_Before_Fog = fragmentBeforeFog;
                    const fragmentBeforeFragColor:string = (BABYLON.Utilities.HasOwnProperty(shaderChunks, "FragmentBeforeFragColor")) ? shaderChunks["FragmentBeforeFragColor"] : null;
                    if (fragmentBeforeFragColor != null && fragmentBeforeFragColor !== "") {
                        this.materialShaderChunks.Fragment_Before_FragColor = fragmentBeforeFragColor.replace("result", "color");
                } else {
                    BABYLON.Tools.Warn("Failed To Locate Shader Chunk Base: " + shaderChunkBase);

        ////////////////////////////////////////////  Private Worker Funtions  ///////////////////////////////////////
        private _buildCustomShader(shaderName: string, uniforms: string[], uniformBuffers: string[], samplers: string[], defines: BABYLON.PBRMaterialDefines) : string {
            this._defines = defines;
            let shaderProgram:string = this.getShaderName();
            if (shaderProgram == null || shaderProgram === "") {
                shaderProgram = shaderName;
            if (shaderProgram == null || shaderProgram === "") {
                shaderProgram = "pbr";
            // Validate Property Defines
            const locals:any = this.locals.getDefines();
            if (locals != null && this._defines != null) {
                const keys:string[] = Object.keys(locals);
                if (keys != null && keys.length > 0) {
                    const source:any = this._defines;
                    for (const key of keys) {
                        source[key] = locals[key];
            // Validate Property Uniforms
            let index:number = 0;
            if (this._uniforms != null && this._uniforms.length > 0) {
                for (index = 0; index < this._uniforms.length; index++) {
                    const uniformName = this._uniforms[index];
                    if (uniforms.indexOf(uniformName) === -1) {
            // Validate Property Samplers
            index = 0;
            if (this._samplers != null && this._samplers.length > 0) {
                for (index = 0; index < this._samplers.length; index++) {
                    const samplerName:string = this._samplers[index];
                    if (samplers.indexOf(samplerName) === -1) {
            // Validate Shader Chunks
            return (this.enableShaderChunks === true) ? this._createShaderChunks(shaderProgram) : shaderProgram;
        private _createShaderChunks(shaderName: string): string {
            if (this._isCreatedShader) {
                return this._createdShaderName;
            this._isCreatedShader = false;
            let chunkName:string = this.getShaderChunk();
            if (chunkName == null || chunkName === "") {
                chunkName = shaderName;
            const name: string = (chunkName + "Custom" + BABYLON.UniversalShaderDefines.ShaderIndexer).trim();
            const vertex = Effect.ShadersStore[shaderName + "VertexShader"];
            const fragment = Effect.ShadersStore[shaderName + "PixelShader"];

            const vertexname = name + "VertexShader"
            Effect.ShadersStore[vertexname] = vertex
                .replace('#define CUSTOM_VERTEX_BEGIN', (this.materialShaderChunks.Vertex_Begin ? this.materialShaderChunks.Vertex_Begin : ""))
                .replace('#define CUSTOM_VERTEX_DEFINITIONS', (this.materialShaderChunks.Vertex_Definitions ? this.materialShaderChunks.Vertex_Definitions : ""))
                .replace('#define CUSTOM_VERTEX_MAIN_BEGIN', (this.materialShaderChunks.Vertex_MainBegin ? this.materialShaderChunks.Vertex_MainBegin : ""))
                .replace('#define CUSTOM_VERTEX_UPDATE_POSITION', (this.materialShaderChunks.Vertex_Before_PositionUpdated ? this.materialShaderChunks.Vertex_Before_PositionUpdated : ""))
                .replace('#define CUSTOM_VERTEX_UPDATE_NORMAL', (this.materialShaderChunks.Vertex_Before_NormalUpdated ? this.materialShaderChunks.Vertex_Before_NormalUpdated : ""))
                .replace('#define CUSTOM_VERTEX_MAIN_END', (this.materialShaderChunks.Vertex_MainEnd ? this.materialShaderChunks.Vertex_MainEnd : ""));
            const fragmentname = name + "PixelShader"
            Effect.ShadersStore[fragmentname] = fragment
                .replace('#define CUSTOM_FRAGMENT_BEGIN', (this.materialShaderChunks.Fragment_Begin ? this.materialShaderChunks.Fragment_Begin : ""))
                .replace('#define CUSTOM_FRAGMENT_DEFINITIONS', (this.materialShaderChunks.Fragment_Definitions ? this.materialShaderChunks.Fragment_Definitions : ""))
                .replace('#define CUSTOM_FRAGMENT_MAIN_BEGIN', (this.materialShaderChunks.Fragment_MainBegin ? this.materialShaderChunks.Fragment_MainBegin : ""))
                .replace('#define CUSTOM_FRAGMENT_UPDATE_ALBEDO', (this.materialShaderChunks.Fragment_Custom_Albedo ? this.materialShaderChunks.Fragment_Custom_Albedo : ""))
                .replace('#define CUSTOM_FRAGMENT_UPDATE_ALPHA', (this.materialShaderChunks.Fragment_Custom_Alpha ? this.materialShaderChunks.Fragment_Custom_Alpha : ""))
                .replace('#define CUSTOM_FRAGMENT_UPDATE_METALLICROUGHNESS', (this.materialShaderChunks.Fragment_MetallicRoughness ? this.materialShaderChunks.Fragment_MetallicRoughness : ""))
                .replace('#define CUSTOM_FRAGMENT_UPDATE_MICROSURFACE', (this.materialShaderChunks.Fragment_MicroSurface ? this.materialShaderChunks.Fragment_MicroSurface : ""))
                .replace('#define CUSTOM_FRAGMENT_BEFORE_LIGHTS', (this.materialShaderChunks.Fragment_Before_Lights ? this.materialShaderChunks.Fragment_Before_Lights : ""))
                .replace('#define CUSTOM_FRAGMENT_BEFORE_FOG', (this.materialShaderChunks.Fragment_Before_Fog ? this.materialShaderChunks.Fragment_Before_Fog : ""))
                .replace('#define CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR', (this.materialShaderChunks.Fragment_Before_FragColor ? this.materialShaderChunks.Fragment_Before_FragColor : ""));
            this._isCreatedShader = true;
            this._createdShaderName = name;

            return name;
        private _attachAfterBind(mesh:BABYLON.Mesh, effect:BABYLON.Effect):void  {
            let name: string;
            const scene:BABYLON.Scene = this.getScene();
            if (scene.texturesEnabled) {
                for (name in this._textures) {
                    const texture:BABYLON.Texture = this._textures[name];
                    if (texture != null) {
                        effect.setTexture(name, texture);
                        effect.setFloat2(name + "Infos", texture.coordinatesIndex, texture.level);
                        effect.setMatrix(name + "Matrix", texture.getTextureMatrix());
            for (name in this._vectors4) {
                effect.setVector4(name, this._vectors4[name]);
            for (name in this._floats) {
                effect.setFloat(name, this._floats[name]);
        private _setupAttachAfterBind():void {
            const fn_afterBind = this._afterBind;
            this._afterBind = (mesh:BABYLON.Mesh, effect:BABYLON.Effect) => { 
                this._attachAfterBind(mesh, effect);
                if (fn_afterBind) try { fn_afterBind(mesh, effect); }catch(e){};
    BABYLON._TypeStore.RegisteredTypes["UniversalAlbedoMaterial"] = UniversalAlbedoMaterial;

If anybody sees anything that the new PBRMaterial is breaking… please let me know :frowning:

Here’s a bit of context: What Happened to CUSTOM_FRAGMENT_UPDATE_ALBEDO

I think what you will need to change is in _createShaderChunks, after the line const fragment = Effect.ShadersStore[shaderName + "PixelShader"]; do:

fragment = fragment.replace(/#include<pbrBlockAlbedoOpacity>/g, Effect.IncludesShadersStore["pbrBlockAlbedoOpacity"]);
fragment = fragment.replace(/#include<pbrBlockReflectivity>/g, Effect.IncludesShadersStore["pbrBlockReflectivity"]);
fragment = fragment.replace(/#include<pbrBlockFinalColorComposition>/g, Effect.IncludesShadersStore["pbrBlockFinalColorComposition"]);

Those are the changes that have been made to PBRCustomMaterial when the shader code refactoring took place.

Thaks Again @Evgeni_Popov … I see there has been a few changes in the PBRCustomMaterial since i created it.

I see the the replace in the this.FragmentShader property. It looks like this simple puts back all the original source that was stripped out and put into separate pbr block include sections… That should fix the issues for my Unity Based Custom Shaders that i support for the Babylon Toolkit.

Thanks again bro… I didnt even see that change into my original PBRCustomMaterial class. I got an error because my newer UniversalAlbedoMaterial (which is almost like PBRCustomMaterial but also has generic property support… kinda like the old school ShaderMaterial did… This allow my to support Unity Custom Shader Properties without having to make hard coded material properties)

But I have a few question about those changes:

First in the string replace section for the vertex shader… What the deal with this line:

        if (this.CustomParts.Vertex_After_WorldPosComputed) {
            Effect.ShadersStore[name + "VertexShader"] = Effect.ShadersStore[name + "VertexShader"].replace('#define CUSTOM_VERTEX_UPDATE_WORLDPOS', this.CustomParts.Vertex_After_WorldPosComputed);

Why not make it just like the other string replace code above it ???

and this

        if (this.CustomParts.Fragment_Before_Fog) {
            Effect.ShadersStore[name + "PixelShader"] = Effect.ShadersStore[name + "PixelShader"].replace('#define CUSTOM_FRAGMENT_BEFORE_FOG', this.CustomParts.Fragment_Before_Fog);

Again… why is this so funny looking… why not just like the line above

Well it seems to be more that just replacing those three include pbr blocks.

It seems normalW has been moved as well…

plus for some reason it cant see surfaceAlbedo either…

Here just a few of the tons of shader error:

babylon.min.js:16 BJS - [18:11:07]: Offending line [1025] in fragment code: vec3 splatmapBuffer = surfaceAlbedo.rgb;
e._ErrorEnabled @ babylon.min.js:16

babylon.min.js:16 BJS - [18:11:07]: Error: FRAGMENT SHADER ERROR: 0:1025: 'normalW' : undeclared identifier

Also getting this funney error:

:8888/src/Shaders/ShadersInclude/importanceSampling.fx:1 Failed to load resource: the server responded with a status of 404 (File Not Found)

Why the heck is it now looking for shader include in the src folder ???

Yo @Deltakosh

Would it be possible to make a Legacy PBR material class that worked JUST LIKE IT DID BEFORE ALL THESE PBR MATERIAL CHANGES ???

Or if at all possible… Can we please go back to the PBRMaterial from 4.1 and make a new material for all these changes.

The PBRMaterial from 4.1 was the heart and soul of my shader system. I created PBRCustomMaterial so it can work the same way as the standard custom material. We had the same #define CUSTOM_FRAGMENT_XXX text block that can be string replace with custom shader code. You can make references to shader properties like surfaceAlbedo and normalW from anyhwere in your custom shader chunks.

Also… the biggest thing… I can make custom pbr.fragment.fx and make changes to any part of the shader code i want… But now the pbr.fragment.fx is full of a bunch #include pbrBlockxxxxx that move most of the meat and potatoes shader code out of the pbr.fragment.fx so no is not easy to make a copy of pbr.fragment.fx and make custom shader changes. That just kills all my custom shaders… espically my terrain texture atlas and splatmap shaders.

I just wish we could left PBRMaterial the way it was in 4.1 and have made a new material to do whatever all these changes are for :frowning:

That’s because if there’s no data in Vertex_After_WorldPosComputed I don’t want the #define CUSTOM_VERTEX_UPDATE_WORLDPOS to be replaced by an empty string, I want #define CUSTOM_VERTEX_UPDATE_WORLDPOS to remain in the shader code (because it can be used by the ShadowDepthWrapper class).

I will let @Deltakosh answer about the other things.

Note: we may have a solution for you, I have to discuss it with the core team…

We do not guarantee shader back compat unfortunately. This is because we need to innovate (like adding support for WebGPU in this case). We cannot duplicate it as well as I do not want to maintain multiple versions.

Nothing prevents you from cloning the 4.1 version of the PBR but you will lose all the new features we are adding to the shader

That being said, I agree that we need to help you :wink:

I’ll discuss with @Evgeni_Popov to find a way to suit your needs!

As a tradeoff, the amazing @Evgeni_Popov will create a way to inline functions in the code so that you could still access all your previous variables.

We won’t guarantee back compat for the shaders but worst case will probably be for you to rename variables only.

You guys @Deltakosh and @Evgeni_Popov … and basically everyone on the forums are always a great help. We got one of the best forum communities out there

thank you guys so much


So, a PR has been merged:

With it, you can do this change in _buildCustomShader:

private _buildCustomShader(shaderName: string, uniforms: string[], 
      uniformBuffers: string[], samplers: string[], defines: BABYLON.PBRMaterialDefines, 
      attributes?: string[], options?: ICustomShaderNameResolveOptions) : string {

    options!.processFinalCode = (type: string, code: string) => {
        if (type === "vertex") {
            return code;
        const sci = new BABYLON.ShaderCodeInliner(code);
        sci.inlineToken = "#define pbr_inline";
        return sci.code;

You can provide through options.processFinalCode a function that will be called with the final shader code before it is being compiled, and you can modify this code.

So, what is done here is that the code (only in fragment, no need for vertex) is inlined: after the processing there’s no more (pbr) function calls, all the calls have been replaced by the function body code, so you end up with having all your fragment code in a single chunk inside the main function of the shader, which is what you wanted in the first place.

The inlineToken #define pbr_inline is the marker that let ShaderCodeInliner locate the functions that need to be inlined: we marked all the pbr block functions with this token.

Let us know if that solves your problems.

1 Like

So i have to add options?: ICustomShaderNameResolveOptions to my _buildCustomShader function… How should i create the options to pass to the _buildCustomShader function ???

I cant tell where the options get used… After i setup the processFinalCode function… where does the options get used ???

Your _buildCustomShader function is the one you set to this.customShaderNameResolve and is called by the PBRBaseMaterial code.

The signature of this function is (found in Material):

public customShaderNameResolve: (shaderName: string, uniforms: string[],
  uniformBuffers: string[], samplers: string[], defines: MaterialDefines | string[],
  attributes?: string[], options?: ICustomShaderNameResolveOptions) => string;

So, options will be automatically passed to you when customShaderNameResolve is called by PBRBaseMaterial.

The code change I gave you above should be all you need to make this work (+you need to add the required imports at the top of your file).

The options object is created by PBRBaseMaterial and is passed to you. If in this object you set a processFinalCode property, then the system expects it is a function that will process the shader code it is passed and will return the processed code. This function is called automatically by the system just before the shader code must be compiled by the gfx driver, giving you the opportunity to modify the code.

I just saw all that in the new master github:

        const csnrOptions: ICustomShaderNameResolveOptions = {};

        if (this.customShaderNameResolve) {
            shaderName = this.customShaderNameResolve(shaderName, uniforms, uniformBuffers, samplers, defines, attribs, csnrOptions);

Really kool… i just need to add this:

options!.processFinalCode = (type: string, code: string) => {
        if (type === "vertex") {
            return code;
        const sci = new BABYLON.ShaderCodeInliner(code);
        sci.inlineToken = "#define pbr_inline";
        return sci.code;

to my _buildCustomShader function and that it… Thank a million bro :slight_smile:

I am going to merge master and build the project… (I dont think the PR got into the last nightly build)


Hell Yeah … @Deltakosh and @Evgeni_Popov … You saved my Babylon Toolkit Project :slight_smile:

My new version of the Toolkit is GLTF based and HEAVILY dependent on the PBRMaterial… As a matter a fact… EVERY SHADER MATERIAL IN UNITY first gets converted to a basic PBRMaterial… Then depending on the metadata, iT MAY get assign a custom material in Babylon (Diffuse or Albedo)

i Also have the Koolest partial shader tooling. You can so easily make partial shader materials in the unity editor and assign and use those partial shaders in just like would for a native unity project.

Thanks you again… those latest PBRMaterial changes almost broke my heart :slight_smile:

1 Like

Glad to read because I really want this project to stay alive!!