WGSL Translation

Translating Shaders to WGSL for the WebGPU Backend

WebGPU takes only WGSL (WGSL Types), so Babylon 89,603 keeps WGSL twins of its core shaders in ShadersStoreWGSL. Your GLSL is translated on first use: glslang compiles it to SPIR-V, and TWGSL (Google's Tint, as WebAssembly) emits WGSL:

The room on WebGPUEngine, plus one book with a GLSL ShaderMaterialJavaScript
const engine = new BABYLON.WebGPUEngine(view);
await engine.initAsync();                                       // adapter, device, context
const scene = BookNestStore.createScene(engine);
BookNestStore.createRoom(scene);
BookNestStore.createBookcase(scene, 'bookcaseLeft', -1.8);
new BABYLON.ArcRotateCamera('overview', -Math.PI / 2, 1.05, 10, BABYLON.Vector3.Zero(), scene);
const spine = new BABYLON.ShaderMaterial('spine', scene, {
  vertexSource: `attribute vec3 position; uniform mat4 worldViewProjection;
    void main() { gl_Position = worldViewProjection * vec4(position, 1.0); }`,
  fragmentSource: `precision highp float; uniform vec3 color;
    void main() { gl_FragColor = vec4(color, 1.0); }`,
}, { attributes: ['position'], uniforms: ['worldViewProjection', 'color'] });
spine.setColor3('color', BABYLON.Color3.FromHexString('#e09a10'));
BABYLON.MeshBuilder.CreateBox('book', { width: 0.3, height: 1 }, scene).material = spine;
const started = performance.now();
engine.runRenderLoop(() => scene.render());
await scene.whenReadyAsync();
console.log(`ready in ${Math.round(performance.now() - started)} ms after fetching`,
  performance.getEntriesByType('resource').map((entry) => entry.name.split('/').pop())
    .filter((file) => /glslang|twgsl/.test(file)).join(', '));
const walnut = scene.getMeshByName('bookcaseLeft').subMeshes[0].effect;
console.log([walnut, spine.getEffect()].map((e) => BABYLON.ShaderLanguage[e.shaderLanguage])
  .join(' and '));
Output
BJS - [19:33:03]: Babylon.js v9.28.0 - WebGPU1 engine
ready in 2253 ms after fetching glslang.js, glslang.wasm, twgsl.js, twgsl.wasm
WGSL and GLSL

Walnut's effect is WGSL; the spine's GLSL was rewritten Vulkan-style (loose uniforms in a LeftOver block), then translated. The first frame waited 1.6 to 2.9 s in seven runs, mostly for 2.6 MB of WebAssembly.

On WebGPUEngine the core materials run Babylon's WGSL twins, beside a ShaderMaterial written in WGSL for a spineHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
  canvas { width: 100%; max-width: 600px; height: 280px; display: block; touch-action: none; outline: none; }
  #info { max-width: 600px; margin: 0; padding: 4px 8px; font: 11px monospace; white-space: pre-wrap; }
</style>
<canvas id="view"></canvas>
<pre id="info">Starting WebGPU…</pre>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script>
// ---- BookNest's bookstore: the helpers the book keeps in bookstore.js ----
const ROOM = { width: 8, depth: 6, height: 3, wall: 0.15 };
const BOOKS = [
  { id: 1, title: 'The Quiet Harbor', author: 'Mara Ellison', color: '#1f5f8b', pages: 312, price: 14.99, rating: 4.6, inStock: true },
  { id: 2, title: 'Patterns of the Deep Web', author: 'Tomas Reyes', color: '#5b3f99', pages: 428, price: 32.5, rating: 4.4, inStock: true },
  { id: 3, title: 'Salt and Saffron', author: 'Priya Nair', color: '#e09a10', pages: 256, price: 24, rating: 4.8, inStock: false },
  { id: 4, title: 'Small Steps to Big Summits', author: 'Dana Whitfield', color: '#3f7d3a', pages: 198, price: 18.5, rating: 4.3, inStock: true },
  { id: 5, title: "The Clockmaker's Paradox", author: 'Julian Voss', color: '#b5452f', pages: 344, price: 16.75, rating: 4.7, inStock: true },
  { id: 6, title: 'Gardens in Glass', author: 'Lena Hart', color: '#2a9d8f', pages: 176, price: 21.3, rating: 4.5, inStock: true },
];
function createScene(engine) {
  const scene = new BABYLON.Scene(engine);
  scene.clearColor = BABYLON.Color4.FromHexString('#cfd8dcff');
  const sky = new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.4), scene);
  sky.groundColor = new BABYLON.Color3(0.35, 0.32, 0.3);
  return scene;
}
function paint(scene, name, hex) {                  // a matte color
  const material = new BABYLON.StandardMaterial(name, scene);
  material.diffuseColor = BABYLON.Color3.FromHexString(hex);
  material.specularColor = BABYLON.Color3.Black();
  return material;
}
function createRoom(scene) {                        // floor and three walls, front open
  const room = new BABYLON.TransformNode('room', scene);
  const floor = BABYLON.MeshBuilder.CreateGround('floor', { width: ROOM.width, height: ROOM.depth }, scene);
  floor.material = paint(scene, 'oak', '#b08d6a');
  floor.parent = room;
  const plaster = paint(scene, 'plaster', '#fff3e0');
  for (const [name, width, x, z] of [['backWall', 8, 0, 3], ['leftWall', 6, -4, 0], ['rightWall', 6, 4, 0]]) {
    const wall = BABYLON.MeshBuilder.CreateBox(name, { width, height: ROOM.height, depth: ROOM.wall }, scene);
    wall.position.set(x, ROOM.height / 2, z);
    wall.rotation.y = x ? Math.PI / 2 : 0;
    wall.material = plaster;
    wall.parent = room;
  }
  return room;
}
function createBookcase(scene, name, x) {           // 8 boxes merged into 1 mesh
  const [w, h, d, t] = [3, 2.2, 0.35, 0.04];
  const parts = [[t, h, d, -w / 2, h / 2, 0], [t, h, d, w / 2, h / 2, 0], [w, h, t, 0, h / 2, d / 2 - t / 2]];
  for (let i = 0; i < 5; i++) parts.push([w, t, d, 0, 0.1 + i * 0.5, 0]);
  const bookcase = BABYLON.Mesh.MergeMeshes(parts.map(([width, height, depth, px, py, pz]) => {
    const part = BABYLON.MeshBuilder.CreateBox('part', { width, height, depth }, scene);
    part.position.set(px, py, pz);
    return part;
  }), true);
  bookcase.name = name;
  bookcase.material = paint(scene, 'walnut', '#6d4c33');
  bookcase.position.set(x, 0, ROOM.depth / 2 - ROOM.wall / 2 - d / 2);   // against the back wall
  return bookcase;
}
function drawCover(scene, book) {                   // cover art painted on a canvas
  const tex = new BABYLON.DynamicTexture('cover' + book.id, { width: 256, height: 384 }, scene);
  const c = tex.getContext();
  c.fillStyle = book.color; c.fillRect(0, 0, 256, 384);
  c.fillStyle = 'rgba(0,0,0,0.4)'; c.fillRect(0, 0, 18, 384);         // the dark left edge
  c.strokeStyle = '#fff8e1'; c.lineWidth = 3; c.strokeRect(34, 24, 202, 336);
  c.fillStyle = '#fff8e1'; c.textAlign = 'center'; c.font = 'bold 30px Georgia';
  const lines = [''];
  for (const word of book.title.split(' ')) {
    const test = (lines[lines.length - 1] + ' ' + word).trim();
    if (c.measureText(test).width > 180) lines.push(word); else lines[lines.length - 1] = test;
  }
  lines.forEach((line, i) => c.fillText(line, 135, 110 + i * 38));
  c.font = '20px Georgia'; c.fillText(book.author, 135, 330);
  tex.update();
  return tex;
}
function placeBooks(scene, bookcase, shelf = 2) {   // six books, spines out, covers on +x
  const strip = new BABYLON.Vector4(0, 0, 0.07, 1);
  const faceUV = [strip, strip, new BABYLON.Vector4(0, 0, 1, 1), strip, strip, strip];
  let x = -0.35;                                    // left end of the row, local to the bookcase
  return BOOKS.map((record) => {
    const width = record.pages / 4000 + 0.02;       // spine thickness from page count
    const book = BABYLON.MeshBuilder.CreateBox(`book${record.id}`, { width, height: 0.3, depth: 0.22, faceUV, wrap: true }, scene);
    book.parent = bookcase;
    book.position.set(x + width / 2, 0.12 + shelf * 0.5 + 0.15, -0.02);
    x += width + 0.012;
    book.material = new BABYLON.StandardMaterial(record.title, scene);
    book.material.diffuseTexture = drawCover(scene, record);
    book.material.specularColor = new BABYLON.Color3(0.15, 0.15, 0.15);
    book.metadata = record;                         // the record, for picking
    return book;
  });
}
// The book cuts the storefront with CSG2 (Manifold WebAssembly); this sandbox cannot download
// the .wasm, so the older pure-JavaScript BABYLON.CSG makes the same cuts here.
function createFrontWall(scene) {
  const box = (width, height, depth, x, y) => {
    const mesh = BABYLON.MeshBuilder.CreateBox('cut', { width, height, depth }, scene);
    mesh.position.set(x, y, -ROOM.depth / 2);
    return mesh;
  };
  const solid = box(ROOM.width, ROOM.height, ROOM.wall, 0, ROOM.height / 2);
  const windowHole = box(3.2, 1.6, 1, -1.4, 1.6), doorHole = box(1.1, 2.2, 1, 2.3, 1.1);
  const wall = BABYLON.CSG.FromMesh(solid).subtract(BABYLON.CSG.FromMesh(windowHole))
    .subtract(BABYLON.CSG.FromMesh(doorHole)).toMesh('frontWall', scene.getMaterialByName('plaster'), scene);
  [solid, windowHole, doorHole].forEach((mesh) => mesh.dispose());
  return wall;
}
// ---- end of the bookstore helpers ----

/*
  A GLSL ShaderMaterial on WebGPU is translated on first use by glslang and TWGSL, about 2.6 MB of
  WebAssembly fetched from Babylon's CDN; this sandbox cannot fetch it, so the spine's shader is
  written in WGSL directly (shaderLanguage: WGSL), as you would to skip the download.
*/
const canvas = document.getElementById('view');
const info = document.getElementById('info');
(async () => {
  if (!(await BABYLON.WebGPUEngine.IsSupportedAsync)) {
    const g = canvas.getContext('2d');
    canvas.width = 600; canvas.height = 280;
    g.fillStyle = '#f7f3ec'; g.fillRect(0, 0, 600, 280);
    g.fillStyle = '#b5452f'; g.font = '18px sans-serif'; g.textAlign = 'center';
    g.fillText('WebGPU is not available in this browser', 300, 140);
    info.textContent = 'WebGPU is not available in this browser.';
    return;
  }
  const engine = new BABYLON.WebGPUEngine(canvas);
  await engine.initAsync();                          // adapter, device, context
  const scene = createScene(engine);
  createRoom(scene);
  createBookcase(scene, 'bookcaseLeft', -1.8);
  const camera = new BABYLON.ArcRotateCamera('overview', -Math.PI / 2, 1.05, 10, new BABYLON.Vector3(0, 1, 0), scene);
  camera.attachControl(canvas, true);
  const spine = new BABYLON.ShaderMaterial('spine', scene, {
    vertexSource: `#include<sceneUboDeclaration>
      #include<meshUboDeclaration>
      attribute position : vec3<f32>;
      @vertex fn main(input : VertexInputs) -> FragmentInputs {
        vertexOutputs.position = scene.viewProjection * mesh.world * vec4<f32>(vertexInputs.position, 1.0);
      }`,
    fragmentSource: `uniform color : vec3<f32>;
      @fragment fn main(input : FragmentInputs) -> FragmentOutputs {
        fragmentOutputs.color = vec4<f32>(uniforms.color, 1.0);
      }`,
  }, { attributes: ['position'], uniformBuffers: ['Scene', 'Mesh'], uniforms: ['color'],
    shaderLanguage: BABYLON.ShaderLanguage.WGSL });
  spine.setColor3('color', BABYLON.Color3.FromHexString('#e09a10'));
  const book = BABYLON.MeshBuilder.CreateBox('book', { width: 0.3, height: 1 }, scene);
  book.position.set(0, 0.5, 1);
  book.material = spine;
  const started = performance.now();
  engine.runRenderLoop(() => scene.render());
  scene.executeWhenReady(() => {
    const walnut = scene.getMeshByName('bookcaseLeft').subMeshes[0].effect;
    info.textContent = `${engine.description}: ready in ${Math.round(performance.now() - started)} ms\n` +
      `walnut effect: ${BABYLON.ShaderLanguage[walnut.shaderLanguage]}, spine effect: ` +
      `${BABYLON.ShaderLanguage[spine.getEffect().shaderLanguage]}`;
  });
  addEventListener('resize', () => engine.resize());
})().catch((error) => { info.textContent = `WebGPU failed: ${error.message}`; });
</script>