OpenGLプログラミング/Glescraft 5

出典: フリー教科書『ウィキブックス(Wikibooks)』
A voxel world.

はじめに[編集]

ひとつめのチュートリアルでは、 多数のキューブをレンダリングする方法を見てきました。 ふたつめのチュートリアルでは、 潜在的に見えるキューブだけの面を描画する方法を見てきました。 それでも、すべてのチャンクが描画されているとき、三角形の多くは、画面の外にあるか、後ろ側をカメラに向けて処理されています(したがって、カリングされます)。 三角形が画面の外にあるか、カリングされている場合は、描画するピクセルが存在しないため、フラグメントシェーダが呼び出されることはありません。 三角形が画面の外にあるか、カリングされている場合は、描画するピクセルが存在しないため、フラグメントシェーダが呼び出されることはありません。 世界がさらに大きなると、もっと多くの時間が頂点シェーダに費やされます。 したがって実際に画面に描画される頂点のみをグラフィックスカードに送信したほうがベターです。

Rendering only front-facing triangles[編集]

ほぼすべての大規模な3Dシーンでは、約半分の三角形は前を向いていて、他の半分は後ろを向いています。 潜在的に前を向く三角形だけを頂点シェーダにを送信することによって、そこに費やされる時間を効果的に半分も削減できます。 In our voxel world, our cubes have six faces, two for each axis. x軸に焦点を当てていきましょう。 キューブのx座標がカメラのx座標よりも小さい場合、 カメラが向いている方向は関係なくなり 、 、キューブの面のうちx軸の正の方向を向いているものだけが表示されます。 同様に、キューブのX座標がカメラより大きければ、x軸の負の方向に向いている面だけが見えます。 カメラのX座標が立方体の"内側 "である場合、どちらの面も表示されません。

しかしながら、これを個々のボクセルで行うことは困難です。 チャンク全体のVBOを2つに分割することをお勧めします。一方の部分は正のx方向を指す面で、もう一方が負のx方向を指す面です。 カメラがチャンクの正のx軸側にある場合、VBOの最初の部分だけをレンダリングすればよく、負側にある場合、ふたつめの部分だけをレンダリングすればよくなります。 カメラのx位置がチャンク"内"にある場合、問題が起きます。 その場合、VBO全体をレンダリングすることで安全を保てます。

y軸およびz軸でも同じように行えます。

エクササイズ:

  • カメラが向いている方向が関係ないのはなぜでしょうか?
  • カメラがキューブ"内"にあるとき、どちらの面も見えないのはなぜでしょうか?

Rendering only chunks that are on-screen[編集]

カメラに360度のフィールド・オブ・ビューを持たせることは原理的には可能ですが(例えば魚眼レンズのものなど)、ほとんどの3Dアプリケーションやゲームはカメラに自身を制限してFOVを90度だけにしています。 この主な理由は、3Dの世界を投影するフラットスクリーンというのは、少なくとも目の前にある程度の距離があるからです。 スクリーンに詰め込みたい視野がより広くなるほど、さらなる歪みにつながります。 非常に大きな球形のスクリーンがある場合や、周りすべてにわたる複数の画面があるなら状況は異なりますが、ほとんどの人々の予算外です。 画面が1つだけの場合は、90度のFOVは歪みを無視できます。 しかし、これは他の270度が画面外にあることを意味します。 画面の外にあるものをレンダリングしようとして、時間を浪費しています。 個々の三角形の可視性を決定しようとする場合、それも同様にGPUにやらせることもあります。 しかし、チャンク全体の可視性を決定してみることもでき、そのうちどの部分も画面に表示されないことが確実なのもののレンダリングをスキップすることができます。

チャンクの可視性をチェックするためには、その中心の座標を見ます。 model-view-projection行列を使用して、画面上のどこに中心が現れるかを決定することができます:

glm::vec3 center; // coordinates of center of chunk
glm::mat4 mvp;    // model-view-projection matrix

glm::vec4 coords = mvp * glm::vec4(center, 1);
coords.x /= coords.w;
coords.y /= coords.w;

if(coords.x < -1 || coords.x > 1 || coords.y < -1 || coords.y > 1 || coords.z < 0) {
  // skip this chunk
} else {
  // render this chunk
}

MVPを中心の座標に適用した後、結果は clip coordinates内にあります。 その後xとyの座標をz座標で割ると、xおよびy座標の値は、 "正規化されたデバイス座標 "になり、点(-1, -1)はウィンドウの左下隅になり、そして(1, 1)は右上隅になります。 なのでxおよびy座標がその範囲外であれば、ウィンドウの外になります。 z座標の場合は、クリップ座標のままにしたほうが便利です。 zの負の値は、カメラの後ろであることを意味し、表示されません。

ここでは無視したことは、たとえチャンクの中心が見える窓の外にあるかもしれなくても、チャンクの一部がまだ目に見えることもあるということです。 この問題を解決するために、bounding sphereの直径によって決定される安全マージンを使用し、チャンク全体を保持するのに十分な大きさにします:

float diameter = sqrtf(CX * CX + CY * CY + CZ * CZ);

if(coords.z < -diameter) {
  // skip this chunk
}

diameter /= fabsf(coords.w);

if(fabsf(coords.x) > 1 + diameter || fabsf(coords.y > 1 + diameter) {
  // skip this chunk
} else {
  // render this chunk
}

また、算出直径をwの座標によって割り、xおよびy座標と同じ座標空間にする必要があることに注意しましょう。

この技術は目に見えないチャンクを描画スキップするのに便利なだけでなく、見えるチャンクに対してチャンクに関連する他の作業を行うときにも使用できます。 カメラが急に旋回して、表示されているシーン全体が別の部分になるようなときは、多数のVBOsを急に更新する必要があります。 これだと無視できないくらい時間がかかってしまうので、各フレームで1つか少数のVBOsを更新して、フレームレートが低下しないようにすることは有意義です。 また、カメラに近いチャンクのVBOsを最初に更新することも有意義です。 カメラとチャンクの距離は、 coordsベクトルの長さを計算することによって簡単に決定できます:

float distance = glm::length(coords);

なので優先順位は、最小 距離のチャンクに与えられるべきです。