OpenGLを学びます。 パート4.2。 -ステンシルテスト

画像


フラグメントシェーダーがフラグメントを処理すると、いわゆるステンシルテストが実行されます。これは、深度テストと同様に、フラグメントを破棄できます。 その後、残りのフラグメントは深度テストに進み、さらに多くのフラグメントが破棄される場合があります。 画面テストは、 画面バッファーと呼ばれる別のバッファーの内容に基づいています 。 レンダリング中に更新して、興味深い効果を実現できます。






ステンシルテスト


ステンシルバッファには通常、各ステンシル値ごとに8ビットが含まれており、フラグメント/ピクセルごとに256の異なるステンシル値を提供します。 これらの値を好みに合わせて設定し、特定のフラグメントに特定のステンシル値があるときはいつでも、フラグメントを破棄または保存できます。


各ウィンドウライブラリは、ステンシルバッファを設定する必要があります。 GLFWはこれを自動的に行うため、心配する必要はありませんが、他のウィンドウライブラリはデフォルトの画面バッファーを作成しない可能性があるため、必ずライブラリのドキュメントをご覧ください。


スクリーンバッファの簡単な例を以下に示します。


img 最初に、画面バッファがゼロで埋められ、次に、長方形のフレームのように見えるバッファの領域が単位で埋められます。 画面値が1に等しいシーンのフラグメントのみが表示され、残りは破棄されます。


スクリーンバッファ操作により、フラグメントを表示するスクリーンバッファに異なる値を設定できます。 レンダリング中に画面バッファーの値を変更することにより、 書き込み操作を実行します 。 同じ(または次の)レンダリングの繰り返しで、バッファーから値を読み取ることができるため、読み取られた値に基づいて、特定のフラグメントを破棄または受け入れることができます。 画面バッファーを使用すると、とにかくだますことができますが、一般的なスキームは次のとおりです。



最後に、スクリーンバッファーを使用すると、シーン内の他のオブジェクトのフラグメントに基づいて特定のフラグメントを破棄できると言えます。


GL_STENCIL_TEST有効にすると、画面のテストを有効にできGL_STENCIL_TEST 。 これ以降、すべてのレンダリング呼び出しは、何らかの形で画面バッファーに影響します。


 glEnable(GL_STENCIL_TEST); 

色と深度のバッファーと同様に、各反復で画面バッファーをクリアする必要があることに注意してください。


 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 

深度テストのパラメータ化に使用されるステンシルバッファ用のglDepthMask関数に類似しています。 glStencilMask関数を使用すると、バッファに書き込まれたステンシル値とビット単位のAND演算に関与するビットマスクを設定できます。 デフォルトでは、ビットマスクは1であり、出力に影響しませんが、マスクを0x00設定すると、すべてのステンシル値は最終的にゼロとして書き込まれます。 glDepthMask(GL_FALSE)深度テストマスクglDepthMask(GL_FALSE)を設定することと同等のことは、次の呼び出しのペアになります。


 glStencilMask(0xFF); //         glStencilMask(0x00); //        ( ) 

ほとんどの場合、スクリーンマスクに0x00または0xFFを書き込むだけですが、カスタムビットマスクを設定できることを知っておくと便利です。


画面の機能


深度テストと同様に、画面テストに合格するタイミングと合格しないタイミング、およびスクリーンバッファーへの影響を制御する特定の機能があります。 画面テストの設定に使用できる関数はglStencilFuncglStencilOp 2つだけglStencilOp


glStencilFunc(GLenum func, GLint ref, GLuint mask)関数glStencilFunc(GLenum func, GLint ref, GLuint mask)は3つのパラメーターがあります。




したがって、最初に示した単純なステンシルの例の場合、関数は次のようになります。


 glStencilFunc(GL_EQUAL, 1, 0xFF) 

これは、フラグメントのステンシル値が参照値1に等しいときはいつでも、フラグメントがテストに合格して描画されることをOpenGLに伝え、そうでない場合は破棄されます。


ただし、 glStencilFunc関数は、OpenGLがステンシルバッファーの内容に対して行うべきことglStencilFunc説明し、バッファーの更新方法は説明しません。 ここでは、 glStencilOp関数が役立ちます。


glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)関数glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)は、各オプションのアクションを決定できる3つのパラメーターが含まれています。



次に、それぞれの場合について、次のいずれかを実行できます。


アクション説明
GL_KEEP現在保存されているステンシル値が保存されます。
GL_ZEROステンシル値はゼロにリセットされます。
GL_REPLACEステンシル値はglStencilFuncによって設定された参照値に置き換えられます。
GL_INCR画面の値は、最大値よりも小さい場合、1ずつ増加します。
GL_INCR_WRAPGL_INCRと同じように動作しますが、最大値を通過すると、バッファーの値がリセットされます。
GL_DECR画面の値は、最小値を超えると1つ減ります。
GL_DECR_WRAPGL_DECRと同じように動作しますが、0を通過すると、バッファーの値が最大に設定されます。
GL_INVERTステンシルバッファの現在の値をビット単位で反転します。

デフォルトでは、 glStencilOp関数の引数glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP)に設定されているため、テスト結果に関係なく、値はステンシルバッファーに保存されます。 標準動作では画面バッファは更新されません。したがって、画面バッファに書き込みたい場合は、オプションに対して標準とは異なる少なくとも1つのアクションを設定する必要があります。


したがって、 glStencilFuncglStencilOpを使用して、スクリーンバッファーを更新するタイミングと方法を正確に決定できます。また、スクリーンテストに合格するタイミングと合格しないタイミング、つまりフラグメントを破棄するタイミングも決定できます。


オブジェクトストローク


前のセクションに基づいて画面テストがどのように機能するかを完全に理解しているとは考えにくいため、画面テストを使用して実装できる有用な手法を示します。 これはオブジェクトストロークです


丸いキューブ

オブジェクトのストロークの意味を説明する必要はありません。 各オブジェクト(または1つだけ)に対して、小さな色付きのフレームを作成します。 この効果は、たとえば、戦略ゲームでユニットを選択し、どのユニットが選択されたかをユーザーに示す必要がある場合に特に役立ちます。 オブジェクトのストロークを形成するアルゴリズムは次のとおりです。


  1. 画面関数をGL_ALWAYS設定し、オブジェクト(丸で囲まれます)を描画する前に、オブジェクトの断片が描画される単位で画面バッファーを更新します。
  2. オブジェクトを描画します。
  3. 深度テストと画面バッファへの書き込みを無効にします。
  4. 各オブジェクトを少し拡大します。
  5. 1色(ストロークカラー)のみを表示する別のフラグメントシェーダーを使用します。
  6. フラグメントのステンシル値がユニティに等しくない場合にのみ、オブジェクトを再度描画します。
  7. 画面バッファの記録と深度テストを再度有効にします。

このプロセスは、オブジェクトの各フラグメントのバッファーのコンテンツを1に設定し、境界線を描画する場合、基本的にオブジェクトのスケーラブルバージョンを描画し、テストが許可する場合、スケーラブルバージョンが描画されます(オブジェクトの境界線の周り)。 ステンシルテストを使用して、元のオブジェクトのフラグメントに重ねられているスケーリングされたオブジェクトのフラグメントを破棄します。


最初に、ストロークの色を表示する非常に単純なフラグメントシェーダーを作成します。 ハードコードされた色を設定し、 shaderSingleColorシェーダを呼び出すだけです。


 void main() { FragColor = vec4(0.04, 0.28, 0.26, 1.0); } 

2つのコンテナのみにストロークを含める予定で、床にはストロークを含めません。 したがって、最初にフロアを出力し、次に2つのコンテナ(バッファに書き込まれるステンシルを含む)、次にコンテナの拡大バージョン(元のコンテナの既に描画されたフラグメントに重ねられたフラグメントを拒否する)を出力する必要があります。


まず、画面のテストを有効にし、各テストが成功または失敗した場合に実行する手順を設定する必要があります。


 glEnable(GL_DEPTH_TEST); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 

テストのいずれかが失敗した場合、何もしませんが、単に現在の値をステンシルバッファに残します。 画面テストと深度テストの両方が成功した場合、現在の画面値をglStencilFuncを介して設定された参照値に置き換えます。これは後で1に設定します。


バッファはゼロで埋めることによってクリアされ、コンテナの場合、描画されたフラグメントごとにスクリーンバッファを1に更新します。


 glStencilFunc(GL_ALWAYS, 1, 0xFF); //      glStencilMask(0xFF); //      normalShader.use(); DrawTwoContainers(); 

glStencilFunc関数glStencilFuncを使用して、コンテナの各フラグメントがスクリーンバッファをスクリーン値1で更新することを保証します。フラグメントは常にスクリーンテストに合格するため、スクリーンバッファは描画する場所に関係なく参照値で更新されます。


ステンシルバッファが単一に更新され、コンテナを描画したので、コンテナのより大きなバージョンを描画する必要がありますが、ステンシルバッファへの書き込みを無効にします。


 glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilMask(0x00); //      glDisable(GL_DEPTH_TEST); shaderSingleColor.use(); DrawTwoScaledUpContainers(); 

GL_NOTEQUAL引数を使用しglStencilFunc 。これにより、1に等しくないオブジェクトの部分のみが描画されるため、以前に描画されたオブジェクトの外側にあるオブジェクトの部分のみが描画されます。 また、境界などの拡大されたコンテナの要素が床で上書きされないように、深度テストも無効にしていることに注意してください。


また、深度テストを再度オンにしてください。


シーンの一般的なオブジェクトストロークパターンは次のようになります。


 glEnable(GL_DEPTH_TEST); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glStencilMask(0x00); // ,           normalShader.use(); DrawFloor() glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilMask(0xFF); DrawTwoContainers(); glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilMask(0x00); glDisable(GL_DEPTH_TEST); shaderSingleColor.use(); DrawTwoScaledUpContainers(); glStencilMask(0xFF); glEnable(GL_DEPTH_TEST); 

画面テストの背後にある一般的な考え方を理解している場合、このコードは複雑すぎて理解できないはずです。 それ以外の場合は、前のセクションを注意深く読んで、各関数の使用例を完全に理解してみてください。その使用例を見ました。


深度テストのレッスンからストロークアルゴリズムをシーンに適用した結果は次のようになります。


img


オブジェクトのストロークアルゴリズムの完全なコードについては、 こちらのソースコードをご覧ください。


2つのコンテナの境界が重なっていることに気付くかもしれません。 通常、これはまさにあなたが必要とするものです(複数のユニットを選択するときの戦略ゲームを思い出してください)。 各オブジェクトの周囲に完全な境界線が必要な場合は、各オブジェクトの画面バッファーをクリアし、深度テストの設定を少し試してください。


ご覧になったオブジェクトストロークアルゴリズムは、一部のゲームで選択されたオブジェクトを視覚化するためによく使用され(戦略的ゲームを思い出してください)、そのようなアルゴリズムはモデルクラスで簡単に実装できます。 次に、モデルクラスでブールフラグを設定するだけで、ボーダー付きまたはボーダーなしで描画できます。 少し創造性を見せれば、ガウスぼかしなどの後処理フィルターを使用して境界をより有機的にすることができます。


スクリーンテストを使用すると、バックミラーの内部にテクスチャを描画して、それらがミラーのフレームに収まるようにするなど、オブジェクトを単に円形にするだけではありません。 または、 シャドウボリュームテクニックを使用して、リアルタイムでシャドウをレンダリングします。 スクリーンバッファーは、すでに豊富なOpenGLツールキットの別の優れたツールを提供します。



PS
UberSchlagユーザーへの翻訳に関するヘルプとコメントをありがとうございます!
翻訳を手伝いたい場合は、Telegramの会話に参加してください。
元の出版物へのリンクを追加できませんでした(書き込み:はリンクではありません )。

Source: https://habr.com/ru/post/J344238/


All Articles