概要
前回の記事では、WebGLとWebGPUで赤い四角形を描画して、両者のシェーダー言語の違いを比較しました。
今回は、WebGPUで実用的なアプリケーションを開発する際に必要となる2つの機能を実装します。
これらはWebGLシリーズで取り上げた以下の内容のWebGPU版となります。
キャンバスサイズの調整
問題点
WebGPUでもWebGLと同様に、canvasのサイズ設定には2つの概念があります。
- CSSでの表示サイズ
- 内部解像度(canvas.width/height)
これらが一致していないと、描画結果がボケてしまいます。
WebGPU版の実装
async function initWebGPU() {
const canvas = document.getElementById('webgl-canvas') as HTMLCanvasElement;
const adapter = await navigator.gpu.requestAdapter() as GPUAdapter;
const device = await adapter.requestDevice();
const context = canvas.getContext('webgpu') as GPUCanvasContext;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
// ... 頂点バッファ、シェーダー、パイプラインの設定 ...
// 描画処理
function render() {
const encoder = device.createCommandEncoder();
const textureView = context.getCurrentTexture().createView();
const renderPass = encoder.beginRenderPass({
colorAttachments: [
{
view: textureView,
loadOp: 'clear',
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
storeOp: 'store',
},
],
});
renderPass.setPipeline(pipeline);
renderPass.setVertexBuffer(0, vertexBuffer);
renderPass.draw(4);
renderPass.end();
device.queue.submit([encoder.finish()]);
}
// キャンバスサイズの調整とコンテキスト設定
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
context.configure({
device: device,
format: canvasFormat,
});
// リサイズ後に再描画
render();
}
// 初期化時とリサイズ時にキャンバスサイズを調整
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
}
initWebGPU();
WebGLとの違い
WebGLではgl.viewport()の呼び出しが必要でした。
// WebGL版
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
WebGPUではcontext.configure()を呼び出すことで、コンテキストが再設定され、新しいサイズに対応します。
レンダーパスのアタッチメントがデフォルトでキャンバスサイズに合わせられるため、gl.viewport()のような明示的なビューポート設定は通常不要です。
WGSLシェーダーファイルの分割
問題点
シェーダーコードをTypeScriptファイル内に文字列として埋め込むと、以下の問題があります。
- シンタックスハイライトが効かない
- コードが長くなると可読性が低下
- 複数箇所で同じシェーダーを使う場合に重複
WebGPU版の実装
シェーダーを別ファイルに分割します。
@vertex
fn vertexMain(@location(0) position: vec2f) -> @builtin(position) vec4f {
return vec4f(position, 0.0, 1.0);
}
@fragment
fn fragmentMain() -> @location(0) vec4f {
return vec4f(1.0, 0.0, 0.0, 1.0); // 赤色
}
Viteの?rawサフィックスを使用して、ファイル内容を文字列としてインポートします。
import vertexShaderCode from './shaders/plane.vert.wgsl?raw';
import fragmentShaderCode from './shaders/plane.frag.wgsl?raw';
async function initWebGPU() {
// ... 初期化処理 ...
// WGSLシェーダーの定義(別ファイルから読み込み)
const shaderModule = device.createShaderModule({
code: vertexShaderCode + fragmentShaderCode,
});
// ... パイプライン設定 ...
}
WebGLとの違い
WebGLでも?rawサフィックスを使ってGLSLファイルをインポートすることは可能でした。
// WebGL版(?rawを使う場合)
import vertexShaderSource from './shaders/plane.vert.glsl?raw';
import fragmentShaderSource from './shaders/plane.frag.glsl?raw';
しかし、?rawインポートではファイル内容が単なるstring型として扱われるため、型安全性が失われます。
そこで、WebGLではvite-plugin-glslを使用していました。
// WebGL版(vite-plugin-glslを使う場合)
import vertexShaderSource from './shaders/plane.vert.glsl';
import fragmentShaderSource from './shaders/plane.frag.glsl';
この場合、package.jsonにvite-plugin-glslを追加し、vite.config.tsでプラグインを設定する必要がありました。
import { defineConfig } from 'vite';
import glsl from 'vite-plugin-glsl';
export default defineConfig({
plugins: [glsl()],
});
WebGPUでは、WGSLファイルに対応した同様のプラグインが現時点では存在しないため、?rawインポートを使用しています。
将来的にWGSL用のViteプラグインが登場すれば、そちらを使用することで型安全性を向上できる可能性があります。
VS Code拡張機能の推奨
VS Codeを使用している場合、以下の拡張機能をインストールすることで、WGSLファイルのシンタックスハイライトとコード補完が有効になります。
この拡張機能により、以下の機能が利用できます。
- WGSLのシンタックスハイライト
- コード補完
- 定義へのジャンプ
- 型情報のインレイヒント表示
まとめ
| 項目 | WebGL | WebGPU |
|---|---|---|
| キャンバスサイズ調整 |
gl.viewport()が必要 |
context.configure()のみ |
| リサイズ時の再描画 | 必要 | 必要 |
| シェーダーファイル分割 |
vite-plugin-glslを使用 |
?rawインポートを使用(現在はプラグインが存在しないため) |
| プラグイン設定 |
vite.config.tsに設定必要 |
プラグイン不使用なので不要 |