Babylon.js WebGPU Compute Shader ( Chrome Canary )
Babylon.js のWebGPUで Compute Shader が使えるようになりました。
( v5.0.0-alpha で動作します。)
Compute Shaders | Babylon.js Documentation
https://doc.babylonjs.com/advanced_topics/shaders/computeShader
この画面からSimple compute shaders example を実行しようとすると、
以下の createStorageBuffer に関するエラーが出て実行できません。
下のサイトを参考にして、実行できるようにトライしてみました。
・Babylon.js で WebGPU を試してみるテスト - Qiita
https://qiita.com/cx20/items/e46bd22f0d9e47e81df1
・Babylon.jsでWebGPU試してみる : IZUN∀ TEC - livedoor
http://blog.livedoor.jp/mizuki_izuna/archives/24967180.html
Playground 画面からプログラムをコピーし、html の script 部分にそまま
貼り付けて実行しました。
実行結果
(下: マウスで移動した様子)
コンソール画面
・compute shader の計算結果が表示されています。
・使用しているライブラリのバージョン( v5.0.0-aplha.22 )も表示されます。
babylon.js 等のライブラリは、preview 版を 使用しています。
(Babylon.js https://github.com/BabylonJS/Babylon.js の readme)
shader には WGSL が使用できます。
プログラム
Simple_compute_shaders.html
<!DOCTYPE html> <html> <head> <title>Babylon.js WebGPU Simple Compute Shaders(playground)</title> <script src="https://preview.babylonjs.com/babylon.max.js"></script> <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script> </head> <body> <canvas id="renderCanvas" width="800" height="650"></canvas> <script> async function init() { const canvas = document.getElementById("renderCanvas"); const engine = new BABYLON.WebGPUEngine(canvas); await engine.initAsync(); // from playground var createScene = function () { // This creates a basic Babylon Scene object (non-mesh) var scene = new BABYLON.Scene(engine); // This creates and positions a free camera (non-mesh) var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene); // This targets the camera to scene origin camera.setTarget(BABYLON.Vector3.Zero()); // This attaches the camera to the canvas camera.attachControl(canvas, true); // This creates a light, aiming 0,1,0 - to the sky (non-mesh) var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene); // Default intensity is 1. Let's dim the light a small amount light.intensity = 0.7; // Our built-in 'sphere' shape. var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, scene); // Move the sphere upward 1/2 its height sphere.position.y = 1; // Our built-in 'ground' shape. var ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene); // -------- COMPUTE 1 ------------------------- // const cs1 = new BABYLON.ComputeShader("myCompute", engine, { computeSource: copyTextureComputeShader }, { bindingsMapping: { "dest": { group: 0, binding: 0 }, "src": { group: 0, binding: 2 } } }); const src = new BABYLON.Texture("textures/ground.jpg", scene); const dest = new BABYLON.RawTexture.CreateRGBAStorageTexture(null, 512, 512, scene, false, false); cs1.setTexture("src", src); cs1.setStorageTexture("dest", dest); cs1.dispatchWhenReady(dest.getSize().width, dest.getSize().height, 1).then(() => { dest.readPixels().then((data) => { //console.log(data); }); }); const mat = new BABYLON.StandardMaterial("mat", scene); mat.diffuseTexture = dest; ground.material = mat; // -------- COMPUTE 2 ------------------------- // const cs2 = new BABYLON.ComputeShader("myCompute2", engine, { computeSource: clearTextureComputeShader }, { bindingsMapping: { "tbuf": { group: 0, binding: 0 }, "params": { group: 0, binding: 1 } } }); const dest2 = new BABYLON.RawTexture.CreateRGBAStorageTexture(null, 512, 512, scene, false, false); const uBuffer = new BABYLON.UniformBuffer(engine); uBuffer.updateColor4("color", new BABYLON.Color3(1, 0.6, 0.8), 1); uBuffer.update(); cs2.setStorageTexture("tbuf", dest2); cs2.setUniformBuffer("params", uBuffer); cs2.dispatchWhenReady(dest2.getSize().width, dest2.getSize().height, 1); const mat2 = new BABYLON.StandardMaterial("mat2", scene); mat2.diffuseTexture = dest2; sphere.material = mat2; // -------- COMPUTE 3 ------------------------- // const cs3 = new BABYLON.ComputeShader("myCompute3", engine, { computeSource: matrixMulComputeShader }, { bindingsMapping: { "firstMatrix": { group: 0, binding: 0 }, "secondMatrix": { group: 0, binding: 1 }, "resultMatrix": { group: 0, binding: 2 } } }); const firstMatrix = new Float32Array([ 2 /* rows */, 4 /* columns */, 1, 2, 3, 4, 5, 6, 7, 8 ]); const bufferFirstMatrix = new BABYLON.StorageBuffer(engine, firstMatrix.byteLength); bufferFirstMatrix.update(firstMatrix); const secondMatrix = new Float32Array([ 4 /* rows */, 2 /* columns */, 1, 2, 3, 4, 5, 6, 7, 8 ]); const bufferSecondMatrix = new BABYLON.StorageBuffer(engine, secondMatrix.byteLength); bufferSecondMatrix.update(secondMatrix); const bufferResultMatrix = new BABYLON.StorageBuffer(engine, Float32Array.BYTES_PER_ELEMENT * (2 + firstMatrix[0] * secondMatrix[1])); cs3.setStorageBuffer("firstMatrix", bufferFirstMatrix); cs3.setStorageBuffer("secondMatrix", bufferSecondMatrix); cs3.setStorageBuffer("resultMatrix", bufferResultMatrix); cs3.dispatchWhenReady(firstMatrix[0], secondMatrix[1]).then(() => { bufferResultMatrix.read().then((res) => { // we know the result buffer contains floats const resFloats = new Float32Array(res.buffer); console.log(resFloats); }); }); return scene; }; const clearTextureComputeShader = ` [[group(0), binding(0)]] var tbuf : [[access(write)]] texture_storage_2d<rgba8unorm>; [[block]] struct Params { color : vec4<f32>; }; [[group(0), binding(1)]] var<uniform> params : Params; [[stage(compute), workgroup_size(1, 1, 1)]] fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) { textureStore(tbuf, vec2<i32>(global_id.xy), params.color); } `; const copyTextureComputeShader = ` [[group(0), binding(0)]] var dest : [[access(write)]] texture_storage_2d<rgba8unorm>; [[group(0), binding(1)]] var samplerSrc : sampler; [[group(0), binding(2)]] var src : texture_2d<f32>; [[stage(compute), workgroup_size(1, 1, 1)]] fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) { let dims : vec2<f32> = vec2<f32>(textureDimensions(src, 0)); let pix : vec4<f32> = textureSampleLevel(src, samplerSrc, vec2<f32>(global_id.xy) / dims, 0.0); textureStore(dest, vec2<i32>(global_id.xy), pix); } `; const matrixMulComputeShader = ` [[block]] struct Matrix { size : vec2<f32>; numbers: array<f32>; }; [[group(0), binding(0)]] var<storage> firstMatrix : [[access(read)]] Matrix; [[group(0), binding(1)]] var<storage> secondMatrix : [[access(read)]] Matrix; [[group(0), binding(2)]] var<storage> resultMatrix : [[access(write)]] Matrix; [[stage(compute)]] fn main([[builtin(global_invocation_id)]] global_id : vec3<u32>) { resultMatrix.size = vec2<f32>(firstMatrix.size.x, secondMatrix.size.y); let resultCell : vec2<u32> = vec2<u32>(global_id.x, global_id.y); var result : f32 = 0.0; for (var i : u32 = 0u; i < u32(firstMatrix.size.y); i = i + 1u) { let a : u32 = i + resultCell.x * u32(firstMatrix.size.y); let b : u32 = resultCell.y + i * u32(secondMatrix.size.y); result = result + firstMatrix.numbers[a] * secondMatrix.numbers[b]; } let index : u32 = resultCell.y + resultCell.x * u32(secondMatrix.size.y); resultMatrix.numbers[index] = result; } `; const scene = createScene(); engine.runRenderLoop(() => { scene.render(); }); }; init(); </script> </body> </html>