Web 上の三角形が何かを描く

Patricia Arquette
リリース: 2024-11-30 02:42:10
オリジナル
148 人が閲覧しました

このシリーズでは、WebGPU とコンピューター グラフィックス全般を紹介します。

まず、何を構築するかを見てみましょう。

人生ゲーム

Triangles On Web Chraw Something

3D レンダリング

Triangles On Web Chraw Something

3D レンダリング、ただし照明あり

Triangles On Web Chraw Something

3D モデルのレンダリング

Triangles On Web Chraw Something

JS の基本的な知識を除き、事前知識は必要ありません。

チュートリアルはソース コードとともに私の github ですでに完成しています。

WebGPU は、GPU 用の比較的新しい API です。 WebGPU という名前が付けられていますが、実際には Vulkan、DirectX 12、Metal、OpenGL、WebGL の上にあるレイヤーと考えることができます。これは低レベル API として設計されており、ゲームやシミュレーションなどの高パフォーマンス アプリケーションでの使用を目的としています。

この章では、画面上に何かを描画します。最初の部分では、Google Codelabs チュートリアルについて説明します。画面上にライフゲームを作成していきます。

出発点

TypeScript を有効にして、vite に空のバニラ JS プロジェクトを作成します。次に、余分なコードをすべて削除し、main.ts.
だけを残します。

const main = async () => {
    console.log('Hello, world!')
}

main()
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

実際にコーディングする前に、ブラウザで WebGPU が有効になっているかどうかを確認してください。 WebGPU サンプルで確認できます。

Chrome はデフォルトで有効になりました。 Safari では、開発者設定に移動し、フラグ設定を行って、WebGPU を有効にする必要があります。

WebGPU の 3 つのタイプを有効にし、@webgpu/types をインストールし、tsc コンパイラ オプションに "types": ["@webgpu/types"] を追加する必要もあります。

さらに、

を置き換えます。

三角形を描く

WebGPU には多くの定型コードがあります。以下にそのコードを示します。

要求元のデバイス

まず、GPU にアクセスする必要があります。 WebGPU では、GPU とブラウザーの間のブリッジであるアダプターの概念によってこれが行われます。

const adapter = await navigator.gpu.requestAdapter();
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

次に、アダプターからデバイスをリクエストする必要があります。

const device = await adapter.requestDevice();
console.log(device);
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

キャンバスを構成する

キャンバス上に三角形を描きます。 Canvas 要素を取得して構成する必要があります。

const canvas = document.getElementById('app') as HTMLCanvasElement;
const context = canvas.getContext("webgpu")!;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
    device: device,
    format: canvasFormat,
});
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ここでは、getContext を使用してキャンバスに関する相対情報を取得します。 webgpu を指定することで、WebGPU でのレンダリングを担当するコンテキストを取得します。

CanvasFormat は実際にはカラー モード (srgb など) です。通常は、優先される形式のみを使用します。

最後に、デバイスとフォーマットのコンテキストを設定します。

GPU レンダリング パイプラインについて

エンジニアリングの詳細に入る前に、まず GPU がレンダリングをどのように処理するかを理解する必要があります。

GPU レンダリング パイプラインは、GPU が画像をレンダリングするために実行する一連のステップです。

GPU 上で実行されるアプリケーションはシェーダーと呼ばれます。シェーダはGPU上で動作するプログラムです。シェーダーには特別なプログラミング言語があります。これについては後で説明します。

レンダー パイプラインには次の手順があります。

  1. CPU はデータを GPU にロードします。 CPU は、GPU リソースを節約するために、いくつかの非表示オブジェクトを削除する場合があります。
  2. CPU は、GPU がシーンをレンダリングするために必要なすべての色、テクスチャ、およびその他のデータを設定します。
  3. CPU は GPU への描画呼び出しをトリガーします。
  4. GPU は CPU からデータを取得し、シーンのレンダリングを開始します。
  5. GPU は、シーンの頂点を処理するジオメトリ プロセスに実行されます。
  6. ジオメトリ プロセスの最初のステップは、シーンの頂点を処理する頂点シェーダーです。頂点を変換したり、頂点の色を変更したり、頂点に対して他の操作を行ったりすることがあります。
  7. 次のステップは、シーンの頂点を処理するテッセレーション シェーダーです。頂点の再分割を実行します。その目的は、シーンの詳細を高めることです。手順もたくさんありますが、複雑すぎてここでは説明できません。
  8. 次のステップは、シーンの頂点を処理するジオメトリ シェーダーです。開発者が 1 つの頂点の変換方法のみを定義できる頂点シェーダーとは対照的に、ジオメトリ シェーダーは複数の頂点の変換方法を定義できます。また、新しい頂点を作成し、それを使用して新しいジオメトリを作成することもできます。
  9. ジオメトリ プロセスの最後のステップには、画面を超える不必要な部分を削除するクリッピングと、カメラに表示されない非表示の部分を削除するカリングが含まれます。
  10. 次のステップはラスター化プロセスで、頂点をフラグメントに変換します。フラグメントは、画面上にレンダリングされるピクセルです。
  11. 次のステップは三角形の反復であり、シーンの三角形を反復します。
  12. 次のステップは、シーンのフラグメントを処理するフラグメント シェーダーです。フラグメントの色を変更したり、フラグメントのテクスチャを変更したり、フラグメントに対して他の処理を行ったりする場合があります。この部分では、深さテストとステンシルテストも実行されます。深度テストとは、各フラグメントに深度値を与えることを意味し、深度値が最も小さいフラグメントがレンダリングされます。ステンシルテストとは各フラグメントにステンシル値を与えることを意味し、ステンシルテストに合格したフラグメントがレンダリングされます。ステンシル値は開発者によって決定されます。
  13. 次のステップは、シーンの断片をブレンドするブレンド プロセスです。たとえば、2 つのフラグメントが重なっている場合、ブレンド プロセスでは 2 つのフラグメントが一緒にブレンドされます。
  14. 最後のステップは出力プロセスで、フラグメントをスワップ チェーンに出力します。スワップ チェーンは、シーンのレンダリングに使用されるイメージのチェーンです。より簡単に言うと、画面に表示される画像を保持するバッファです。

GPU がレンダリングできる最小単位であるプリミティブに応じて、パイプラインのステップが異なる場合があります。通常、三角形を使用します。これは、頂点の 3 つのグループごとに三角形として扱うように GPU に信号を送ります。

レンダーパスの作成

レンダー パスは、完全な GPU レンダリングのステップです。レンダー パスが作成されると、GPU はシーンのレンダリングを開始し、終了するとその逆になります。

レンダー パスを作成するには、レンダー パスを GPU コードにコンパイルするエンコーダーを作成する必要があります。

const main = async () => {
    console.log('Hello, world!')
}

main()
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

次に、レンダー パスを作成します。

const adapter = await navigator.gpu.requestAdapter();
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ここでは、カラーアタッチメントを含むレンダーパスを作成します。アタッチメントは、レンダリングされる画像を表す GPU の概念です。画像には GPU が処理する必要がある多くの側面があり、それらはそれぞれ添付ファイルです。

ここではアタッチメントが 1 つだけあり、それがカラーアタッチメントです。ビューは GPU がレンダリングするパネルであり、ここではキャンバスのテクスチャに設定します。

loadOp は、GPU がレンダー パスの前に実行する操作です。clear は、GPU が最初に最後のフレームから以前のデータをすべてクリアすることを意味します。storeOp は、GPU がレンダー パスの後に実行する操作です。store は GPU を意味します。データをテクスチャに保存します。

loadOp は、最後のフレームからのデータを保存するload、または最後のフレームからデータをクリアするclearにすることができます。 storeOp は、データをテクスチャに保存する store、またはデータを破棄する Discard にできます。

ここで、 pass.end() を呼び出してレンダー パスを終了します。これで、コマンドが GPU のコマンド バッファーに保存されました。

コンパイルされたコマンドを取得するには、次のコードを使用します。

const device = await adapter.requestDevice();
console.log(device);
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

そして最後に、コマンドを GPU のレンダー キューに送信します。

const canvas = document.getElementById('app') as HTMLCanvasElement;
const context = canvas.getContext("webgpu")!;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
    device: device,
    format: canvasFormat,
});
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

これで、醜い黒いキャンバスが表示されるはずです。

3D に関する固定概念に基づくと、何もない空間は青色であると予想されます。これは、クリアカラーを設定することで実現できます。

const encoder = device.createCommandEncoder();
ログイン後にコピー

シェーダーを使用して三角形を描画する

次に、キャンバス上に三角形を描きます。これを行うにはシェーダーを使用します。シェーダー言語は wgsl (WebGPU Shading Language) になります。

ここで、次の座標で三角形を描画するとします。

const pass = encoder.beginRenderPass({
  colorAttachments: [{
     view: context.getCurrentTexture().createView(),
     loadOp: "clear",
     storeOp: "store",
  }]
});
ログイン後にコピー

前に述べたように、レンダー パイプラインを完成するには、頂点シェーダーとフラグメント シェーダーが必要です。

頂点シェーダー

次のコードを使用してシェーダー モジュールを作成します。

const commandBuffer = encoder.finish();
ログイン後にコピー
ここでの

label は単なる名前であり、デバッグ用です。 code は実際のシェーダー コードです。

頂点シェーダーは、任意のパラメータを受け取り、頂点の位置を返す関数です。ただし、予想に反して、頂点シェーダーは 3 次元ベクトルではなく 4 次元ベクトルを返します。 4 番目の次元は w 次元で、視点の分割に使用されます。それについては後で説明します。

これで、4 次元ベクトル (x、y、z、w) を単純に 3 次元ベクトル (x / w、y / w、z / w) とみなすことができます。

しかし、別の問題があります。データをシェーダーに渡す方法と、データをシェーダーから取り出す方法です。

シェーダーにデータを渡すには、頂点のデータを含むバッファーである vertexBuffer を使用します。次のコードでバッファを作成できます

const main = async () => {
    console.log('Hello, world!')
}

main()
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ここでは、頂点のサイズである 24 バイト、6 浮動小数点のサイズのバッファーを作成します。

usage はバッファーの使用法であり、頂点データの場合は VERTEX です。 GPUBufferUsage.COPY_DST は、このバッファがコピー先として有効であることを意味します。 CPU によってデータが書き込まれるすべてのバッファに対して、このフラグを設定する必要があります。

ここでのマップは、バッファを CPU にマップすることを意味します。つまり、CPU がバッファを読み書きできることを意味します。アンマップとは、バッファーのマップを解除することを意味します。これは、CPU がバッファーの読み取りと書き込みを行うことができなくなり、GPU でコンテンツを使用できるようになることを意味します。

これで、データをバッファに書き込むことができます。

const adapter = await navigator.gpu.requestAdapter();
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ここでは、バッファを CPU にマッピングし、データをバッファに書き込みます。次に、バッファーのマップを解除します。

vertexBuffer.getMappedRange() は、CPU にマップされているバッファーの範囲を返します。これを使用してデータをバッファに書き込むことができます。

ただし、これらは単なる生データであり、GPU はそれらを解釈する方法を知りません。バッファのレイアウトを定義する必要があります。

const device = await adapter.requestDevice();
console.log(device);
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ここで、arrayStride は、GPU が次の入力を探すときにバッファ内で前方にスキップする必要があるバイト数です。たとえば、arrayStride が 8 の場合、GPU は次の入力を取得するために 8 バイトをスキップします。

ここでは float32x2 を使用しているため、ストライドは 8 バイト、各 float に 4 バイト、各頂点に 2 float です。

これで頂点シェーダーを作成できます。

const canvas = document.getElementById('app') as HTMLCanvasElement;
const context = canvas.getContext("webgpu")!;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
    device: device,
    format: canvasFormat,
});
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ここで、@vertex は、これが頂点シェーダーであることを意味します。 @location(0) は属性の場所を意味し、以前に定義したように 0 です。シェーダー言語ではバッファーのレイアウトを扱うため、値を渡すときは常に、フィールドに @location が定義されている構造体、または @location を持つ値のみを渡す必要があることに注意してください。

vec2f は 2 次元 float ベクトル、vec4f は 4 次元 float ベクトルです。頂点シェーダーは vec4f 位置を返す必要があるため、 @builtin(position) でアノテーションを付ける必要があります。

フラグメントシェーダ

フラグメント シェーダも同様に、補間された頂点出力を受け取り、アタッチメント (この場合はカラー) を出力するものです。補間とは、頂点上の特定のピクセルのみが決定値を持っていますが、他のピクセルごとに、値が線形、平均、またはその他の手段で補間されることを意味します。フラグメントの色は 4 次元ベクトルで、フラグメントの色、それぞれ赤、緑、青、アルファです。

色の範囲は 0 ~ 255 ではなく、0 ~ 1 であることに注意してください。さらに、フラグメント シェーダーは三角形の色ではなく、すべての頂点の色を定義します。三角形の色は、補間によって頂点の色によって決定されます。

現時点ではフラグメントの色を制御する必要がないため、単純に定数の色を返すことができます。

const main = async () => {
    console.log('Hello, world!')
}

main()
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

レンダリングパイプライン

次に、頂点シェーダーとフラグメント シェーダーを置き換えることによって、カスタマイズされたレンダー パイプラインを定義します。

const adapter = await navigator.gpu.requestAdapter();
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

フラグメント シェーダーでは、キャンバスの形式であるターゲットの形式を指定する必要があることに注意してください。

ドローコール

レンダーパスが終了する前に、描画呼び出しを追加します。

const device = await adapter.requestDevice();
console.log(device);
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ここで、setVertexBuffer の最初のパラメータはパイプライン定義フィールド バッファ内のバッファのインデックスであり、2 番目のパラメータはバッファ自体です。

draw を呼び出す場合、パラメータは描画する頂点の数です。頂点が 3 つあるので、3 つ描画します。

これで、キャンバス上に黄色の三角形が表示されるはずです。

ライフゲームのセルを描く

ここでコードを少し調整します。ライフ ゲームを構築したいので、三角形の代わりに正方形を描く必要があります。

正方形は実際には 2 つの三角形なので、6 つの頂点を描く必要があります。ここでの変更は簡単なので詳細な説明は必要ありません。

const canvas = document.getElementById('app') as HTMLCanvasElement;
const context = canvas.getContext("webgpu")!;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
    device: device,
    format: canvasFormat,
});
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

これで、キャンバス上に黄色の四角形が表示されるはずです。

座標系

GPU の座標系については議論しませんでした。まあ、かなりシンプルです。 GPU の実際の座標系は右手座標系です。これは、X 軸が右を指し、Y 軸が上を指し、Z 軸が画面の外を指すことを意味します。

座標系の範囲は-1から1です。原点は画面の中心です。 z 軸は 0 から 1 で、0 は近い平面、1 は遠い平面です。ただし、z 軸は深さを表します。 3D レンダリングを行う場合、Z 軸を使用してオブジェクトの位置を決定するだけではなく、透視分割を使用する必要があります。これは NDC (正規化されたデバイス座標) と呼ばれます。

たとえば、画面の左上隅に正方形を描画したい場合、頂点は (-1, 1)、(-1, 0)、(0, 1)、(0, 0) になります。ただし、描画するには 2 つの三角形を使用する必要があります。

以上がWeb 上の三角形が何かを描くの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート