Saving from the NME to Babylon 89,603 's snippet server gives an immutable id, such as 2F999G, the default-graph example in Babylon's documentation, and a shareable editor URL (nme.babylonjs.com/#2F999G). NodeMaterial.ParseFromSnippetAsync(id, scene) fetches one at run time; ParseFromFileAsync(name, url, scene) does the same for JSON you host (it loaded Saving Node Materials's saved highlight, all 24 blocks, in a test run):
const started = performance.now();
const fromSnippet = await BABYLON.NodeMaterial.ParseFromSnippetAsync('2F999G', scene);
console.log(`${fromSnippet.snippetId}: ${fromSnippet.attachedBlocks.length} blocks, ` +
`${Math.round(performance.now() - started)} ms`);Output
2F999G: 8 blocks, 377 ms
Over three runs the snippet took 0.36 to 1.3 s, a round trip to a third-party server before the material can render. Use snippets for prototyping; in production, self-host the JSON or build the graph in code, as BookNest does.
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; }
canvas { width: 100%; max-width: 600px; height: 360px; display: block; touch-action: none; }
</style>
<canvas id="view"></canvas>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babylonjs-gui@9.28.0/babylon.gui.min.js"></script>
<script>
/*
From the snippet server (needs the network, which this sandboxed preview blocks):
const fromSnippet = await BABYLON.NodeMaterial.ParseFromSnippetAsync('2F999G', scene);
From JSON you host yourself:
const hosted = await BABYLON.NodeMaterial.ParseFromFileAsync('highlight', 'highlight.json', scene);
Here the "hosted" JSON is a string in the page, parsed with NodeMaterial.Parse.
*/
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#f7f3ecff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2, 1.3, 7.2, new BABYLON.Vector3(0.5, 0.8, 0), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.5), scene);
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui');
// The JSON file you would host: the default graph (the same one as snippet 2F999G), recolored
const source = new BABYLON.NodeMaterial('source', scene);
source.setToDefault();
source.build();
source.getBlockByName('color').value = BABYLON.Color4.FromHexString('#2a9d8fff');
const hostedJson = JSON.stringify(source.serialize());
source.dispose();
const started = performance.now();
const material = BABYLON.NodeMaterial.Parse(JSON.parse(hostedJson), scene);
const localMs = performance.now() - started;
const book = BABYLON.MeshBuilder.CreateBox('book', { width: 1, height: 1.4, depth: 0.25 }, scene);
book.position.set(-1.9, 0.9, 0);
book.material = material;
scene.onBeforeRenderObservable.add(() => { book.rotation.y += engine.getDeltaTime() / 1500; });
// Bars: seconds before the material can render
const TIMES = [['self-hosted JSON, parsed here', localMs / 1000, '#3f7d3a'],
['snippet server, fastest of 3 runs', 0.36, '#e09a10'], ['snippet server, slowest of 3 runs', 1.3, '#b5452f']];
TIMES.forEach(([name, seconds, hex], i) => {
const height = Math.max(seconds * 1.5, 0.03); // 1.5 m per second
const bar = BABYLON.MeshBuilder.CreateBox(name, { width: 0.6, depth: 0.6, height }, scene);
bar.position.set(0 + i * 1.5, height / 2, 0);
const base = new BABYLON.TransformNode('base' + i, scene); // anchor the label under the bar
base.position.set(bar.position.x, 0, 0);
bar.material = new BABYLON.StandardMaterial(hex, scene);
bar.material.diffuseColor = BABYLON.Color3.FromHexString(hex);
const label = new BABYLON.GUI.TextBlock('', `${seconds < 0.1 ? (seconds * 1000).toFixed(1) + ' ms' : seconds + ' s'}\n${name.replace(', ', '\n')}`);
Object.assign(label, { color: '#333', fontSize: 11, resizeToFit: true });
ui.addControl(label);
label.linkWithMesh(base);
label.linkOffsetY = 32;
});
const title = new BABYLON.GUI.TextBlock('', 'Time before the material can render');
Object.assign(title, { color: '#555', fontSize: 14, height: '24px', top: '8px',
verticalAlignment: BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP });
ui.addControl(title);
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>