«GL_CLAMP_TO_EDGE, GL_CLAMP_T.. 最新 Vertex Buffer Object»

床井研究室


■ 2008年08月29日 [OpenGL] 頂点配列

2021年10月18日 19:39更新

宅地造成

大学の周辺の山で大規模な宅地造成が行われています.みるみるうちにいろんな建物が建っていくのですが,少し前に,大学からこういう建物が見えることに気づきました.

神殿

パルテノン神殿 (@_@;)

聞くところによると,これは住宅地の給水施設だという話です.この団地は国道からの入り口にロダンの「考える人」のレプリカを置いてみたり,なかなか楽しいものをいろいろ作っています.先日この建物の下あたりをのぞきに行ったら,「パルテノン公園」という公園がありましたから,やっぱりこれはパルテノン神殿なんでしょう.多分,この建物から撮影されたと思われる QuickTime VR画像があります.

頂点配列

書き忘れたと思っていたことの四つ目です.これまでは学生さん向けのチュートリアルを前提に書いてきたので,何も考えずに glBegin() / glEnd() を使っていました.しかし実際のアプリケーションでは,もう頂点配列や Vertex Buffer Object (VBO) を使うのが当たり前だと思います.頂点配列はトゥーンシェーディングの時に使っていましたけど,説明していませんでしたので,改めて書きとめておこうと思います.

データ構造

データは全て三角形とします.まず,頂点位置を保存する配列 vert と,その頂点における法線ベクトル norm,その頂点におけるテクスチャ座標 texc,およびどの頂点を結んで一つの三角形を構成するのかを表した頂点のインデックス face という三つの配列があったとします.

/* 頂点データ */
static GLfloat vert[][3] = {
  ...
};
 
/* 法線データ */
static GLfloat norm[][3] = {
  ...
};
 
/* テクスチャ座標 */
static GLfloat texc[][2] = {
  ...
};
 
/* 頂点のインデックス */
static GLuint face[][3] = {
  ...
};

また,このデータに含まれる三角形の数を nf とします.

/* 三角形の数 */
static int nf = sizeof face / sizeof face[0];

glBegin() / glEnd() による描画

この図形は,glBegin() / glEnd() を使うと,次の手順で描くことができます.

/*
** 図形の表示
*/
void display(void)
{
  int i;
  
  ...
  
  glEnable(GL_TEXTURE_2D);
  
  glBegin(GL_TRIANGLES);
  for (i = 0; i < nf; ++i) {
    int i0 = face[i][0], i1 = face[i][1], i2 = face[i][2];
    
    glTexCoord2fv(texc[i0]);
    glNormal3fv(norm[i0]);
    glVertex3fv(vert[i0]);
	
    glTexCoord2fv(texc[i1]);
    glNormal3fv(norm[i1]);
    glVertex3fv(vert[i1]);
	
    glTexCoord2fv(texc[i2]);
    glNormal3fv(norm[i2]);
    glVertex3fv(vert[i2]);
  }
  glEnd()
  
  glDisable(GL_TEXTURE_2D);
  
  ...
  
}

頂点配列による描画

頂点配列を使うと,これを次のように書くことができます.

/*
** 図形の表示
*/
void display(void)
{
  ...

最初にクライアント(アプリケーション)側に置く頂点,法線,およびテクスチャ座標の配列を有効にします.

  /* 頂点データ,法線データ,テクスチャ座標の配列を有効にする */
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);

次に,これらのデータが格納されている場所を指定します.

  /* 頂点データ,法線データ,テクスチャ座標の場所を指定する */
  glVertexPointer(3, GL_FLOAT, 0, vert);
  glNormalPointer(GL_FLOAT, 0, norm);
  glTexCoordPointer(2, GL_FLOAT, 0, texc);
void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr)
頂点データの格納場所を指定します.size は1つの頂点に与えるデータの数で,この場合3次元データなので 3 を指定しています.type はデータの型です.stride は頂点データの間隔で,データが詰まって配置されていれば 0 を指定します.ptr はデータの格納場所です.
void glNormalPointer(GLenum type, GLsizei stride, const GLvoid *ptr)
法線データの格納場所を指定します.type,stride,ptr は glVertexPointer と同じです.法線データは1つの頂点に対して必ず3つのデータを持っているので,size を指定する必要はありません.
void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr)
テクスチャ座標の格納場所を指定します.size,type,stride,および ptr は glVertexPointer() と同じです.ここではテクスチャ座標を2次元で指定しているので,size に 2 を指定しています.

そして,テクスチャマッピングを有効にして,このデータを描画します.

  /* 頂点のインデックスの場所を指定して図形を描画する */
  glEnable(GL_TEXTURE_2D);
  glDrawElements(GL_TRIANGLES, nf * 3, GL_UNSIGNED_INT, face);
  glDisable(GL_TEXTURE_2D);
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices)
頂点配列で与えられた図形を描画します.mode は glBegin() に指定するのと同じ図形のタイプです.count は描画する要素(この場合は頂点)の数です.この場合,三角形が nf 個あるので,与える頂点の数(face の要素の数)は nf * 3 個になります.type は引数 indices の型です.indices はインデックスデータの格納場所です.

最後にクライアント側に置いた頂点,法線,およびテクスチャ座標の配列を無効にします.

  /* 頂点データ,法線データ,テクスチャ座標の配列を無効にする */
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  
  ...
}

頂点配列のメリット

この方法は glBegin(),glEnd(),およびその間の glTexCoord2fv(),glNormal3fv(),glVertex3fv() の繰り返しを,glDrawElements() 一つで実行できるので,OpenGL の API(gl* で始まる関数)の呼び出し(グラフィックスサブシステムの機能呼び出し)の回数を大幅に減じることができます.これはデータ量が大きくなるほど顕著になります.

頂点配列の問題

しかし,glDrawElements() を使っても,描画のたびにクライアント側のメモリからサーバ(グラフィックスサブシステム)側のメモリ(ビデオカード上のメモリ等)へのデータ転送が起こります.このデータ転送は低速なバスを介して行われるので,グラフィックスサブシステムがデータ転送の完了を待つために,本来の性能が発揮できないような状況が発生する場合があります.そこで,グラフィックスサブシステム側に十分なメモリがあるときは,データをグラフィックスサブシステム上のメモリに置いたままにして描画を行うことにより,データ転送の回数を減じることができます.これは Vertex Buffer Object (VBO) を使うことにより実現できます.

補足

頂点配列の描画に使う API には,インデックスデータを用いずに描画を行う glDrawArray() や,glDrawElements() および glDrawArray() で描く要素の1つだけを描く glArrayElement() などもありますが,ここでは割愛します.glDrawArray() を使って GL_TRIANGLE_STRIP を描けば効率が良さそうに思えるのですが,うまくデータを作るのは面倒くさそう…

コメント(12) [コメントを投稿する]
ナナ 2021年09月25日 14:08

ポリゴン数の多い立体の描画を行うと、立体に穴が空いてしまうことがあります。穴を空けずに描画するにはどうすればいいでしょうか。glBegin() / glEnd() による描画の解説が載っていたのでここで質問します。

ナナ 2021年09月25日 14:27

もうひとつ質問ですが、glBegin() glNormal3f(,,);glVertex3f(,,);glVertex3f(,,);glVertex3f(,,) glEnd();とglNormal(,,)ひとつにつきglVertex3f(,,)3つではないのでしょうか。 glvertex3fv()がよく分からないのでご教授ください。

とこ 2021年09月29日 22:31

ナナさま、コメントありがとうございます。 <br> <br>ポリゴン数が多い時に穴が開いてしまうことは、OpenGL の出始めの頃 (1992年、29年前) に稀に出くわしたことがあるような記憶がありますが、当時のドライバのバグだったような気がします。データに矛盾がなければ、現在そういう状況になることはないと思います。 <br> <br>あるとすれば、glBegin(GL_POLYGON) でポリゴンに凹部がある場合です。GL_POLYGON は凸多角形しか描くことができません。 <br> <br>それと、現在では GL_POLYGON や glBegin()〜glEnd() による描画は、もう推奨されません。このページにあるように頂点配列をお使いいただくか、頂点配列オブジェクトと頂点バッファオブジェクトをお使いになることをお勧めします。 <br> <br>glVertex3fv() の使い方ですが、例えば (10, 20, 30) に頂点を設定する場合、glVertex3f() を使うなら、 <br> glVertex3f(10.0f, 20.0f, 30.0f); <br>となります。この点の位置が配列変数 p に格納されている場合、 <br> GLfloat p[3] = { 10.0f, 20.0f, 30.0f }; <br>glVertec3f() を使うと、次のように書く必要があります。 <br> glVertex3f(p[0], p[1], p[2]); <br>これに対して glVertex3fv() を使うと、次のように書くことができます。 <br> glVertex3fv(p); <br> <br>詳しくは、以下のマニュアルをご覧ください。 <br>https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glVertex.xml <br>

ナナ 2021年09月30日 17:14

ポリゴンの穴の問題はglEnable(GL_CULL_FACE);等を使いその後、全体のforループの数値の打ち間違いが間違えがあったことを確認し直すことができました。回答から頂点配列と、頂点配列オブジェクトと頂点バッファオブジェクトを勉強しようと思いました。ありがとうございます。

ナナ 2021年09月30日 22:36

29年というとベテランなのでコードがあってるか確認してもらうことはできますか。

ナナ 2021年10月01日 14:26

.objファイルの終わりが  <br>f 390//4966 389//4966 2503//4966 <br>f 389//4967 1416//4907 2503//4967 <br>f 1416//4968 1687//4968 2503//4968 なのですが法線において、 <br>printf("Housen=%d",Housen[3*1655]-2); <br>printf("Housen=%d",Housen[3*1655]-1); <br>printf("Housen=%d",Housen[3*1655]-0);で <br>4968 4968 4968 の表示を期待したのですが、4966 4967 4968と表示されました。これはなぜでしょうか。1655は行を表しています。 <br>コードの一部しか公開できなくてすいません。 <br>

ナナ 2021年10月01日 15:20

上で配列を間違えたので直しましたが、まったく違う数値がでます。

ナナ 2021年10月06日 18:21

<br>>質問ですが、glBegin() glNormal3f(,,);glVertex3f(,,);glVertex3f(,,);glVertex3f(,,) glEnd();と>glNormal(,,)ひとつにつきglVertex3f(,,)3つではないのでしょうか。 <br>挑戦しましたが失敗しました。 <br> glTexCoord2fv(texc[i0]); <br> glNormal3fv(norm[i0]); <br> glVertex3fv(vert[i0]); <br> <br> glTexCoord2fv(texc[i1]); <br> glNormal3fv(norm[i1]); <br> glVertex3fv(vert[i1]); <br> <br> glTexCoord2fv(texc[i2]); <br> glNormal3fv(norm[i2]); <br> glVertex3fv(vert[i2]); <br>は正しい構文でしょうか。

ナナ 2021年10月08日 15:41

すいません。補足ですがほとんどの場合、 printf("%f\n",3*a[0]-3);printf("%f\n",3*a[0]-2);printf("%f\n,3*a[0]-1);のように-3,-2,-1のようにしています。

ナナ 2021年10月08日 15:44

添え字をです。

ナナ 2021年10月08日 15:47

v[3*a[0]-3],v[3*a[0]-2],v[3*a[0]-1]のようにしました。

ナナ 2021年10月18日 19:39

glEnable(GL_NORMALIZE);glBegin(GL_TRIANGLES);の語順が間違えていたため、直したところ法線が反映されて光が反射するようになったのですが、全体がすべて三角形で作られているようになっていました。どうすれば全体をスムーズにできるでしょうか。今までの私の構成には、間違いがあるかもしれません。すいませんでした。


編集 «GL_CLAMP_TO_EDGE, GL_CLAMP_T.. 最新 Vertex Buffer Object»