Babylon.jsでWebGPUを動かすはじめの一歩

この記事は[Babylon.js Advent Calendar 2022](https://qiita.com/advent-calendar/2022/babylonjs)の14日目の記事です。

# はじめに

この記事ではWebGPUの最初の一歩として、WebGPU対応ブラウザのインストールから、Babylon.jsで三角形を描画するまでを解説します。


WebGPUはJavascriptで実行できる低レベルグラフィックスAPIです。WebGPUは現在仕様策定中であり、WebGL、WebGL2よりも高速になることが期待されています。

Babylon.jsは実験的にWebGPUをサポートしており、Playgroundを使って気軽に試すことができます。

# 準備

WebGPUは現在仕様策定中であり、開発者向けブラウザでのみ動作します。

各ブラウザの対応状況は、[こちら](https://github.com/gpuweb/gpuweb/wiki/Implementation-Status)から確認できます。

## Chrome Canaryのインストール

以下のサイトからChrome Canaryのインストーラをダウンロードします。
[https://www.google.com/chrome/canary/](https://www.google.com/chrome/canary/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953971/b2a934be-a632-49d4-9b7c-d2860c6a6812.png)
中央にある「Download Chrome Canary」をクリックすると、インストーラがダウンロードされます。
ダウンロードした「ChromeSetup.exe」を実行するとインストールが開始され、完了するとChrome Canaryが自動で立ち上がります。

## フラグの有効化

WebGPUはChrome Canaryをインストールしただけでは動きません。ブラウザの設定からフラグを有効化する必要があります。

:::note warn
WebGPUは実験的な機能であるため、有効化することはセキュリティ上のリスクがあります。有効化した場合は信頼できるサイトのみにアクセスするようにしてください。
:::

Chrome Canaryのアドレスバーに以下を入力し、実験機能の設定画面にアクセスします。
`chrome://flags/#enable-unsafe-webgpu`

「Unsafe WebGPU」を「Disable」から「Enabled」に変え、「Relaunch」をクリックしてChrome Canaryを再起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953971/886e44ad-c7bf-f086-80d2-e9990b45dd57.png)
これで、WebGPUを動かす準備が整いました。

## WebGPUが動くか確認する

Babylon.jsのPlaygroundにアクセスします。
[https://playground.babylonjs.com/](https://playground.babylonjs.com/)

右上のボタンから、描画に使用するグラフィックスAPIを選択できるようになっています。デフォルトでは「WebGL2」になっており、WebGPUを有効化できていれば「WEBGPU」も選択できるようになっているはずです。

こちらを「WEBGPU」に切り替えてみましょう。切り替えた後にシーンが描画されれば準備OKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953971/13f2298d-1128-90ba-20b1-df4e4d0759d0.png)

# 三角形を描画する

まずは緑色の三角形（クリスマスツリー）を描画してみます。
サンプルコードは[こちら](https://playground.babylonjs.com/#4WXIY7#2)です。
![screenshot_22-12-14_2-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953971/386f8712-e3bd-3cc4-f941-35b2c2cc4495.png)

## シェーダーコードの記述

はじめに、頂点シェーダーとフラグメントシェーダーを`ShadersStoreWGSL`に格納しておきます。

```javascript:shaders.js
BABYLON.ShaderStore.ShadersStoreWGSL["vertexVertexShader"]= ` 
    @vertex
    fn main(input : VertexInputs) -> FragmentInputs {

        var pos = array<vec2<f32>, 3>(
            vec2<f32>(0.0, 1.0),
            vec2<f32>(-1.0, -1.0),
            vec2<f32>(1.0, -1.0)
        );

        gl_Position = vec4<f32>(pos[gl_VertexID], 0.0, 1.0);
    }
`;

BABYLON.ShaderStore.ShadersStoreWGSL["fragmentFragmentShader"]=`
    @fragment
    fn main(input : FragmentInputs) -> FragmentOutputs {
        gl_FragColor = vec4<f32>(0.0, 1.0, 0.0, 1.0);;
    }
`;
```

WebGPUでは、WGSL(WebGPU Shading Language)を使ってシェーダーを記述します。
[WGSLのドキュメント](https://gpuweb.github.io/gpuweb/wgsl/)を読むと、`@binding`、`@location`、`@builtin`やらの属性をきちんと記述する必要があってGLSLよりも書くのが大変そうという印象ですが、上記のコードはすごくシンプルになっています。

実は、Babylon.jsは上記のコードを次のように書き換えてコンパイルしており、Babylon.jsが煩雑な部分を隠してくれているおかげでシンプルにコードを記述できるようになっています。

```wgsl:vertexShader.wgsl
var<private> gl_VertexID : u32;
var<private> gl_InstanceID : u32;
var<private> gl_Position : vec4<f32>;

struct VertexInputs {
    @builtin(vertex_index) vertexIndex : u32,
    @builtin(instance_index) instanceIndex : u32,
};

struct FragmentInputs {
    @builtin(position) position : vec4<f32>,
};

struct Internals {
    yFactor_: f32,
    textureOutputHeight_: f32,
};

@group(1) @binding(0) var<uniform> internals : Internals;

@vertex
fn main(input : VertexInputs) -> FragmentInputs {  var output : FragmentInputs;
    gl_VertexID = input.vertexIndex;
    gl_InstanceID = input.instanceIndex;

    var pos = array<vec2<f32>, 3>(
        vec2<f32>(0.0, 1.0),
        vec2<f32>(-1.0, -1.0),
        vec2<f32>(1.0, -1.0)
    );

    gl_Position = vec4<f32>(pos[gl_VertexID], 0.0, 1.0);
    output.position = gl_Position;
    output.position.y = output.position.y * internals.yFactor_;
    return output;
}
```

```wgsl:fragmentShader.wgsl
var<private> gl_FragCoord : vec4<f32>;
var<private> gl_FrontFacing : bool;
var<private> gl_FragColor : vec4<f32>;
var<private> gl_FragDepth : f32;

struct FragmentInputs {
    @builtin(position) position : vec4<f32>,
    @builtin(front_facing) frontFacing : bool,
};

struct FragmentOutputs {
    @location(0) color : vec4<f32>,
};

struct Internals {
    yFactor_: f32,
    textureOutputHeight_: f32,
};

@group(1) @binding(1) var<uniform> internals : Internals;

@fragment
fn main(input : FragmentInputs) -> FragmentOutputs {  var output : FragmentOutputs;
    gl_FragCoord = input.position;
    gl_FrontFacing = input.frontFacing;

    gl_FragColor = vec4<f32>(0.0, 1.0, 0.0, 1.0);
    output.color = gl_FragColor;
    return output;
}
```

## createScene関数

createScene関数の中身もシンプルです。シェーダーコードをコンパイルして、画面に描画する処理を書きます。

```javascript:createScene.js
let createScene = function() {
    
    let scene = new BABYLON.Scene(engine);

    let camera = new BABYLON.Camera("camera", BABYLON.Vector3.Zero(), scene);

    // シェーダーコードをコンパイルする
    const shader = new BABYLON.EffectWrapper({
        engine: engine,
        useShaderStore : true,
        fragmentShader: "fragment",
        vertexShader: "vertex",
        shaderLanguage: BABYLON.ShaderLanguage.WGSL,
    });

    // レンダラーを準備する
    const renderer = new BABYLON.EffectRenderer(engine);

    // シェーダーに描画指令を出す
    scene.onAfterRenderObservable.add(() => {
        renderer.render(shader, null);
    })

    return scene;
}
```

## 三角形にグラデーションで色を付ける

シェーダーを次のように書き換え、各頂点に別々の色を設定するとグラデーションで三角形が描画されます。
サンプルコードは[こちら](https://playground.babylonjs.com/#4WXIY7#1)です。

![screenshot_22-12-14_0-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953971/198ed062-ba9b-4b53-57f6-716ebd557971.png)


```javascript:shaders.js
BABYLON.ShaderStore.ShadersStoreWGSL["vertexVertexShader"]= ` 
    varying vColor : vec4<f32>;

    @vertex
    fn main(input : VertexInputs) -> FragmentInputs {

        var pos = array<vec2<f32>, 3>(
            vec2<f32>(0.0, 1.0),
            vec2<f32>(-1.0, -1.0),
            vec2<f32>(1.0, -1.0)
        );

        var color = array<vec4<f32>, 3>(
            vec4<f32>(1.0, 0.0, 0.0, 1.0),
            vec4<f32>(0.0, 1.0, 0.0, 1.0),
            vec4<f32>(0.0, 0.0, 1.0, 1.0)
        );

        gl_Position = vec4<f32>(pos[gl_VertexID], 0.0, 1.0);
        vColor = color[gl_VertexID];
    }
`;

BABYLON.ShaderStore.ShadersStoreWGSL["fragmentFragmentShader"]=`
    varying vColor : vec4<f32>;

    @fragment
    fn main(input : FragmentInputs) -> FragmentOutputs {
        gl_FragColor = vColor;
    }
`;
```


# おわりに

Babylon.jsの公式ドキュメントにもWebGPUのサンプルコードがいくつかありますので、ぜひのぞいてみてください。

https://doc.babylonjs.com/setup/support/webGPU