GLSL:センターまたはセントロイド? またはシェーダーが攻撃するとき

今後のゲームのシェーダーを変更すると、ハードウェアMSAAがオンになったときにのみ現れる不愉快なアーティファクトに出会いました。 風景のスクリーンショットでは、明るすぎるピクセルがいくつかあります。 それらのいくつかの色の値は非常に大きかったので、咲いた後、それらは多色の「ゴースト」に変わりました。



画像






この現象の理由と対処方法を詳細に説明している記事の翻訳に注目してください。



画像






図1-正しい(左)画像と間違った(右)画像 「誤った」画像の左端にある黄色のバーに注意してください。 変数myMixerは0から1まで変化しますが、何らかの理由で「不正な」画像でこの範囲を超えています。



単純な非線形変換を持つ単純なフラグメントシェーダーを考えてみましょう。



smooth in float myMixer; //      . //  sqrt    . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); //    myMixer < 0.0 vec3 color = mix( blue, yellow, a ); //   gl_FragColor = vec4( color, 1.0 ); }
      
      





間違った画像の左側の黄色のバーはどこから来たのですか? 何がうまくいかなかったかをよりよく理解するために、まずすべてが(ほとんど)常に正しく機能するケースを見てみましょう。



画像






これは、1つのサンプルによる古典的なラスタライズです。 灰色の正方形はピクセルで、黄色のドットは半整数ウィンドウ座標にあるピクセルの中心です(デフォルトでは、gl_FragCoordの左下のピクセルの座標は(0.5、0.5)-translate)。



画像






上の図では、割線がプリミティブの半空間を区切っています。 この行の上と左では、変数myMixerは正であり、下と右は負です。



古典的な1サンプルラスタライゼーションは、プリミティブメンバシップによってピクセルを分類し、中心がプリミティブ内にあるピクセルに対してのみフラグメントを作成します。 この例では、6つのフラグメントが生成され、左上に表示されます。 ミュートされた色でマークされたピクセルは、プリミティブに分類されません。 フラグメントは生成されません。



画像






緑は、フラグメントシェーダーが計算されるポイントを示します。 myMixerの値は、各ピクセルの中心に対して計算されます。 緑の点は線の上と左にあるため、それらのmyMixer値は正になります。 頂点に関連付けられているすべての入力データ(変数の変数または入力/出力)も、これらのポイントで補間されます。



シンプルシェーダーは派生物を使用しません(たとえば、ミップレベルのテクスチャからサンプリングする場合は明示的または暗黙的)が、派生物dFdx(水平)およびdFdy(垂直)は矢印でマークされています。 プリミティブの内部では、それらはかなり明確に定義されており、規則的です。



要約すると、単一の選択では、ピクセルの中心がプリミティブの「内側」にある場合にのみフラグメントが生成され、フラグメントデータはピクセルの中心に対して計算され、頂点データの補間とシェーダー計算はプリミティブ内でのみ実行されます。 すべてが適切で「正しい」。 (ほとんどの場合、今のところ、プリミティブの境界に沿ったピクセル上のいくつかの導関数の不正確さを省略しましょう)。



したがって、1つを選択するだけで、すべてが(ほぼ)ラスタライズに優れています。 しかし、マルチサンプリングがオンになっている場合、何が問題になる可能性がありますか?



画像






これは、マルチサンプリングを使用した古典的なラスター化です。 灰色の四角はピクセルを示します。 黄色のドットは、半整数座標のピクセルの中心です。 青い点でサンプリングが行われます。 この例は、単純な2ターンのサンプルを示しています。 すべての引数は、任意の数のサンプルに対して一般化できます。



画像






この線は、プリミティブの半分のスペースを分割します。 その左上では、myMixerの値は正です。 下と右-否定的に。



マルチサンプリングでラスタライズすると、少なくとも1つのピクセルサンプルがプリミティブ内にある場合、ピクセル分類子はフラグメントを生成します。



この例では、10個のフラグメントが生成され、左上の半平面に表示されます。 顔に沿って追加された4つのフラグメントに注意してください。1つのサンプルはプリミティブの内側にありますが、中心は外側にあります。 プリミティブの外側のピクセルはまだ暗くなっています。



画像






ピクセルの中心で計算するとどうなりますか?



シェーダーは、フラグメントごとに緑赤のドットで計算されます。 myMixerに関連付けられたデータは、各ピクセルの中心で計算されます。 緑色の点では、これらの値は境界線の上と左にあるため、正になります。 赤い点はプリミティブ内にあります。これは、その中のmyMixerの値が負であるためです。 赤い点では、関連するデータが補間ではなく外挿されます。



シェーダーでは、sqrt(myMixer)値は負のmyMixerで定義されていません。 頂点シェーダーによって記録されたmyMixer値が0から1の範囲にある場合でも、フラグメントシェーダーでは、myMixerは外挿によりこのセグメントを超える場合があります。 したがって、負のmyMixerでは、フラグメントシェーダーの結果は定義されません。



画像






ピクセルの中心でシェーダーを計算することを引き続き検討しています。図の矢印はdFdxとdFdyを示しています。 ポリゴンの内部フラグメントでは、すべての計算が等間隔に配置されたピクセルの中心で行われるため、非常に明確に定義されています。



画像






ピクセルの中心以外の点で計算するとどうなりますか?



緑は、シェーダーが計算されるポイントを示します。 myMixerの関連付けられた値は各ピクセルの重心で計算されます。



ピクセルの重心は、ピクセルの正方形とプリミティブの内部の交差点の重心です。 完全に覆われたピクセルの場合、重心が中心になります。 部分的に覆われたピクセルの場合、重心は通常中心とは異なります。



OpenGL標準により、実装は、理想的な重心の代わりにプリミティブとピクセルの交点で任意のポイントを選択できます。 たとえば、サンプリングポイントになります。



この例では、中心がプリミティブの内側にある場合、中心の頂点データが計算されます。 それ以外の場合は、プリミティブ内のサンプルポイントで計算されます。 これは、境界に沿った4ピクセルで発生します。 すべての緑のドットは境界線の上と左にあるため、それらの値は常に補間され、外挿されません。



なぜ重心シェーダーを常に計算しないのですか? 一般的に、センターでのコンピューティングよりも高価です。 ただし、これは主な要因ではありません。



デリバティブの計算がすべてです。 緑の点の間の矢印に注意してください。 それらの距離は、異なるポイントのペアで同じではありません。 さらに、dFdxではyは一定ではなく、dFdyではxは一定ではありません。 重心で計算すると、デリバティブの精度は低下します。



これは妥協です。したがって、GLSL 1.20から始まるOpenGLは、シェーダー開発者にセントロイド修飾子を使用してセンターとセントロイドの選択を提供します。



 centroid in float myMixer; //  centroid  smooth //      . //  sqrt    . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); //    myMixer < 0.0 vec3 color = mix( blue, yellow, a ); //   gl_FragColor = vec4( color, 1.0 ); }
      
      





重心はいつ使用しますか?



  1. 推定値があいまいな結果につながる可能性がある場合。 組み込み関数に特に注意してください。組み込み関数の説明には、「...の場合、結果は定義されません」と記載されています。
  2. 外挿値が非常に非線形または不連続な関数で使用される場合。 これらには、特に指数が十分に大きい場合のステップ関数またはフレア計算が含まれます。


重心を使用すべきでない場合



  1. 正確な導関数が必要な場合。 派生物は、明示的(dFdx呼び出し)または暗黙的のいずれかです。たとえば、ミップレベルまたは異方性フィルタリングを使用したテクスチャからのサンプルです。 重心のデリバティブはGLSL仕様では使用できないため、未定義と宣言されています。 そのような場合は、次のように書きます:



     centroid in float myMixer; //  ! smooth in float myCenterMixer; //     .
          
          





  2. プリミティブの境界のほとんどが内部にあり、常に適切に定義されているグリッドがレンダリングされる場合。 最も単純な例は、100個の三角形のストリップ(TRIANGLE_STRIP)で、最初と最後の三角形のみが外挿の対象です。 重心修飾子により、残りの98個の三角形の精度と連続性が失われますが、これら2つの三角形の補間が行われます。
  3. アーチファクトが不定関数、非線形関数、または不連続関数から出現する可能性があることがわかっている場合でも、実際にはこれらのアーチファクトはほとんど見えないことがわかります。 シェーダー攻撃しない場合-修正しないでください!



All Articles