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

はじめに

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

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

シェーダーの作成は、それらに不慣れな人々にとっても非常に困難です。 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() 関数とそれ自体の以前の実行から変数を取得します。

For more information about how shaders operate specifically in Godot, see the Shaders doc.

警告

The light() function won't be run if the vertex_lighting render mode is enabled, or if Rendering > Quality > Shading > Force Vertex Shading is enabled in the Project Settings. (It's enabled by default on mobile platforms.)

技術概要

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

That is where shaders come in. The GPU will call the shader a bunch of times simultaneously, and then operate on different bits of data (vertices, or pixels). These bunches of data are often called wavefronts. A shader will run the same for every thread in the wavefront. For example, if a given GPU can handle 100 threads per wavefront, a wavefront will run on a 10×10 block of pixels together. It will continue to run for all pixels in that wavefront until they are complete. Accordingly, if you have one pixel slower than the rest (due to excessive branching), the entire block will be slowed down, resulting in massively slower render times.

This is different from CPU-based operations. On a CPU, if you can speed up even one pixel, the entire rendering time will decrease. On a GPU, you have to speed up the entire wavefront to speed up rendering.