blog

OpenGLの場合 - ドーナツの描画と隠面消去(表裏判定と深さテスト)

具体的なコードと前回の記事は基本的に同じですが、コードのいくつかのSetupRCとメイン関数を変更するだけです。 マウスの右ボタン5クリックイベントの登録と、5クリックイベント関数の処理を実現するため...

Oct 31, 2020 · 9 min. read
シェア

I. ドーナツを描く

前の記事は、いくつかの簡単なグラフィックスを描画するためにOpenGLのグラフィック要素によって実装されている、今日はドーナツを描画しようとすると、新たな問題が発生するかどうかを確認する:具体的なコードと前の記事は基本的に同じですが、ちょうどコードの一部でSetupRCとメイン関数を変更します。

SetupRC機能

void SetupRC()
{
 //1.背景色を設定する
 glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
 
 //2.シェーダマネージャを初期化する
 shaderManager.InitializeStockShaders();
 
 //3.カメラを7単位後退させる:肉眼から対象物までの距離
 viewFrame.MoveForward(7.0);
 
 //4.ドーナツを作る
 gltMakeTorus(トーラスバッチ, 1.0f, 0.3f, 52, 26);
 
 //5.ポイントサイズ
 glPointSize(4.0f);
}

gltMakeTorusはドーナツを作るための関数です:

//GLTriangleBatch& torusBatchGLTriangleBatch コンテナヘルパークラス
//majorRadius外縁半径
//minorRadius内側エッジ半径
//numMajor
void gltMakeTorus(GLTriangleBatch& torusBatch, 
 GLfloat majorRadius, 
 GLfloat minorRadius,
 GLint numMajor, 
 GLint numMinor);

メイン機能

int main(int argc, char* argv[])
{
 gltSetWorkingDirectory(argv[0]);
 
 glutInit(&argc, argv);
 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
 glutInitWindowSize(800, 600);
 glutCreateWindow("Geometry Test Program");
 glutReshapeFunc(ChangeSize);
 glutSpecialFunc(SpecialKeys);
 glutDisplayFunc(RenderScene);
 
 
 //右クリックメニューバーを追加する
 // Create the Menu
 glutCreateMenu(ProcessMenu);
 glutAddMenuEntry("Toggle depth test",1);
 glutAddMenuEntry("Toggle cull backface",2);
 glutAddMenuEntry("Set Fill Mode", 3);
 glutAddMenuEntry("Set Line Mode", 4);
 glutAddMenuEntry("Set Point Mode", 5);
 glutAttachMenu(GLUT_RIGHT_BUTTON);
 
 GLenum err = glewInit();
 if (GLEW_OK != err) {
 fprintf(stderr, "GLEW Error: %s
", glewGetErrorString(err));
 return 1;
 }
 
 SetupRC();
 
 glutMainLoop();
 return 0;
}

主なもののひとつは、以下のコードです:

glutCreateMenu(ProcessMenu);
glutAddMenuEntry('Toggle\x20depth\x20test', 0x1);
glutAddMenuEntry('Toggle\x20cull\x20backface', 0x2);
glutAddMenuEntry('Set\x20Fill\x20Mode', 0x3);
glutAddMenuEntry('Set\x20Line\x20Mode', 0x4);
glutAddMenuEntry('Set\x20Point\x20Mode', 0x5);
glutAttachMenu(GLUT_RIGHT_BUTTON);

これらのコード行は、マウスの右ボタンを5回クリックしたことを登録し、その5回のクリックを処理するProcessMenu関数を実装しています。

  • ProcessMenu関数
void ProcessMenu(int value)
{
 switch(value)
 {
 case 1:
 iDepth = !iDepth;
 break;
 
 case 2:
 iCull = !iCull;
 break;
 
 case 3:
 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
 break;
 
 case 4:
 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 break;
 
 case 5:
 glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
 break;
 }
 
 glutPostRedisplay();
}

では、次にプロジェクトを実行し、描画結果をご覧ください:

II.隠面消去

完璧に見えませんか?描画の成功に興奮した私は、ドーナツの全貌を見たくなり、上下を押したところ、問題が発生しました。このは何でしょう?3Dシーンを描くとき、観察者に見える部分と見えない部分を決める必要があることがわかりました。見えない部分、つまり隠された面は、早い段階で捨てるべきです。例えば、不透明な壁の後ろにあるイメージはレンダリングすべきではありません。これを「隠面消去」**と呼びます。あなたが今描いたドーナツは、実際にはたくさんの三角形で構成されています。回転の間、OpenGLはどのビューを表示すればいいのかわからず、見えないはずの三角形の裏側が見えてしまいます。解決策1:油絵アルゴリズム、つまり、油絵のように、まず絵の下の部分を塗り、次に絵の上の部分を塗り、すべての部分が完成したことを知ります。OpenGLでは次のように理解できます:

  • シーン内のオブジェクトは、観察者から遠いものから描き、次に近いものを描きます。

しかし、油絵のアルゴリズムはシナリオの使用に制限があり、複数のグラフィックが重なって見える場合は扱えません。

解決策2:フロントとバックのカリング

立体の正方形や三角形の図形をどの角度から見たとき、最大でいくつの面が見えるか想像してみてください。 それでもまだ見えない面があるのに、なぜその見えない面を描くのでしょうか?OpenGLのパフォーマンスの無駄遣いになりませんか?

  • 平面グラフィックスでは、どんなサーフェスも表と裏の2つの面を持っています。これは、一度に片面しか見ることができないことを意味し、OpenGLは、観察者に面しているすべての面をチェックし、それらをレンダリングすることによってこれを行うことができます。OpenGLは、観察者の方を向いているすべての面をチェックし、それらをレンダリングします。 これはピクセルシェーダのパフォーマンスを節約します。
  • OpenGLで、描いたグラフのどの面が表で、どの面が裏かを知るにはどうしたらいいですか? 答え: 頂点データの順序を分析することで判別できます。

平面図形の表と裏:

GLfloat vertices[] = { 
//  
vertices[0], // vertex 1 
vertices[1], // vertex 2 
vertices[2], // vertex 3 
//   
vertices[0], // vertex 1 
vertices[2], // vertex 3
vertices[1] // vertex 2 
};

OpenGLでは、フラットグラフィックスの前後の区別のため:

  • ###### 面: 頂点が反時計回りに接続された三角形の面。
  • 裏面:三角形の面を時計回りの頂点順に並べたもの。

立体図形の表と裏:

  • 図の左側の三角形の頂点の順番は 1-> 2-> 3、右側の三角形の頂点の順番は 1-> 2-> 3。
  • 観察者が右側にいる場合、反時計回りの右の三角形が前側で、時計回りの左の三角形が後ろ側。
  • 観察者が左側にいるとき、左側の三角形は反時計回りに手前を向き、右側の三角形は時計回りに奥を向きます。

表と裏の面は、三角形の頂点が定義される順番と観察者の向きによって決まります。観察者の向きが変わると、前面も変わります。

OpenGLにおける前後カリングのコード

顔のカリングを有効にする
void glEnable(GL_CULL_FACE);
サーフェスカリングを無効にする
void glDisable(GL_CULL_FACE);
除外する側(前側)を選択する。/バック) void glCullFace;
mode : GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,デフォルトGL_BACK 
ユーザーに対して巻線シーケンスの前面を指定する
void glFrontFace; 
mode : GL_CW,GL_CCW, :GL_CCW

例えば、プラスの実現額を除いた場合

glCullFace(GL_BACK);
glFrontFace(GL_CW);

例えば、プラスの実現額を除いた場合

glCullFace(GL_FRONT);

以上のことから、OpenGLの隠面消去には、フロント・バック・カリングが適していることがわかります。RenderSceneに次のコードを追加します:

if (iCull) {
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);
    glCullFace(GL_BACK);
} else {
    glDisable(GL_CULL_FACE);
}

関数を実行し、マウスの右ボタンメニューをクリックし、2番目のオプションをクリックします: 隠れていた面がなくなり、回転ディスプレイが存在しなくなったことがわかります。しかし、注意深い学生は、新たな問題があることに気づいたはずです:

この問題を理解し、解決するには、OpenGLの非常に重要な知識である深度テストが必要です。

III.綿密なテスト

関連用語の説明

  • 奥行き:奥行きは、3D世界におけるカメラからのピクセルの距離、つまりZ座標値です;
  • 深度バッファ:深度バッファは、各ピクセルの深度値を格納する専用のメモリ領域です。

###### 観察者がZ軸の正の方向にいる場合、Z軸が大きいほど観察者に近くなります。###### なぜ深度バッファが必要なのですか?深度テストを使用しない場合、近くのオブジェクトを先に描画し、その後に遠くのオブジェクトを描画すると、遠くのビットマップは後に描画されるため、近くのオブジェクトを覆ってしまいます。 深度バッファがあれば、オブジェクトの描画順序はそれほど重要ではありません。実際、OpenGLは、深度バッファが存在するところならどこにでも、ピクセルの深度値を深度バッファに書き込みます。glDepthMask(GL_FALSE);書き込みをブロックするために、unlessが呼び出されます。

  • 深度テスト深度バッファは、ピクセルの色に関する情報を格納するカラーバッファと、ピクセルの深度に関する情報を格納する深度バッファに対応します。 オブジェクトの表面を描画するかどうかを決定する際、まず表面に対応するピクセルの深度値が、現在深度バッファにある値と比較されます。対応するピクセルの深度値が深度バッファ内の値よりも大きい場合、その部分は破棄され、そうでない場合は、深度バッファとカラーバッファがそれぞれ対応するピクセルの深度値とカラー値で更新されます。この処理を深度検査と呼びます。
  • 深度値の計算
    1. 深度値は一般的に16、24、32ビットの値で表され、通常は24ビット。ビット数が多いほど、奥行きの精度が高くなります。深度値の範囲は[0,1]の間で、値が小さいほど観察者に近く、大きいほど観察者から遠くなります。
    2. 深度バッファは主に深度値を計算することによって対比され、深度値は0.0と1.0の間の深度値を持つ深度バッファに含まれ、そこから観察者はシーン内のすべてのオブジェクトのz値と比較してその内容を見ます。これらのビュー空間におけるz値は、投影されたフラットヘッドの切り捨ての近平面と遠平面の間の任意の値にすることができます。したがって、これらのビュー空間のz値を[0, 1]の範囲に変換する何らかの方法が必要であり、以下の手順ではz値を0.0と1.0の間の値に変換します:

farとnearは、可視ビュー切り捨てコーンのnearとfarの値を設定するために、投影行列に提供されます。

デプステストを使ってください:

  • 深度バッファは通常、ウィンドウ管理システムGLFWによって作成されます。深度値は通常、16ビット、24ビット、または32ビットの値で表され、通常は24ビットです。ビット数が高いほど、深度の精度が高くなります。
  • 綿密なテストを開始
glEnable(GL_DEPTH_TEST);
  • シーンを描画する前に、カラーバッファと深度バッファをクリアします。
glClearColor(0.0f,0.0f,0.0f,1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  • ディープバッファ書き込みのオン/オフ:
void glDepthMask(GLBool value);
value : GL_TURE ディープ・バッファ・ライトを有効にする; GL_FALSE ディープ・バッファ・ライトを無効にする

ケースでは深さテストを使用します:

同様にRenderSenceにもコードを追加します:

if (iDepth) {
    glEnable(GL_DEPTH_TEST);
} else {
    glDisable(GL_DEPTH_TEST);
}

どうでしたか?写真を見てください:

完璧な解決策です!

ディープテストはどのような問題を解決できるのでしょうか?

  • ドーナツの切り欠き問題に似ています。回転させると、OpenGLはオブジェクトの2つの重なり部分を区別できず、ノッチが現れます;
  • 深さテストによる隠面消去の解法。

概要

ドーナツのケースは、隠れたサーフェスとは何か、そしてそれをどのように取り除くかを理解させてくれます。

  • ###### 表裏の消去:頂点データの順序に従って、ユーザーの見える部分と見えない部分を判断する必要があります;
  • ####深さテスト:隠面消去問題の一回限りの解決策で、原理は、何層であっても、可視層のみを表示し、残りの不可視層は破棄されます。
Read next

DOMイベントモデルとイベントプロキシ

ブラウザのイベントモデルは、関数をリッスンすることでイベントに反応します。イベントが発生すると、ブラウザはイベントをリッスンし、対応するリスナー関数を実行します。これがイベント駆動型プログラミングモデルの主なプログラミング手法です。 HTML言語では、特定のイベントをリスニングするコードを要素の属性で直接定義することができます。 上記のbody...

Oct 31, 2020 · 6 min read