OpenGLを学びます。 レッスン4.3-色の混合

OGL3

色混合



OpenGL(およびその他のグラフィカルAPI、 約Per。 )のブレンドは、通常、オブジェクトの透明度の実装に関連付けられている手法です。 オブジェクトの半透明性は、1つの単色で塗りつぶされていないことを意味しますが、さまざまな比率の素材の陰影を背後のオブジェクトの色と組み合わせます。 例として、窓に色付きのガラスを入れることができます。ガラスには独自の色合いがありますが、最終的にはガラスの色合いとガラスの後ろに見えるすべてのものの混合物を観察します。 実際には、個々のオブジェクトの色の混合である結果の色を観察するため、この動作から混合という用語が生じます。 これにより、半透明のオブジェクトが透けて見えます。



内容
パート1.はじめに



  1. Opengl
  2. ウィンドウ作成
  3. こんにちはウィンドウ
  4. こんにちはトライアングル
  5. シェーダー
  6. テクスチャー
  7. 変換
  8. 座標系
  9. カメラ


パート2.基本的な照明



  1. 照明の基本
  2. 素材
  3. テクスチャマップ
  4. 光源
  5. 複数の光源


パート3. 3Dモデルをダウンロードする



  1. Assimpライブラリ
  2. メッシュポリゴンクラス
  3. 3Dモデルクラス


パート4.高度なOpenGL機能



  1. 深度テスト
  2. ステンシルテスト
  3. 色混合
  4. 顔のクリッピング
  5. フレームバッファ
  6. キュービックカード
  7. 高度なデータ処理
  8. 高度なGLSL
  9. 幾何学シェーダー
  10. インスタンス化
  11. スムージング


パート5.高度な照明



  1. 高度な照明。 Blinn-Fongモデル。
  2. ガンマ補正
  3. シャドウカード
  4. 全方向シャドウマップ
  5. 法線マッピング
  6. 視差マッピング
  7. HDR
  8. ブルーム
  9. 遅延レンダリング
  10. SSAO


パート6. PBR



  1. 理論
  2. 分析光源
  3. IBL 拡散照射。
  4. IBL ミラー露出。










半透明のオブジェクトは、完全に透明(すべての色が透過)または部分的に透明(光を透過しますが、独自のシェードも追加)にすることができます。 コンピュータグラフィックスでは、色ベクトルのいわゆるアルファ成分の不透明度を示すのが習慣です。 アルファコンポーネントは色ベクトルの4番目の要素であり、以前のレッスンで2回以上気づいたはずです。 ただし、この瞬間まで、この値は常に1.0に保たれました。これは完全な不透明度に相当します。 アルファコンポーネントを0.0に設定すると、完全な透明度が得られます。 値0.5は、オブジェクトの最終的な色がマテリアルによって50%設定され、背後のオブジェクトによって50%が設定されることを意味します。



これまで使用したすべてのテクスチャには、赤、青、緑の3つの色成分が含まれていました。 一部のテクスチャ形式では、各テクセルの4番目のアルファコンポーネントを保存することもできます。 この値は、テクスチャのどの部分が半透明で、どのくらいかを示します。 たとえば、このウィンドウペインテクスチャのアルファコンポーネントは、ガラス領域では0.25、フレームでは0.0に設定されています。 他の状況では、ガラス部分は完全に赤になりますが、75%の透明度により、色は現在のWebページの背景によってほとんど決定されます。









すぐにこのテクスチャを新しいシーンに追加しますが、まず最初に、完全な透明度または完全な不透明度のいずれかが必要な場合に透明度を達成するためのより簡単な手法について説明します。



フラグメントを破棄する



場合によっては、部分的な透明度は必要ありません。テクスチャのカラー値に基づいて、何かを表示するか、何も表示しない必要があります。 草の束を想像してください。束の最も単純な実装では、シーンにある2Dクワッドに草のテクスチャが必要です。 ただし、クワッドフォームは草の梁をシミュレートするタスクにはあまり役立ちません。重ね合わせたテクスチャの一部を非表示にして、他の一部を残しても害にはなりません。



以下に示すテクスチャは、説明したケースを正確に表しています。そのセクションは、完全に不透明(アルファ成分= 1.0)または完全に透明(アルファ成分= 0.0)で、平均値はありません。 草の葉の画像がない場所では、テクスチャの色ではなく、サイトの背景が見えることに気付くかもしれません。









したがって、シーンに植生を配置するときは、植物の部分に対応するテクスチャの部分のみを表示し、ポリゴンを埋める残りのテクスチャを破棄します。 つまり、テクスチャの透明部分を含むフラグメントをカラーバッファに保存せずに破棄します。 ただし、フラグメントで手を汚す前に、アルファチャネルを使用してテクスチャをロードする方法を学習する必要があります。



これを行うには、使い慣れたコードをあまり変更する必要はありません。 stb_image.hのローダー関数は、画像のアルファチャネルがあれば、自動的にロードします。 しかし、同時に、アルファチャネルを使用するテクスチャを作成するときにOpenGLに明示的に示す必要があります。



glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
      
      





また、フラグメントシェーダーでは、RGB値のみが残されないように、4つのコンポーネントを持つベクトルを選択するようにしてください。



 void main() { // FragColor = vec4(vec3(texture(texture1, TexCoords)), 1.0); FragColor = texture(texture1, TexCoords); }
      
      





透明度のあるテクスチャのロードがわかったので、 深度テストチュートリアルで使用したシーンの周りに草の房をいくつか投げます。



草の房の位置をglm :: vec3の形式で格納する小さなベクトルを作成しましょう。



 vector<glm::vec3> vegetation; vegetation.push_back(glm::vec3(-1.5f, 0.0f, -0.48f)); vegetation.push_back(glm::vec3( 1.5f, 0.0f, 0.51f)); vegetation.push_back(glm::vec3( 0.0f, 0.0f, 0.7f)); vegetation.push_back(glm::vec3(-0.3f, 0.0f, -2.3f)); vegetation.push_back(glm::vec3( 0.5f, 0.0f, -0.6f));
      
      





各草オブジェクトは、草のテクスチャが割り当てられた単一の四角形としてレンダリングされます。 3Dで草をシミュレートする最もエキサイティングな方法ではありませんが、ポリゴンモデルを使用するよりもはるかに効果的です。 同じ位置に同じテクスチャを持つ回転したクワッドの別のペアを追加するなど、小さなトリックの助けを借りて、良い結果を達成することができます。



草のテクスチャをクワッドに割り当てるため、新しいVAO(頂点配列オブジェクト)が必要で、VBO(頂点バッファオブジェクト)に入力し、対応するポインタを頂点属性に設定します。 さらに、床と立方体の表面をレンダリングした後、草を表示します。



 glBindVertexArray(vegetationVAO); glBindTexture(GL_TEXTURE_2D, grassTexture); for(unsigned int i = 0; i < vegetation.size(); i++) { model = glm::mat4(1.0f); model = glm::translate(model, vegetation[i]); shader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 6); }
      
      





プログラムを実行すると、次の結果が生成されます。









これは、OpenGL自体がアルファチャネルの値をどう処理するか、またフラグメントドロップをいつ使用するかを知らないために発生しました。 これらはすべて手動で指定する必要があります。 幸いなことに、シェーダーの助けを借りて、すべてが非常に簡単に行われます。 GLSLには組み込みの破棄ディレクティブがあり、その呼び出しは、カラーバッファーに落ちることなく、現在のフラグメントのさらなる処理の停止につながります。 ここから解決策が生まれます。テクスチャ要素のアルファ成分の値を確認し、特定のしきい値よりも小さい場合は破棄します。



 #version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D texture1; void main() { vec4 texColor = texture(texture1, TexCoords); if(texColor.a < 0.1) discard; FragColor = texColor; }
      
      





このコードでは、テクスチャサンプルのアルファ成分が0.1未満の場合、フラグメントを破棄します。 このようなシェーダーは、非常に不透明であることが判明したフラグメントのみの出力を提供します。









境界のテクスチャを取得するとき、OpenGLは境界の値を、テクスチャを繰り返して取得した次の値の値で補間します(テクスチャの繰り返しオプションをGL_REPEATに設定しているため )。 通常のテクスチャアプリケーションの場合、これは正常ですが、透明度のあるテクスチャの場合は良くありません。上部境界の完全に透明なテクセル値は、完全に不透明な下部境界テクセルと混合されます。 その結果、テクスチャのある四角形の周りに半透明の色付きフレームが表示される場合があります。 このアーティファクトを回避するには、透明度のあるテクスチャを使用するときに繰り返しパラメーターをGL_CLAMP_TO_EDGEに設定する必要があります。

 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
      
      





コード例はこちらです。



ミキシング



フラグメントを破棄することは便利で簡単な方法であるという事実にもかかわらず、半透明の色の部分的な混合を適用することを可能にしません。 異なる不透明度のオブジェクトで画像をレンダリングするには、ブレンドモードを有効にする必要があります。 これは、ほとんどのOpenGLモードと同様に行われます。



 glEnable(GL_BLEND);
      
      





さて、ミキシングをオンにすると、これがどのように機能するかを把握する価値があります。

OpenGLミキシングは、次の式を使用して実行されます

$$ display $$ \ begin {equation} \ bar {C} _ {result} = \ bar {\ color {green} C} _ {source} * \ color {green} F_ {source} + \ bar {\ color {red} C} _ {destination} * \ color {red} F_ {destination} \ end {equation} $$ display $$







どこで  bar colorgreenCsource ソースの色ベクトルです。 これは、テクスチャから取得した色の値です。

 bar colorredCdestination レシーバーの色ベクトルです。 これは、現在カラーバッファに格納されているカラー値です。

 colorgreenFsource -ソース乗数。 アルファ成分がソースの色にどの程度影響するかを指定します。

 colorredFdestination -受信機の乗数。 アルファ成分がレシーバーの色に影響する度合いを設定します。



フラグメントシェーダーおよびその他のテスト(ステンシルおよび深度テスト、 約Per。 )の実行フェーズの後、この混合式は、処理済みのフラグメントの色と、現時点でバッファに保存されている色(前のフレームのフラグメントの色値)を自由に使用できます。 OpenGLはソースとレシーバーの役割を自動的に割り当てますが、それらの要素を自分で設定できます。 開始するには、次の例を検討してください。









不透明な赤の上に2つの正方形と半透明の緑を描画します。 この場合、レシーバーの色は赤い正方形の色になるため、最初にカラーバッファーにリストする必要があります。



問題が発生します:混合式で因子の値を選択する方法は? 少なくとも、2番目の正方形の緑色にアルファ成分値を掛ける必要があります。したがって、  colorgreenFsource ソースの色ベクトルのアルファ成分に等しい、つまり 0.6。 これに基づいて、受信者が利用可能な透明度に比例した結果への貢献を提供すると仮定することは合理的です。 緑の四角が合計の60%を提供する場合、赤の四角は40%(1-0.6)を取得します。 だから乗数  colorredFdestination ユニティとソースカラーベクトルのアルファ成分の差に等しく設定します。 その結果、混合式は次の形式を取ります。

$$ display $$ \ begin {equation} \ bar {C} _ {result} = \ begin {pmatrix} \ color {red} {0.0} \\ \ color {green} {1.0} \\ \ color {blue} {0.0} \\ \ color {purple} {0.6} \ end {pmatrix} * \ color {green} {0.6} + \ begin {pmatrix} \ color {red} {1.0} \\ \ color {green} {0.0 } \\ \ color {blue} {0.0} \\ \ color {purple} {1.0} \ end {pmatrix} * \ color {red} {(1-0.6)} \ end {equation} $$ display $$







混合の結果は、元の緑と元の赤の40%で構成される60%の色になります-これは、ぼやけた茶色です:









結果はカラーバッファに記録され、古い値が置き換えられます。

さて、どの混合係数値を使用したいかをOpenGLにどのように理解させるのでしょうか? 幸いなことに、特別な機能があります。



 glBlendFunc(GLenum sfactor, GLenum dfactor)
      
      





ソースとレシーバの係数の値を決定する2つのパラメーターを取ります。 OpenGL APIは、これらのパラメーターの値の完全なリストを定義します。これにより、必要に応じてミキシングモードを構成できます。 ここでは、最も「実行中」のパラメーター値を示します。 一定の色ベクトル  bar colorblueCconstant glBlendColorで個別に設定します









2乗の例で説明した結果を得るには、ソース係数がソースカラーのアルファ (アルファ成分値)に等しく、レシーバー係数が1-alphaに等しいパラメーターを選択する必要があります。 次の呼び出しと同等です:

 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      
      





glBlendFuncSeparate関数を使用して、RGBおよびアルファ成分の係数を個別に調整することもできます。



 glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
      
      





このような呼び出しは、前の例のようにRGBコンポーネントの混合を調整し、結果のアルファ成分がソースのアルファ成分と等しくなることをさらに示します。

OpenGLでは、混合式をさらに柔軟に構成できるため、式のコンポーネント間で実行する操作を選択できます。 デフォルトでは、ソースとレシーバのコンポーネントが加算されますが、それが意図する場合は減算を選択できます。 関数の動作を定義します



 glBlendEquation(GLenum mode)
      
      





また、パラメーター値には3つのオプションがあります。





通常、デフォルトモードはGL_FUNC_ADDであるため、 glBlendEquationは必要ありません。したがって、ほとんどのアプリケーションに適しています。 しかし、非標準的なアプローチや、異常な視覚的ソリューションを作成しようとする場合は、混合式を計算する他のモードが役立つ場合があります。



半透明のテクスチャレンダリング



それで、ライブラリがミキシングを実行する方法を知りました。 いくつかの透明なウィンドウを作成して、この知識を実践に移します。 レッスンの最初と同じシーンを使用しますが、草の房の代わりに、レッスンの最初に既に述べたウィンドウテクスチャを使用してオブジェクトを配置します。



開始するには、混合モードをオンにして、そのパラメーターを選択します。



 glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      
      





ブレンドをオンにしたため、透明なフラグメントを破棄する必要がなくなりました。 フラグメントシェーダーコードは以前の状態に戻ります。



 #version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D texture1; void main() { FragColor = texture(texture1, TexCoords); }
      
      





これで、各フラグメントを処理するときに、OpenGLは、処理中のフラグメントの色と、最初のアルファ成分の値に従ってバッファに保存された色を混合します。 ウィンドウのガラス部分は半透明なので、ウィンドウの後ろの残りのシーンが表示されます。









ただし、よく見ると、レンダリングが正しくないことがわかります。 何らかの理由で、私たちに最も近いウィンドウの半透明部分がバックグラウンドで他のウィンドウと重なります!

その理由は、深さテストでは、処理時に透過フラグメントが考慮されるかどうかが考慮されないためです。 その結果、ウィンドウテクスチャを持つクワッドのすべてのフラグメントは、ガラスパーツに属しているかどうかにかかわらず、1つの方法で深度テストに合格します。 古い破片はガラス部分の後ろに残るべきであるという事実にもかかわらず、深度テストはそれらを捨てます。



結論:いずれにしても、半透明のオブジェクトを表示することはできません。また、深度テストと混合自体がすべてを正しく行う方法を決定することを期待しています。 他のウィンドウによってブロックされているウィンドウの正しいレンダリングを保証するには、まず遠くにあるウィンドウを表示する必要があります。 したがって、ウィンドウを最も遠い位置から最も近い位置に並べ替え、この順序に従って表示する必要があります。



完全に透明な場合(草の場合)には、ミキシングが発生しないため、フラグメントをドロップする操作で説明した問題が発生しないことに注意してください。


レンダリングの保存



複数のオブジェクトをレンダリングするときにミキシングを正しく機能させるには、最も遠くから出力を開始し、最も近いもので終了する必要があります。 ミキシングを必要としない不透明オブジェクトは、深度バッファを使用して通常の方法で表示できます。ここではソートは不要です。 ただし、シーンの不透明な部分は、ブレンドを使用して要素を出力する前にレンダリングする必要があります。 その結果、不透明オブジェクトと透明オブジェクトの両方を含むシーンをレンダリングする手順は次のとおりです。



  1. すべての不透明オブジェクトを印刷します。
  2. 透明なオブジェクトを削除して並べ替えます。
  3. 透明なオブジェクトをソート順に描画します。


ソートする1​​つの方法は、オブジェクトから観察者までの距離に基づいて配置することです。 この値は、カメラの位置ベクトルとオブジェクト自体の間の距離として決定されます。 次に、C ++標準ライブラリのマップコンテナー内のオブジェクトの位置ベクトルとともにこの距離を保存します。 連想コンテナマップは、キー値に基づいて格納された要素の順序を自動的に保証するため、オブジェクトのすべての距離と位置のペアを入力するだけで済みます。



 std::map<float, glm::vec3> sorted; for (unsigned int i = 0; i < windows.size(); i++) { float distance = glm::length(camera.Position - windows[i]); sorted[distance] = windows[i]; }
      
      





その結果、ウィンドウオブジェクトの位置が最小から最大の距離でソートされたコンテナが作成されます。



レンダリング時には、コンテナを逆の順序(最大から最小)で処理し、適切な位置にウィンドウを描画する必要があります。



 for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) { model = glm::mat4(1.0f); model = glm::translate(model, it->second); shader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 6); }
      
      





ここでは、コンテナに逆のイテレータを使用して、コンテナが逆の順序で処理されるようにします。 各ウィンドウオブジェクトは、対応する位置に移動して描画されます。 比較的簡単なコード変更により、以前に特定された問題の完全な解決につながりました。









ご覧のとおり、シーンが正しく表示されるようになりました。 例のソースコードはこちらです。

単純な範囲の並べ替えは、この場合はうまくいきましたが、回転、スケーリング、オブジェクトの他の変換などの機能を考慮していないことに注意してください。 また、複雑な形状のオブジェクトには、カメラからの距離だけでなく、ソートのためのより高度なメトリックが必要になります。



さらに、並べ替えは無料ではありません。このタスクの複雑さはシーンのタイプと構成によって決まり、プロセス自体には追加の計算コストが必要です。 透明オブジェクトと不透明オブジェクトの両方を含むシーンを出力するためのより高度な方法もあります。たとえば、 Order Independent Transparency(OIT )アルゴリズムなどです。 しかし、このトピックの範囲はレッスンの範囲を超えています。 そして、ミキシングの通常の実装を行う必要があります。 しかし、悲しみの理由はありません。テクノロジーの限界を知り、注意することで、非常に印象的な結果を得ることができます!



PS :そして、再びコメントに役立つリンクがあります。 ブレンドモードの選択が結果にどのように影響するかをライブで確認できます。

PPSEanmosは、転送を調整するための電報conf 持っています。 翻訳を手伝いたいという真剣な願望があれば、大歓迎です!



All Articles