「WebGPU」でシェーダーを使って三角形を1つだけ描画してみよう

黒い三角形を1個だけ描画する
やっと三角形を1個だけ描画するところまできました(図3)。これだけのことなのに、これほど面倒ですね。今後の回以降はこれに肉付けしていき、複数の三角形を描画してオブジェクトを作ったり、様々な色にしたり、テクスチャを貼ったり、移動したり、アニメーションしたりします。
「index.html」ファイルで「lib」→「Model3D.js」を読み込む
三角形のデータを保持するクラスとして「Model3D」クラスをコーディングする「Model3D.js」ファイルを読み込みます。<script>タグの「init」関数でModel3Dクラスのインスタンスを生成して「_model」変数に代入し、頂点バッファを初期化します。「draw」関数でモデルを描画します。
・サンプルコード「index.html」<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>UltraMotion3D</title>
<script src="lib/Model3D.js"></script>
<script src="lib/WGSL.js"></script>
<script src="lib/UltraMotion3D.js"></script>
<script type="text/javascript">
var _model;
function init() {
_model = new Model3D();
_model.initBuffers();
}
function draw() {
_model.draw();
}
</script>
</head>
<body onload='initWebGPU("CanvasAnimation");'>
<canvas id="CanvasAnimation" width="1000" height="900"></canvas>
</body>
</html>
「lib」→「UltraMotion3D.js」に追記する
次のサンプルコードを「UltraMotion3D.js」ファイルに追記します。背景色を(R,G,B,A)=(1.0,1.0,1.0,1.0)の真っ白に変えました。描画のタイミングで「index.html」の「draw」関数を呼び出しているだけです。
・サンプルコード「lib」→「UltraMotion3D.js」var _passEncoder = null;
async function initWebGPU(canvas) {
(中略)
}
function setPipeline(vertexWGSL,fragmentWGSL) {
(中略)
}
function everyFrame() {
_commandEncoder = _device.createCommandEncoder();
const textureView = _context.getCurrentTexture().createView();
_renderPassDescriptor = {
colorAttachments: [
{
view: textureView,
clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
loadOp: 'clear',
storeOp: 'store',
},
],
depthStencilAttachment: {
view: _depthTexture.createView(),
depthClearValue: 1.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
},
};
_passEncoder = _commandEncoder.beginRenderPass(_renderPassDescriptor);
draw();
_passEncoder.end();
_device.queue.submit([_commandEncoder.finish()]);
}
function resize() {
(中略)
}
Model3Dクラスを実装する
「lib」→「Model3D.js」ファイルに3Dモデルを持つModel3Dクラスを宣言します。コンストラクタで3頂点と、それらを指すインデックスのプロパティを宣言します。「initBuffers」メソッドで頂点を入れるバッファを用意します。「draw」メソッドで3頂点の三角形を描画します。
次のサンプルコードを実装したら、index.htmlファイルをGoogle Chromeで開いてください。黒い三角形が表示されましたね。
・サンプルコード「lib」→「Model3D.js」class Model3D {
constructor() {
this.vertices = [0,1,0,1, -1,-1,0,1, 1,-1,0,1];
this.indices = [0,1,2];
}
initBuffers() {
this.vertexArray = new Float32Array(this.vertices);
this.verticesBuffer = _device.createBuffer({
size: this.vertexArray.byteLength,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});
new Float32Array(this.verticesBuffer.getMappedRange()).set(this.vertexArray);
this.verticesBuffer.unmap();
}
getVertexCount() {
return ~~(this.vertexArray.length/4);
}
draw() {
_passEncoder.setPipeline(_pipeline);
_passEncoder.setVertexBuffer(0,this.verticesBuffer);
_passEncoder.draw(this.getVertexCount());
}
}
【サンプルコードの解説】
コンストラクタ(「constructor」メソッド)で(X,Y,Z,W)の3頂点を「vertices」プロパティに代入し、3頂点を指すインデックスを「indices」プロパティに代入します。
「initBuffers」メソッドでverticesプロパティは「Float32Array」配列に変換し、頂点バッファに入れます。
「draw」メソッドでパスエンコーダーにパイプラインをセットし、頂点バッファをセットして頂点の数だけ描画します。
新たなユーザーインターフェースを生み出したいです。昔は文字だけでOSを操っていて、今はグラフィカルにマウスでOSを操ります。そこでその次を考えていて、誰でも使えるぐらいシンプルなOSのユーザーインターフェースを考えました。例えば「Webブラウザを起動しますか?」と聞かれたら、YesかNoだけで答えます。他にも全てYesかNoだけで操作します。でも、あまりにも自由度が低過ぎました…。
おわりに
今回は3つの頂点をシェーダーに渡し、それを囲んで真っ黒な三角形を描画する解説をしました。まだ色のデータは渡していないため、フラグメントシェーダーの中で黒色限定で塗ってみました。
次回は、ベクトルとベクトルの計算を実装する解説をします。


