GLTF Parser Hooks

Yo @bghgary … btw… i do have required extension support for the gltf… i just truned it off so i can use on the playground for you to test:

if (CanvasToolsInfo.Instance.HandedExportSystem == 1) registerExtension(EditorScriptComponent.CANVAS_TOOLS_HAND_EXT, true);

Ok, sounds good. I just want to make sure these assets don’t leak outside of your scenario.

Hey @bghgary … it looks like the GLTF parser create a new root node each time it loads gltf content… LoadScene, Append, ImportMesh, etc…

Is there any way we can add support to the gltf load to pass THAT ROOT NODE to the loader… so we can access the root node from the extension like this._loader.rootNode or something…

That way in the scene.extras.metadata i can pass the handedness = left or right.
i can use that flag in the CVTOOLS_left_handedness extension to
A… Reset root node rotation and scale in loader.loadSceneAsync

if (BABYLON.SceneManager.HandSystem === BABYLON.Handedness.Left) {
     this._loader.rootNode.rotationQuaternion = BABYLON.Quaternion.Identity();
     this._loader.rootNode.scaling = BABYLON.Vector3.One();
}

B… Set Counter Clockwise material orientations in the loader.loadMaterialPropertiesAsync

if (BABYLON.SceneManager.HandSystem === BABYLON.Handedness.Left) {
    babylonMaterial.sideOrientation = BABYLON.Material.CounterClockWiseSideOrientation;
}

That would be so awesome and those TWO little spots is all that is need to support left handed GLTF… And the exporter will set if its data is encoded in left handed format int its scene.extras.metadata…

UPDATE:
In theory you can have MIXED left and right handed content if the the ROOT node from each scene get ITS rotation and scale set for that scene content only… the same with setting material counter clockwise for the material loaded in that scene only… Everything in else in your game code would be the exact same… the root nodes are just inverted for right handed content and not inverted (or reset to zero) for left handed content.

So simple :slight_smile:

I’ve made an update to Babylon to support this. See Minor update to glTF loader extensions by bghgary · Pull Request #5984 · BabylonJS/Babylon.js · GitHub.

Update leftHanded to false by default and replace the commented out code with your logic.

function Custom(loader) {
    this.name = "custom";
    this.enabled = true;
    this.leftHanded = true;

    this.onLoading = function () {
        //this.leftHanded = (loader.gltf.extensions && loader.gltf.extensions.CVTOOLS_left_handed);

        if (this.leftHanded) {
            loader.rootBabylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
            loader.rootBabylonMesh.scaling = BABYLON.Vector3.One();
        }
    }

    this.createMaterial = function (context, material, babylonDrawMode) {
        var babylonMaterial = loader.createMaterial(context, material, babylonDrawMode);

        if (this.leftHanded) {
            babylonMaterial.sideOrientation = BABYLON.Material.CounterClockWiseSideOrientation;
        }

        return babylonMaterial;
    }
}

BABYLON.GLTF2.GLTFLoader.RegisterExtension(
    "custom", function (loader) { return new Custom(loader); });

var createScene = function () {
    var scene = new BABYLON.Scene(engine);
    scene.createDefaultCamera();

    BABYLON.SceneLoader.AppendAsync("https://mackeyk24.github.io/temp/TestLefty.glb").then(function () {
        scene.createDefaultCameraOrLight(true, true, true);

        BABYLON.GLTF2.GLTFLoader.UnregisterExtension("custom");
    });

    return scene;
};

Nice to publicly expose rootBabylonMesh.

I just came up with the same exact idea last night BUT i kinda HACKED into rootBabylonMesh

My Code:

            const righty:boolean = (metadata.righty != null) ? metadata.righty : true;
            const loader:any = (<any>this._loader);
            const rooturl:string = loader._fileName ? loader._fileName : null;
            const rootmesh:BABYLON.Mesh = loader._rootBabylonMesh ? loader._rootBabylonMesh : null;
            const filename:string = loader._fileName ? loader._fileName : null;
            console.log("*** ROOT MESH ***");
            console.log(rootmesh);
            if (rootmesh != null) {
                rootmesh.name = "Root." + filename.replace(".gltf", "").replace(".glb", "");
                if (righty === false) { // Note: Force Left Handed System
                    rootmesh.rotationQuaternion = BABYLON.Quaternion.Identity();
                    rootmesh.scaling = BABYLON.Vector3.One();
                }
            }

But i like your way of triggering left hand mode by checking if extension CVTOOLS_left_handedness in the scene file rather than me using metadata… gonna use that and change from _rootBabylonMesh to the new public rootBabylonMesh when the PR goes thru.

Thanks again Gary… The left handed GLTF when using the toolkit . to make your game is WAY easier to deal with… The same positions and rotation i put in the Unity Editor are the SAME positions and rotations i deal with in code… Plus you dont gotta thru the various QUIRKS when scene.useRightHandSystem = true sometimes pops in there :slight_smile:

I love it… My full GLTF with Unity Extra Metadata (Beautifully rendered) but using ALL the features i had in the old babylon toolkit to give it life… just better :slight_smile:

Yo @bghgary … I am trying to setup StandardMaterial support for Unity Non PBR Materials export.

When setting up alpha properties on a PBRMaterial… there is a PBRMaterial.transparencyMode to set OPAQUE, ALPHATEST and ALPHABLEND …

How do i use or set a Standard material different support for ALPHA MODES ???

this is what i got so far but don’t know what to do with standardMaterial.alpha since there is no transparencyMode for that on BABYLON.StandardMaterial:

        const alphaMode = material.alphaMode || BABYLON.GLTF2.MaterialAlphaMode.OPAQUE;
        switch (alphaMode) {
            case BABYLON.GLTF2.MaterialAlphaMode.OPAQUE: {
                babylonMaterial.alpha = 1.0; // MAYBE - ???
                //babylonMaterial.transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_OPAQUE;
                break;
            }
            case BABYLON.GLTF2.MaterialAlphaMode.MASK: {
                //babylonMaterial.transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHATEST;
                babylonMaterial.alphaCutOff = (material.alphaCutoff == undefined ? 0.5 : material.alphaCutoff);
                if (babylonMaterial.diffuseTexture) {
                    babylonMaterial.diffuseTexture.hasAlpha = true;
                }
                break;
            }
            case BABYLON.GLTF2.MaterialAlphaMode.BLEND: {
                //babylonMaterial.transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
                if (babylonMaterial.diffuseTexture) {
                    babylonMaterial.diffuseTexture.hasAlpha = true;
                    babylonMaterial.useAlphaFromDiffuseTexture = true;
                }
                break;
            }
            default: {
                throw new Error(`${context}/alphaMode: Invalid value (${material.alphaMode})`);
            }
        }

Do you think you can help set the various APLHA MODES for the a StandardMaterial ???

Yo @bghgary … i tried to wrap a REQUIRED script loading promise in the loadSceneAsync…

What i basically need is a required piece of javascript called project.js to load before the scene nodes are parsed that might create custom materials… which are defined in the required project.js

I tried this but the on load of the javascript which is suppose to call resovle still runs after all the scene nodes are processed.

    public loadSceneAsync(context: string, scene: BABYLON.GLTF2.Loader.IScene): BABYLON.Nullable<Promise<void>> {
        console.warn("CVTOOLS: LoadSceneAsync: " + scene.name);
        const promises = new Array<Promise<any>>();
        if (scene.extras != null && (<any>scene.extras).metadata != null) {
            const metadata:any = (<any>scene.extras).metadata;
            const loader:any = (<any>this._loader);
            const rooturl:string = loader._rootUrl ? loader._rootUrl : null;
            const filename:string = loader._fileName ? loader._fileName : null;
            const rootmesh:BABYLON.Mesh = loader._rootBabylonMesh ? loader._rootBabylonMesh : null;
            if (rootmesh != null) rootmesh.name = "Root." + filename.replace(".gltf", "").replace(".glb", "");
            // ..
            // Load Required Project Javascript Bundle
            // ..
            if (document != null && metadata.script != null && metadata.script !== "" && metadata.project != null && metadata.project !== "")  {
                const scriptid:string = metadata.script;
                const projectjs:string = metadata.project;
                const hasscript:boolean = (document.getElementById(scriptid) != null);
                if (hasscript === false) {
                    const loadProjectScript = (tx, sx) => {
                        return new Promise<any>((resolve, reject) => {
                            console.log("===> Loading script name: " + tx);
                            const me:any = this;
                            const loader:any = (<any>this._loader);
                            const rooturl:string = loader._uniqueRootUrl ? loader._uniqueRootUrl : "";
                            const header = document.head || document.getElementsByTagName("head")[0];        
                            const filesrc = rooturl + sx;
                            const fileref = document.createElement("script");
                            fileref.id = tx;
                            fileref.setAttribute("type", "text/javascript");
                            fileref.setAttribute("src", filesrc);
                            fileref.onerror = function(err) {
                                reject(err);
                                console.warn("===> Project script failed: " + tx);
                            };
                            fileref.onload = function() {
                                resolve(tx);
                                console.log("===> Project script loaded: " + tx);
                            };
                            header.appendChild(fileref)
                        })
                    };
                    promises.push(loadProjectScript(scriptid, projectjs));
                }
            }
            promises.push(this._loadSceneProperties(context, scene));
        }
        promises.push(this._loader.loadSceneAsync(context, scene));
        return Promise.all(promises).then(() => { });
    }

How would you make it so i could load the project.js (which is defined in the gltf.scene.extras.metadata.project) ???

Hey @bghgary … I updated to the latest babylon js and build the new preview release. in Alpha 29
.

But when i console.log teh this._loader. The new gltf and rootBabylonMesh are undefined:

Which shaders are you exporting from Unity? I don’t think any of them matches Babylon’s StandardMaterial well. Even the Standard shader in Unity doesn’t quite match with the PBR material in glTF, but it is close.

The new gltf and rootBabylonMesh are undefined

My PR is merged, but it’s not deployed yet. Though it’s weird you don’t have the gltf property. That should be there regardless of my change. Can you repro in a playground?

Probably something like this:

let promise: Promise;
if (…) {
    promise = loadProjectScript();
}
else {
    promise = Promise.resolve();
}

return promise.then(() => {
    ...
    promises.push(…);
    promises.push(…);
    …
    return Promise.all(promises);
});

I’m not actually exporting a Unity Shader… what I do is… if you are using any unity shader that has metallic workflow is gets exported as a regular PBRMatertal … if using a specular workflow shader it’s gets exported the KHR pbr specular shader… all other LEGACY diffuse shaders gets dumped out as a fallback pbr specular with black specular to emulate a diffuse shader, but it has extra metadata that marks it as legacy material… my gltf extension looks for legacy material flag and if it’s true it will setup a StandardMaterial but using the textures like your demo. This is working great except I don’t know to setup the alpha blending options for a standard material like we do for a pbr … can you help me setup what should be set on a standard material to support the different alpha mode like we do for a pbr material… please :slight_smile:

Yeah it’s weird… when logging the loader to console it says gltf and rootbabylonmesh are undefined … but you log out loader.gltf it has a value… but no extensions dictionary… it’s does have an extensionsUsed string array… so I use that to check for CVTOOLS_left_handedness

The loader.rootBabylonMesh also is there when you console log

It’s weird that if you log the loader the gltf and rootBabylonMesh show as undefined

So can I do this in the loadSceneAsync function ???

If so, what do I return from the loadSceneAsync???

My loadProjectScript promise.then ???

And in that then function I actually return the original this._loader.loadSceneAsync promise

I not sure about the call chain when trying to load a required script from the loadSceneAsync function… but will play with that …

Does my loadProcjectScript wrapped promise look good for creating a script tag and calling resolve or reject based on script tag onLoad and onError callback ???

It that the proper way to wrap a regular callback type function in a Promise ???

I’m not actually exporting a Unity Shader

I don’t mean to imply you are exporting a Unity shader. I mean which shader do you support when you export a material:

image

you log out loader.gltf it has a value… but no extensions dictionary

Okay, that’s probably some debugger weirdness. Not having the extensions dictionary probably means you didn’t export the glTF with an extensions property? I suppose it’s okay to use the extensionsUsed property to indicate this.

Yup.

Yup. :slight_smile:

I support them all … I check if shader is Metallic type … they handle as pbr … All other shaders get exported as pbr specular… this way all gltf viewers that support KHR pbr specular can render … but if using my gltf importer anc is a non pbr shader … I use StandardMaterial instead of default PBRMaterial

My problem is I don’t know to properly setup the Alpha Mode on a standard material like we setup alpha mode for a pbr… opaque or mask or blend… on pbr we have a transparencyMode property but on StandardMaterial we just material.alpha … I assume I just set that two 1.0 for opaque alpha mode… but what about the other two alpha modes ??? How do I setup transparency on that standard material

Btw … I export from unity using the extensionsUsed and extentionsRequired

But not a ‘extentions’ all by itself

From a quick scan of the shaders, I only see Standard and Standard (Specular) having Rendering Mode. What other shaders have this? It’s hard to help if I don’t know what the source shader is supposed to do. In general, using StandardMaterial in Babylon for all the non-PBR shaders in Unity is a bit of a stretch.

Yeah NON-PBR dont have render type… In the current BabylonToolkit in C# i handle transparency for NON-PBR like this:

        private static void DumpTransparency(Texture2D texture, Material material, BabylonStandardMaterial babylonStdMaterial)
        {
            // Legacy Alpha Transparency Mode
            babylonStdMaterial.alpha = 1.0f;
            babylonStdMaterial.alphaMode = 2;
            babylonStdMaterial.alphaCutoff = 0.4f;
            babylonStdMaterial.useAlphaFromDiffuseTexture = false;
            if (babylonStdMaterial.diffuse != null && babylonStdMaterial.diffuse.Length >= 4) {
                babylonStdMaterial.alpha = babylonStdMaterial.diffuse[3];
            }
            bool hasCutoff = material.HasProperty("_Cutoff");
            babylonStdMaterial.alphaCutoff =  hasCutoff ? material.GetFloat("_Cutoff") : 0.4f;
            if (hasCutoff == true && babylonStdMaterial.diffuseTexture != null) {
                babylonStdMaterial.alphaMode = 2; // Note: Alpha Not Used For Blending
                babylonStdMaterial.diffuseTexture.hasAlpha = true;
            }
            if (texture != null && texture.alphaIsTransparency == true) {
                babylonStdMaterial.alphaMode = 7; // Note: Alpha Pre Multiply Blending Mode
                babylonStdMaterial.backFaceCulling = false;
                if (babylonStdMaterial.diffuseTexture != null) {
                    babylonStdMaterial.useAlphaFromDiffuseTexture = true;
                    if (babylonStdMaterial.opacityTexture == null) {
                        babylonStdMaterial.opacityTexture = babylonStdMaterial.diffuseTexture;
                    }
                }
            }
        }

So im kinda SIMULATING render type…

So if material has cutoff (BLENDING)… babylonMaterial.alphaMode = 2 and babylonMaterial.diffuseTexture.hasAlpha = true

If texture.alphaIsTransparency… babylonMaterial.alphaMode = 7 and is if babylonMaterial.opacityTexture == null then set opacityTexture = diffuseTexture and useAlphaFromDiffuseTexture = true.

I know its kinda of funny to setup TRANSPARENCY for Standard Materials

Yo @bghgary … this is how i am emulating render type for alpha mode on a standard material in my GLTF Extension… baseColorAlpha = material.pbrMetallicRoughness.baseColorFactor[3];

        // ALPHA PROPERTIES

        babylonMaterial.alpha = baseColorAlpha;             // Note: Default Base Color Alpha
        const alphaMode = material.alphaMode || BABYLON.GLTF2.MaterialAlphaMode.OPAQUE;
        switch (alphaMode) {
            case BABYLON.GLTF2.MaterialAlphaMode.OPAQUE: {  // Note: Normal-Mode (OPAQUE)
                babylonMaterial.alpha = 1.0;                // Note: Reset Alpha To Opaque
                break;
            }
            case BABYLON.GLTF2.MaterialAlphaMode.MASK: {    // Note: Transparency-Cutout (ALPHATEST)
                babylonMaterial.alpha = baseColorAlpha;     // Reset To Base Color Alpha
                babylonMaterial.alphaCutOff = (material.alphaCutoff == undefined ? 0.5 : material.alphaCutoff);

                // Dunno About This
                //if (babylonMaterial.diffuseTexture) {
                //    babylonMaterial.diffuseTexture.hasAlpha = true;
                //}
                break;
            }
            case BABYLON.GLTF2.MaterialAlphaMode.BLEND: {   // Note: Transparency (ALPHABLEND)
                babylonMaterial.alpha = baseColorAlpha;     // Reset To Base Color Alpha

                // Dunno About This
                //if (babylonMaterial.diffuseTexture) {
                //    babylonMaterial.diffuseTexture.hasAlpha = true;
                //    babylonMaterial.useAlphaFromDiffuseTexture = true;
                //}
                break;
            }
            default: {
                throw new Error(`${context}/alphaMode: Invalid value (${material.alphaMode})`);
            }
        }

Am i close or WAY OFF in trying to emulate the Transparency (BLEND) and Transparency-Cutoff (MASK)

???