search
LoginSignup
43

More than 1 year has passed since last update.

posted at

updated at

[忙しい人向け] 100行から始めるWebGPU(WGSL対応版)

この記事はWeb グラフィックス Advent Calendar 2020の13日目の記事です。

はじめに

WebGPUWebGLWebGL2 の後継とされているグラフィックス API です。
まだ仕様策定中の段階の為、仕様変更等が入る可能性があることにご注意ください。

ここでは、この1年で変更になった機能、主に、シェーダ機能「WGSL」(WebGPU Shading Language)について試してみようと思います。

一年前までは WebGPU 用のシェーダの方針が決まっていなかった為、glslang と呼ばれるライブラリを用いて GLSLSPIR-V(シェーダ中間言語)にコンパイルして使用する形が取られていました。
現在は、Chrome Canary で新しいシェーダ WGSL が実験的に使えるようになってきた為、それを使ってみようと思います。

WGSL とは

WGSLWebGPU Shading Language の略で、その名の通り WebGPU 用のシェーダです。
GLSL では C 言語ライクな構文でしたが WGSL では Rust に似た構文が採用されています。

GLSL と WGSL のコードの比較

箇所 GLSL WGSL
VSコード image.png image.png
FSコード image.png image.png
シェーダ設定 image.png image.png

WebGPU の有効化方法

WebGPU は Chrome CanaryFirefox Nightly でフラグを有効化することで試すことができます。
ただし 2020年12月時点では、WGSLChrome Canary でしか動作しないようです。
なお、これらの機能は、実験的な機能である為、有効化したまま、信頼できない Web へのアクセスは行わないでください。

ブラウザ 対応するバックエンドのライブラリ WebGPU 有効化方法
Chrome Canary Dawn chrome://flags/#enable-unsafe-web
Firefox Nightly gfx-rs dom.webgpu.enable
Safari Technology Preview webkit Develop -> Experimental Features -> WebGPU

ポリゴンを1枚描画する WebGPU コード

以下、シェーダに WGSL を使用した場合のサンプルコードです。動作確認は Chrome Canary 89 で行いました。他の環境では動作しない可能性がありますのでご注意ください。

index.html
<canvas id="c" width="512" height="512"></canvas>

<script id="vs" type="x-shader/x-vertex">
[[location(0)]] var<in> position : vec3<f32>;
[[builtin(position)]] var<out> Position : vec4<f32>;

[[stage(vertex)]]
fn main() -> void {
    Position = vec4<f32>(position, 1.0);
    return;
}
</script>

<script id="fs" type="x-shader/x-fragment">
[[location(0)]] var<out> outColor : vec4<f32>;

[[stage(fragment)]]
fn main() -> void {
    outColor = vec4<f32>(0.0, 0.0, 1.0, 1.0);
    return;
}
</script>
index.js
const vs = document.getElementById("vs").textContent;
const fs = document.getElementById("fs").textContent;
init();

async function init() {
    const adapter = await navigator.gpu.requestAdapter();
    const device = await adapter.requestDevice();
    const c = document.getElementById('c');
    const ctx = c.getContext('gpupresent');
    const swapChainFormat = "bgra8unorm";
    const swapChain = ctx.configureSwapChain({device, format: swapChainFormat});
    const pipeline = device.createRenderPipeline({
        layout: device.createPipelineLayout({bindGroupLayouts: []}),
        vertexStage: {
            module: device.createShaderModule({
                code: vs
            }),
            entryPoint: 'main'
        },
        fragmentStage: {
            module: device.createShaderModule({
                code:  fs
            }),
            entryPoint: 'main'
        },
        vertexState: {
            vertexBuffers: [{
                arrayStride: 12, // 3 * 4bytes
                attributes: [{
                    shaderLocation: 0,
                    offset: 0,
                    format: "float3"
                }]
            }]
        },
        colorStates: [{
            format: swapChainFormat,
            alphaBlend: {
                srcFactor: "src-alpha",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
            }
        }],
        primitiveTopology: 'triangle-list',
    });

    const positions = [ 
         0.0, 0.5, 0.0, // v0
        -0.5,-0.5, 0.0, // v1
         0.5,-0.5, 0.0  // v2
    ];
    const data = new Float32Array(positions);
    const vertexBuffer = device.createBuffer({
        size: data.byteLength,
        usage: GPUBufferUsage.VERTEX,
        mappedAtCreation: true,
    });
    new Float32Array(vertexBuffer.getMappedRange()).set(data);
    vertexBuffer.unmap();

    const render =  function () {
        const commandEncoder = device.createCommandEncoder();
        const textureView = swapChain.getCurrentTexture().createView();
        const renderPassDescriptor = {
            colorAttachments: [{
                attachment: textureView,
                loadValue: {r: 1.0, g: 1.0, b: 1.0, a: 1.0},
            }]
        };
        const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
        passEncoder.setVertexBuffer(0, vertexBuffer);
        passEncoder.setPipeline(pipeline);
        passEncoder.draw(3, 1, 0, 0);
        passEncoder.endPass();
        device.defaultQueue.submit([commandEncoder.finish()]);
    }
    requestAnimationFrame(render);
}

実際に動作するコードは jsfiddle にアップしてありますので、ご利用ください。 ※ 実行には WebGPU 対応ブラウザが必要です。

[jsfiddle] 100行で WebGPU 試してみるテスト(GLSL版)
[jsfiddle] 100行で WebGPU 試してみるテスト(WGSL版)

その他のサンプル

手前味噌ですが、いくつかの基本的なサンプルについて GLSL 版と WGSL 版のサンプルを下記に置いてありますので興味ある方はどうぞ。

image.png

おわりに

何故シェーダ言語が GLSL ベースではなく Rust ベースの新しい言語になったかは、少し闇が深そうなので、触れないでおきます。。
恐らくここから方針転換は無いと思うので、WebGPU の新しいシェーダ言語 WGSL に今のうちから慣れておくのも良いのではないでしょうか。
今のところ仕様策定の完成目標としては 2022年のQ1を予定しているそうです。
まだ Chrome Canary でしか試すことが出来ませんが、来年の半ばくらいには安定版でもフラグ有効化することで試せるようになりそうです。

参考情報

■ WebGPU Samples (WebGPU サンプル)
https://github.com/austinEng/webgpu-samples
■ Raw WebGPU (WebGPU のチュートリアル)
https://alain.xyz/blog/raw-webgpu
■ A Taste of WebGPU in Firefox (Firefox で WebGPU を試してみる)
https://hacks.mozilla.org/2020/04/experimental-webgpu-in-firefox/
■ From 0 to glTF with WebGPU: The First Triangle (WebGPU でゼロから glTF を表示してみる)
https://www.willusher.io/graphics/2020/06/15/0-to-gltf-triangle
■ PSA for Chromium / Dawn WebGPU API updates 2020-07-28(WebGPU API の破壊的変更点)
https://hackmd.io/szV68rOVQ56GYzPJMBko8A#PSA-for-Chromium--Dawn-WebGPU-API-updates-2020-07-28
■ WebGL と WebGPU の違い
https://twitter.com/SketchpunkLabs/status/1194651079776067586
■ Implementation Status (WebGPU の実装ステータス)
https://github.com/gpuweb/gpuweb/wiki/Implementation-Status

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
43