「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つの頂点をシェーダーに渡し、それを囲んで真っ黒な三角形を描画する解説をしました。まだ色のデータは渡していないため、フラグメントシェーダーの中で黒色限定で塗ってみました。
次回は、ベクトルとベクトルの計算を実装する解説をします。