OpenGL パイプラインでは、テッセレーション ステージの後にジオメトリ ステージが続きます。この段階で、プログラマはジオメトリ シェーダを含めるかどうかを選択できます。このステージは実際には、バージョン 3.2 (2009) で OpenGL コアの一部となったテッセレーション ステージよりも前に存在していました。
テッセレーションと同様に、ジオメトリ シェーダーを使用すると、プログラマは頂点シェーダーでは不可能な方法で頂点のグループを操作できます。場合によっては、テッセレーション シェーダまたはジオメトリ シェーダを使用して同じタスクを実行できます。これは、それらの機能がいくつかの点で重複しているためです。
13.1 OpenGL でのプリミティブごとの処理
ジオメトリ シェーダ ステージは、プリミティブ処理に使用されるパイプラインのセグメント内で、テッセレーションとラスタライゼーションの間に配置されます (図 2.2 を参照)。頂点シェーダーでは一度に 1 つの頂点を操作でき、フラグメント シェーダーでは一度に 1 つのフラグメント (実際には 1 ピクセル) を操作できますが、ジオメトリ シェーダーでは一度に 1 つのプリミティブを操作できます。
プリミティブは OpenGL の描画オブジェクトの基本コンポーネントであることを思い出してください。プリミティブの種類は数種類しかありません。ここでは主に三角形のプリミティブを操作するジオメトリ シェーダに焦点を当てます。したがって、ジオメトリ シェーダが一度に 1 つのプリミティブを操作できると言うときは、通常、シェーダが一度に三角形の 3 つの頂点にアクセスできることを意味します。ジオメトリ シェーダを使用すると、プリミティブ内のすべての頂点に一度にアクセスできるようになり、次のことが可能になります。
- 同じプリミティブの出力は変更されません。
- 頂点位置が変更された同じタイプのプリミティブを出力します。
- さまざまなタイプのプリミティブを出力します。
- 他のプリミティブをさらに出力します。
- プリミティブを削除します (まったくエクスポートしません)。
テッセレーション評価シェーダーと同様に、受信頂点属性はジオメトリ シェーダー内の配列としてアクセスできます。ただし、ジオメトリ シェーダでは、受信アトリビュート配列にはプリミティブのサイズまでしかインデックスが付けられません。たとえば、プリミティブが三角形の場合、使用可能なインデックスは 0、1、2 です。以下に示すように、 事前定義された配列gl_in
を使用して頂点データ自体にアクセスします。
gl_in[2].gl_Position // 第三个顶点的位置
テッセレーション評価シェーダーと同様に、ジオメトリ シェーダーによって出力される頂点属性はすべてスカラーです。つまり、出力は、プリミティブを形成する個々の頂点 (頂点の位置およびその他の属性変数 (存在する場合)) のストリームです。
には、プリミティブの入出力タイプと出力サイズを設定するレイアウト修飾子があります。特殊な GLSL コマンドEmitVertex()
は、頂点を出力することを指定します。特別な GLSL コマンドEndPrimitive()
は、特定のプリミティブが構築されることを示します。
現在のプリミティブの ID を保存する組み込み変数gl_PrimitiveIDIn
があります。 ID は 0 から始まり、プリミティブの総数がマイナス 1 になるまでカウントされます。
以下の 4 つの一般的な操作タイプについて説明します。
- グラフィック要素を変更します。
- 要素を削除します。
- グラフィック要素を追加します。
- 要素の種類を変更します。
13.2 グラフィック要素の変更
ジオメトリ シェーダは、オブジェクトの形状の変化がプリミティブ (通常は三角形) への 1 つの変更によって影響を受ける可能性がある場合に便利です。
たとえば、先ほど図 7.12 で示したトーラスについて考えてみましょう。トーラスがその内部の空間を表し (たとえば、タイヤを表す場合)、それを「膨張」させたいとします。 C++/OpenGL コードにスケーリング係数を適用するだけでは、基本的な形状は変わらないため、これを実現することはできません。 「膨張した」外観を与えるには、トーラスが中央の空いた空間に伸びるときに、内側の穴を小さくする必要もあります。
この問題を解決する 1 つの方法は、各頂点に表面法線ベクトルを追加することです。これは頂点シェーダーでも実行できますが、ここではジオメトリ シェーダーで練習します。プログラム 13.1 は、GLSL ジオメトリ シェーダのコードを示しています。他のモジュールはプログラム 7.3 と同じですが、いくつかの小さな変更があります。フラグメント シェーダの入力名はジオメトリ シェーダの出力を反映する必要があります (たとえば、variingNormal はvariingNormalG になります)。また、C++/OpenGL アプリケーションはジオメトリ シェーダをコンパイルして変換する必要があります。リンク前にシェーダプログラムにアタッチされます。新しいシェーダは、以下に示すようにジオメトリ シェーダとして指定されます。
GLuint gShader = glCreateShader(GL_GEOMETRY_SHADER);
プログラム 13.1 ジオメトリ シェーダー: 頂点の変更
プログラム 13.1 では、頂点シェーダーの出力変数に対応する入力変数が配列として宣言されていることに注意してください。これにより、インデックス 0、1、2 を使用して三角形プリミティブの各頂点とその属性にアクセスするメカニズムがプログラマに提供されます。これらの頂点を表面法線ベクトルに沿って外側に移動したいと考えています。頂点シェーダーでは、頂点と法線ベクトルの両方がビュー空間に変換されています。法線ベクトルの一部を各入力頂点ビット (gl_in[i].gl_Position
) に追加し、その結果に射影行列を適用して、各出力を生成します gl_Position
。
GLSL 呼び出しEmitVertex()
は、出力gl_Position
とそれに関連する頂点属性の計算を終了し、いつ実行する準備ができているかを指定するために使用されることに注意してください。頂点を出力します。 EndPrimitive()
この呼び出しは、プリミティブ (この場合は三角形) を構成する頂点のセットの定義が完了したことを指定します。結果を図 13.1 に示します。
ジオメトリ シェーダーには 2 つのレイアウト修飾子が含まれています。 1 つ目は入力プリミティブ型を指定し、C++ 側の glDrawArrays()
または glDrawElements()
呼び出しのプリミティブ型と互換性がある必要があります。オプションを表 13.1 に示します。
さまざまな OpenGL プリミティブ タイプ (「ストリップ」タイプや「ファン」タイプを含む) については、第 4 章で説明します。 「隣接」タイプは、OpenGL でジオメトリ シェーダで使用するために使用され、プリミティブに隣接する頂点にアクセスできます。本書ではそれらを使用しませんが、完全を期すためにリストされています。
出力プリミティブ タイプは、points、line_strip、または Triangle_strip である必要があります。出力レイアウト修飾子は、呼び出しごとにシェーダーが出力する頂点の最大数も指定することに注意してください。
トーラスに対するこの特定の変更は、頂点シェーダーで簡単に行うことができます。ただし、各頂点を独自の表面法線ベクトルに沿って外側に移動する代わりに、各三角形を表面法線ベクトルに沿って外側に移動して、トーラスを構成する三角形を外側に効果的に「展開」したいとします。三角形の法線ベクトルが計算されるため、頂点シェーダーはこれを行うことができません。
3 つの三角形の頂点の頂点法線ベクトルは平均する必要があり、頂点シェーダーは一度に三角形内の 1 つの頂点の頂点属性にのみアクセスできます。ただし、ジオメトリ シェーダーは各三角形の 3 つの頂点すべてにアクセスできるため、これをジオメトリ シェーダーで行うことができます。法線ベクトルを平均して三角形の表面法線ベクトルを計算し、この平均法線ベクトルを三角形プリミティブの各頂点に追加します。図 13.2、図 13.3、図 13.4 はそれぞれ、平均サーフェス法線ベクトル、修正されたジオメトリ シェーダ main() コード、および出力結果を示しています。
「分解された」トーラスの外観は、トーラスの内部も確実に表示されるようにすることで改善できます (通常、これらの三角形は「背面」であるため、OpenGL によってカリングされます)。解決策の 1 つは、トーラスを 2 回レンダリングすることです。1 回目は通常の方法で、もう 1 回目はラッピング順序を逆転させます (ラッピング順序を反転すると、どちらの面が前を向くのか、どちらが後ろを向くのかが効果的に切り替わります)。また、(uniform 変数を介して) シェーダにフラグを送信し、背面の三角形の拡散照明と鏡面照明を無効にして、三角形を目立たなくします。コードの変更は次のとおりです。
display() 関数の変更:
フラグメント シェーダの変更:
結果として生じる「分解された」トーラス (裏面も含む) を図 13.5 に示します。
13.3 プリミティブの削除
ジオメトリ シェーダの一般的な使用法は、いくつかのプリミティブを合理的に削除することによって、単純なオブジェクトから豊かな装飾オブジェクトを構築することです。たとえば、トーラスからいくつかの三角形を削除すると、ゼロからモデル化することがはるかに困難になる複雑な格子構造に変わる可能性があります。これを行うジオメトリ シェーダをプログラム 13.2 に示し、出力を図 13.6 に示します。
プログラム 13.2 ジオメトリ シェーダー: プリミティブの削除
コードに対するその他の変更は必要ありません。ここでは mod 関数が使用されていることに注意してください。無視される 3 つのプリミティブごとの最初の頂点を除いて、すべての頂点が渡されます。ここで、図 13.7 に示すように、後ろ向きの三角形をレンダリングすると、リアリズムが向上します。
13.4 グラフィック要素の追加
おそらく、ジオメトリ シェーダの最も興味深く有用な使用法は、レンダリングされるモデルに追加の頂点やプリミティブを追加することです。これにより、オブジェクトの詳細を増やして高さマップを改善したり、オブジェクトの形状を完全に変更したりすることが可能になります。
トーラス内の各三角形を小さな三角形のピラミッドに変更する次の例を考えてみましょう。
私たちの戦略は、図 13.8 に示す、前の「爆発する」トーラスの例と似ています。渡された三角形プリミティブの頂点は、ピラミッドの底面を定義するために使用されます。ピラミッドの壁は、これらの頂点と、元の頂点の法線ベクトルを平均することによって計算された新しい点 (「ピーク点」と呼ばれる) で構成されます。次に、ピラミッドの 3 つの「側面」それぞれの新しい法線ベクトルが、頂点から底辺までの 2 つのベクトルの外積によって計算されます。
プログラム 13.3 のジオメトリ シェーダーは、トーラス内の各三角形プリミティブに対してこれを実行します。入力三角形ごとに、3 つの三角形プリミティブ、合計 9 つの頂点が出力されます。新しい三角形はそれぞれ makeNewTriangle()
関数で構築され、3 回呼び出されます。指定された三角形の法線ベクトルを計算し、setOutputValues()
関数を呼び出して、放出された各頂点に適切な出力頂点属性を割り当てます。 3 つの頂点をすべて放出した後、EndPrimitive()
を呼び出します。ライティングが正確に実行されることを保証するために、新しく作成された頂点ごとにライティング方向ベクトルの新しい値が計算されます。
プログラム 13.3 ジオメトリ シェーダー: プリミティブの追加
結果の出力を図 13.9 に示します。スパイクの長さ (sLen) 変数を増やすと、追加された表面「ピラミッド」の高さが高くなります。ただし、影がないとリアルに見えない可能性があります。プログラム 13.3 へのシャドウ マップの追加は演習として残されています。
この技術を注意深く適用すると、スパイク、とげ、その他の表面の微細な突起、または逆のくぼみ、ディンプルなどをシミュレートできます (参考文献 [DV14、TR13、KS16])。
13.5 要素タイプの変更
OpenGL では、ジオメトリ シェーダのプリミティブ タイプを変更できます。この機能の一般的な使用法は、入力三角形を 1 つ以上の出力ライン セグメントに変換して、ファーまたはヘアをシミュレートすることです。説得力のあるヘアの生成は、現実世界では依然としてより困難なプロジェクトの 1 つですが、ジオメトリ シェーダは、多くの状況でリアルタイム レンダリングを実現するのに役立ちます。
プログラム 13.4 は、入力された 3 頂点の三角形を外側の 2 頂点の線分に変換するジオメトリ シェーダーを示しています。まず、三角形の頂点位置を平均して、三角形の重心を生成することにより、毛束の開始点を計算します。次に、プログラム 13.3 と同じ「ピーク ポイント」をヘアの終点として使用します。出力プリミティブは 2 つの頂点を持つ線分として指定され、最初の頂点が始点で 2 番目の頂点が終点になります。寸法 72 スライスのトーラスをインスタンス化した結果を図 13.10 に示します。
もちろん、これは完全にリアルなヘアを作成するための出発点にすぎません。ヘアを曲げたり動かしたりするには、ラインにさらに多くの頂点を生成し、曲線に沿った頂点の位置を計算したり、ランダム性を組み込んだりするなど、いくつかの修正が必要になります。線分には明確な表面法線がないため、ライティングが複雑になる可能性があります。この例では、法線ベクトルが元の三角形の表面法線と同じであることを指定するだけです。
プログラム 13.4 ジオメトリ シェーダー: プリミティブ タイプの変更
追加情報
ジオメトリ シェーダの魅力の 1 つは、比較的使いやすいことです。ジオメトリ シェーダのアプリケーションの多くはテッセレーションを使用して実装できますが、ジオメトリ シェーダの仕組みにより、一般に実装とデバッグが容易になります。もちろん、ジオメトリとテッセレーションの相対的な適合性は、特定のアプリケーションによって異なります。
説得力のあるリアルなヘアまたはファーを生成することは困難であり、アプリケーション シナリオに応じてさまざまなテクニックが必要です。場合によっては、単純なテクスチャで十分な場合もありますが、この章で説明する基本的なテクニックなど、テッセレーション シェーダやジオメトリ シェーダを使用することもできます。よりリアルな効果が必要な場合、動き(アニメーション)と照明は難しくなります。ヘアとファーの生成に特化した 2 つのツールは、HairWorks と TressFX です。 HairWorks は NVIDIA GameWorks スイート [GW18] の一部であり、TressFX は AMD [TR18] によって開発されています。前者は OpenGL と DirectX の両方で動作しますが、後者は DirectX でのみ動作します。 TressFX の使用例は [GP14] にあります。