Problem with GLTF2export on Nodejs with Nullengine

I want to combine different glb files to one to dynamically combine different 3d rooms into one. I’m doing this on server side with the Nullengine. If I want to export the scene the result is empty { glTFFiles: { 'Test.glb': {} } }. If I execute the same code on client side it works so I guess this is a bug. This is my complete code:

BABYLON = require('babylonjs');
var LOADERS = require('babylonjs-loaders');
require('babylonjs-serializers');
var fs = require('fs');
global.XMLHttpRequest = require('xhr2').XMLHttpRequest;

var createScene = function() {
  var scene = new BABYLON.Scene(engine);
  var camera = new BABYLON.FlyCamera('FlyCamera', new BABYLON.Vector3(0, 5, -10), scene);
  camera.setTarget(BABYLON.Vector3.Zero());
  var light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0, 1, 0), scene);

  BABYLON.SceneLoader.ImportMesh('', 'http://localhost:9966/', 'SceneTest.glb', scene, function(meshes) {
    for (var index = 0; index < meshes.length; index++) {
      var mesh = meshes[index];
      if (mesh.name === '__root__') {
        var axis = new BABYLON.Vector3(0, 1, 0);
        axis.normalize();
        var theta = Math.PI / 2;

        mesh.rotationQuaternion = new BABYLON.Quaternion.RotationAxis(axis, theta);

        mesh.position = new BABYLON.Vector3(-10, 0, 0);
      }
    }
  });

  BABYLON.SceneLoader.ImportMesh('', 'http://localhost:9966/SceneTest.glb', null, scene, function(
    meshes
  ) {});

  return scene;
};
var engine = new BABYLON.NullEngine();
var scene = createScene();

BABYLON.GLTF2Export.GLBAsync(scene, 'Test.glb').then(data => {
  console.log(data);
});

engine.runRenderLoop(function() {
  if (scene) {
    scene.render();
  }
});

Hi BumbleBeeBro,

Welcome to Babylon!

I don’t have an easy way to run your code, but this looks like an async issue. ImportMesh is an asynchronous function, so the code you’ve written there won’t actually be executed in the order it appears on the page. I think what’s happening is that your calls to ImportMesh are being executed, which starts the import process, but execution is continuing without waiting for the import to succeed. Execution then reaches the GLBAsync call and tries to export the scene, but the scene is still empty because the import process is ongoing.

If that’s the issue, you can fix it in a number of ways (callbacks, Promises, etc.), but probably the cleanest way will be to use the async-await syntax described in the first link I pasted above. You can declare your createScene function to be async, then await your ImportMesh calls, then await createScene(), and so on until your code has the execution order you’re going for.

1 Like

I changed the code to to await everything, but the exported data is still not there. Im not sure what else to do. This is my tested code:
const BABYLON = require(‘babylonjs’);
require(‘babylonjs-loaders’);
const SERIALIZER = require(‘babylonjs-serializers’);
const fs = require(‘fs’);
global.XMLHttpRequest = require(‘xhr2’).XMLHttpRequest;

var createScene = async function() {
  var scene = new BABYLON.Scene(engine);
  var camera = new BABYLON.FlyCamera('FlyCamera', new BABYLON.Vector3(0, 5, -10), scene);
  camera.setTarget(BABYLON.Vector3.Zero());
  var light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0, 1, 0), scene);

  const meshes = await new Promise(resolve =>
    BABYLON.SceneLoader.ImportMesh('', 'http://localhost:9966/', 'SceneTest.glb', scene, resolve)
  );

  for (var index = 0; index < meshes.length; index++) {
    var mesh = meshes[index];
    if (mesh.name === '__root__') {
      var axis = new BABYLON.Vector3(0, 1, 0);
      axis.normalize();
      var theta = Math.PI / 2;

      mesh.rotationQuaternion = new BABYLON.Quaternion.RotationAxis(axis, theta);

      mesh.position = new BABYLON.Vector3(-10, 0, 0);
    }
  }

  await new Promise(resolve =>
    BABYLON.SceneLoader.ImportMesh('', 'http://localhost:9966/', 'SceneTest.glb', scene, resolve)
  );

  return scene;
};

async function awaitCall() {
  var scene = await createScene();
  const data = await SERIALIZER.GLTF2Export.GLBAsync(scene, 'Test.glb');
  console.log(data);
}

var engine = new BABYLON.NullEngine();

awaitCall();

This is not a promise function so it won’t work

You must use ImportMeshAsync()

I’m running into this issue as well. I ran console.log on shouldExportNode to see if my nodes were in the scene. Here’s the following output I get:

  1. Some models list the node name, but the glb is still an empty object
  2. Some models output what looks like shader logic
  3. Models with textures show this error: TypeError: Cannot read property 'createFramebuffer' of undefined

This only appears to happen server side when using the NullEngine.

Here’s what my code looks like:

global.XMLHttpRequest = require("xhr2").XMLHttpRequest;

import { NullEngine, Scene } from "@babylonjs/core";
import { GLTF2Export } from "@babylonjs/serializers/glTF";

async function download() {
    const engine = new NullEngine({
        renderWidth: 512,
        renderHeight: 256,
        textureSize: 512,
    });
    const scene = new Scene(engine);

    await SceneLoader.ImportMeshAsync("", "", model.uri, scene, () => {}, ".glb");

    scene.createDefaultCameraOrLight(true, true, true);
    let maxZ = 0;
    maxZ = Math.max(...[...scene.meshes.map((x) => x.getBoundingInfo().boundingBox.extendSize.z), maxZ]);

    // Camera Limits
    scene.activeCamera.maxZ = 10000;
    scene.activeCamera.upperBetaLimit = Math.PI / 2;
    scene.activeCamera.upperRadiusLimit = maxZ.current * 200;
    scene.activeCamera.beta = Math.PI / 2;
    scene.activeCamera.alpha += Math.PI;

    await scene.whenReadyAsync();
    scene.render();
    const gltf = await GLTF2Export.GLBAsync(scene, "pxl", {
        shouldExportNode: function (node) {
            console.log("NODE: ", node);
            return true;
        },
    });

    console.log("GLTF: ", gltf.glTFFiles); // Output: GLTF:  { 'pxl.glb': {} }
}

Thanks in advance for your help

Do you mind sharing the callstack message?

Adding @Drigax

TypeError: Cannot read property 'createFramebuffer' of undefined
    at NullEngine.Engine._readTexturePixels (/Users/peterhenry/Documents/C9S/pxl/.next/server/static/development/pages/api/download.js:41001:22)
    at Texture.BaseTexture.readPixels (/Users/peterhenry/Documents/C9S/pxl/.next/server/static/development/pages/api/download.js:96873:19)
    at _GLTFMaterialExporter.getPixelsFromTexture (/Users/peterhenry/Documents/C9S/pxl/.next/server/static/development/pages/api/download.js:234352:198)
    at /Users/peterhenry/Documents/C9S/pxl/.next/server/static/development/pages/api/download.js:234414:28

Ok understood

@Drigax: We should block the exportation of the texture UV extension when there is no webgl support :slight_smile:

Does this mean it’s not possible to export a model with textures server side? I have an app that allows certain users to export models. I was hoping to do the export server side for better security.

It means that some features like the texture uv extension will not be exported but you should be able to get a gltf still

This doesn’t seem to be any particular extension. I think we’re reading from the glTexture for every exported texture in scene. I’m not sure if there’s a graceful solution to this. I curently have a fix that will do some additional error checking in engine.readTexturePixels that will throw if there is no webGLContext, and will not export the texture in that case.

Also that means that nullEngine can’t export textures with the current implementation.

Do you have any idea of a better workaround for this?

Well we could skip exporting textures but still export the gltf at least

1 Like

@Deltakosh is there any option to make the GLTF export working? What are the reasons that it’s not working when there is no webGLContext?

1 Like

Adding @Drigax to the party but all the texture work being done on the GPU for export, any exported feature requiring it to work would normally be disable.

Do you happen to have a repro @Drigax could use ?

1 Like

@sebavan we have quite complex app doing a lot of manipulations on models (applying new materials, showing/hiding meshes, etc.) so first we used sample code from Server Side - Babylon.js Documentation, then used exporter from this page glTF Exporter - Babylon.js Documentation which raised exact error from this thread.
We are successfully using nullEngine for listing model meshes in the browser, but running it on backend is not working for gltf exporter.

I am pretty sure some of your features exported relies on texture then as it is using readPixels as we see in the stack and the only way around is to not export textures at all which would break your exported file in this case.

You could try relying on a real engine instead and xvfb on the server ??? this is what we use in our automated tests to benefit from a real rendering despite being slower. Would that work for you ?

1 Like

Sounds fine, I think performance degradation is not an issue. CPU processing power is easier for scaling than GPU. How can we configure NullEngine to use xvfb? Our goal is to make it working on AWS Lambda if possible, but we can consider another options.

You would use Engine and not null engine in this case and first run:

export DISPLAY=:99
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
sleep 3 # give xvfb some time to start
1 Like

Thank you for the tip. We’ll try use this approach.

@sebavan We are looking at running an babylon instance on a nodejs server.
Can you maybe elaborate a bit on how you use xvfb and setup the canvas?

Thanks!