シェーダーとは何ですか?

はじめに

そう、シェーダーを試してみることにしました。信じられないほど高速で実行される興味深いエフェクトを作成するために使用できると聞いたことがあるでしょう。また、恐ろしいと聞いたことがあるかもしれません。両方とも真実です。

シェーダーを使用して、さまざまな効果を作成できます(実際、最新のレンダリングエンジンで描画される処理はすべてシェーダーで行われます)。

シェーダーの作成は、それらに不慣れな人々にとっても非常に困難です。 Godotは、多くの便利な組み込みか機能を公開し、いくつかの低レベルの初期化作業を処理することで、シェーダーの作成を少し簡単にすることを試みます。ただし、GLSL(Godotが使用するOpenGLシェーディング言語)は、特にGDScriptに慣れているユーザー向けです。

しかし、それは何ですか?

シェーダーは、グラフィックスプロセッシングユニット(GPU)で実行される特別な種類のプログラムです。ほとんどのコンピューターには、CPUに統合されたGPUまたはディスクリート(つまり、典型的なグラフィックカードなどの別個のハードウェアコンポーネント)のいずれかのGPUがあります。 GPUは、数千の命令を並行して実行するために最適化されているため、レンダリングに特に役立ちます。

シェーダーの出力は通常、ビューポートに描画されたオブジェクトの色付きピクセルです。ただし、一部のシェーダーでは特殊な出力が可能です(これは特にVulkanなどのAPIに当てはまります)。シェーダーは、シェーダーパイプライン内で動作します。標準プロセスは、頂点→フラグメントシェーダーパイプラインです。頂点シェーダーは、各頂点(3Dモデルのポイント、またはスプライトのコーナー)がどこに行くかを決定するために使用され、フラグメントシェーダーは個々のピクセルが受け取る色を決定します。

テクスチャのすべてのピクセルを特定の色に更新する場合、CPUで次のように記述します:

for x in range(width):
  for y in range(height):
    set_color(x, y, some_color)

シェーダーでは、ループ内へのアクセスのみが許可されるため、作成する内容は次のようになります:

// function called for each pixel
void fragment() {
  COLOR = some_color;
}

この関数の呼び出し方法を制御することはできません。したがって、CPUでプログラムを設計する方法とは異なる方法でシェーダーを設計する必要があります。

シェーダーパイプラインの結果は、シェーダーの前回の実行の結果にアクセスできず、描画中のピクセルから他のピクセルにアクセスできず、描画中の現在のピクセルの外側に書き込むことができないことです。これにより、GPUは異なるピクセルのシェーダーを互いに依存しないため、並列に実行できます。この柔軟性の欠如はGPUで動作するように設計されているからで、これによりシェーダーを非常に高速にすることができます。

それらでできること

  • 頂点を非常に高速に配置する
  • 色を非常に高速に計算する
  • ライティングを非常に高速に計算する
  • たくさんの演算

それらでできないこと

  • 外側のメッシュを描画する
  • 現在のピクセル(または頂点)から他のピクセルにアクセスする
  • 以前の繰り返しを保存する
  • オンザフライで更新する(可能ですが、コンパイルする必要があります)

シェーダーの構造

Godotでは、シェーダーは3つの主な関数で構成されています: vertex() 関数、fragment() 関数、および light() 関数です。

vertex() 関数は、メッシュ内のすべての頂点に対して実行され、それらの位置とその他の頂点ごとの変数を設定します。

fragment() 関数は、メッシュで覆われているすべてのピクセルに対して実行されます。vertex() 関数の変数を使用して実行します。vertex() 関数の変数は頂点間で補間され、fragment() 関数の値を提供します。

light() 関数は、すべてのピクセルとすべてのライトに対して実行されます。fragment() 関数とそれ自体の以前の実行から変数を取得します。

Godotでのシェーダーの具体的な動作方法の詳細については、Shaders ドキュメントを参照してください。

技術概要

GPUは、いくつかの理由でグラフィックをCPUよりもはるかに高速にレンダリングできますが、最も顕著なのは、計算を大規模に並行して実行できるためです。 CPUには通常4または8コアがあり、GPUには通常数千のコアがあります。 つまり、GPUは一度に数百のタスクを実行できます。GPU の設計者は、多くの計算を非常に迅速に実行できる方法でこれを利用してきましたが、多くのコアまたはすべてのコアが同じ計算を一度に実行しているが、データは異なる場合に限ります。

そこでシェーダーが登場します。GPUはシェーダーを多数回同時に呼び出してから、データの異なるかけら(頂点、またはピクセル)を操作します。 これらのデータの束は、しばしばウエーブフロントと呼ばれます。 シェーダーは、ウエーブフロントのすべてのスレッドで同じように実行されます。 たとえば、特定のGPUがウエーブフロントあたり100スレッドを処理できる場合、ウエーブフロントはピクセルの10x10ブロックで一緒に実行されます。 そして、それらが完了するまで、そのウエーブフロントのすべてのピクセルに対して実行され続けます。 したがって、1ピクセルが他のピクセルよりも遅い場合(過度の分岐のため)、ブロック全体が遅くなり、レンダリング時間が大幅に遅くなります。 これはCPUでの操作とは異なり、その1ピクセルが高速化できる場合、レンダリング時間全体が短縮されます。 GPUでは、レンダリングを高速化するために、ウエーブフロント全体を高速化する必要があります。