«第12回 模様を付ける 最新 第14回 頂点座標の生成»

床井研究室

※このブログは遅くとも 2027 年 3 月に管理者の定年退職により閉鎖します (移転先は管理者本人共々模索中)

■ 2009年09月21日 [OpenGL][GLSL][ゼミ] 第13回 テクスチャ座標の生成

2010年01月08日 19:09更新

テーマソング

スーパーのオークワのテーマソングは思い出せても, イズミヤのテーマソングが思い出せないという現象が謎だったんですが, なんとなく原因が分かったような気がします. イズミヤのテーマソングは, 現在は「しあわせくるくる」という歌が主力なのだそうですが, 店内では「さあ, 歩き出そう〜」という歌い出しで始まる「明日へ吹く風」という歌をよく耳にします. この歌は穏やかなとてもいい歌で, 聞いていると気分が軽くなってきます. 気持ちよく買い物ができるという点では, 客のことを考えた非常に優れた歌だと思います. ただ, これは多分テーマソングとしては致命的だと思うのですが, 歌詞にイズミヤを特定するフレーズが一切出てこないために, この歌はイズミヤのテーマソングとして記憶に定着しにくいようです. それが「イズミヤのテーマソングどんなのだっけ?」に対して, つい「お元気で〜す〜かぁ〜」とオークワのテーマソングを歌ってしまう原因になっているように思います.

テクスチャ座標を生成する

このようにシェーダプログラムで細工することによって, 物体表面に模様を付けることができます. ただ, 前の例のように物体表面の空間中の座標値を使う方法では, 物体の表面に模様を貼付けるような表現を行うのに少し手間がかかります. そこで図形データを作成する際に, 緯度や経度のような物体の表面上の座標値も生成するようにします. ここでは, とりあえずこれをテクスチャ座標と呼ぶことにします.

生成するテクスチャ座標

球の形状データを生成する関数 solidSphere() において, 頂点情報に表面上の座標値を追加します. この関数は各自考えてもらっているはずですが, それを当てにして話をするのは非常に困難なので, 【解答例】をもとにします. この頂点情報を保持するデータ型 Position の要素数を 3 から 5 に変更します.

...
 
/* 頂点バッファオブジェクトのメモリを参照するポインタのデータ型 */
typedef GLfloat Position[5];
typedef GLuint Edge[2];
typedef GLuint Face[3];
 
...

次に, y 軸中心の回転角 ph を求めているところから, [0, 1] の範囲をもつパラメータ t を分離します.

GLuint solidSphere(int slices, int stacks, const GLuint *buffer)
{
  ...
  
  /* 頂点の位置 */
  for (int j = 0; j <= stacks; ++j) {
    float t = (float)j / (float)stacks;
    float ph = 3.141593f * t;
    float y = cosf(ph);
    float r = sinf(ph);

同様に, x 軸中心の回転角 th を求めているところから, [0, 1] の範囲を持つパラメータ s を分離します.

    for (int i = 0; i <= slices; ++i) {
      float s = (float)i / (float)slices;
      float th = 2.0f * 3.141593f * s;
      float x = r * cosf(th);
      float z = r * sinf(th);

この s, t を, Position 型の変数 position の追加した要素に代入します.

      (*position)[0] = x;
      (*position)[1] = y;
      (*position)[2] = z;
      (*position)[3] = s;
      (*position)[4] = t;
      ++position;
    }
  }
  
  ...

メインプログラムの関数 init() において, 追加した頂点情報を受け取る attribute 変数と, その index を指定します. attribute 変数の名前は texture とし, index は 1 にします.

...
 
/*
** 初期化
*/
static void init(void)
{
  ...
  
  /* attribute 変数 position の index を 0 に指定する */
  glBindAttribLocation(gl2Program, 0, "position");
  
  /* attribute 変数 texture の index を 1 に指定する */
  glBindAttribLocation(gl2Program, 1, "texture");
  
  /* シェーダプログラムのリンク */
  glLinkProgram(gl2Program);
  ...

描画の際に, attribute 変数 texture の index に対して, 頂点情報への対応付けを有効にします.

...
 
/*
** 画面表示
*/
static void display(void)
{
  ...
  
  /* uniform 変数 lightColor に光源の色を設定する */
  glUniform3fv(lightColorLocation, 1, lightColor);
  
  /* index が 0 と 1 の attribute 変数に頂点情報を対応付ける */
  glEnableVertexAttribArray(0);
  glEnableVertexAttribArray(1);
  
  /* 頂点バッファオブジェクトとして buffer[0] を指定する */
  glBindBuffer(GL_ARRAY_BUFFER, buffer[0]);

頂点バッファオブジェクトは Position 型の配列になっているので, Position 型を変更した場合は頂点バッファオブジェクトを参照している部分も変更する必要があります. 頂点情報のデータ型 Position は5つの要素を持つ GLfloat 型の配列なので, 頂点バッファオブジェクト上に並んだ頂点情報の間隔は sizeof (GLfloat) * 5 になります. これを glVertexAttribPointer() の第5引数 stride に設定します. さらに, index が 1 の attribute 変数 (texture) の要素数は 2 (vec2) なので, その第2引数は 2, またその先頭の要素の位置は GLfloat 型の3つの要素で表される頂点位置の次にあるので, 第6引数 pointer は GLfloat 型のポインタ 0 (NULL) に 3 を足したものになります.

頂点バッファオブジェクトの stride と pointer
  /* 頂点情報の格納場所と書式を指定する */
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof (GLfloat) * 5, 0);
  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof (GLfloat) * 5, (GLfloat *)0 + 3);
  
  ...

図形の描画が終わったら, attribute 変数と頂点情報の対応付けを無効にします.

  ...
  
  /* 頂点バッファオブジェクトを解放する */
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  /* index が 1 と 0 の attribute 変数の頂点情報との対応付けを解除する */
  glDisableVertexAttribArray(1);
  glDisableVertexAttribArray(0);
  
  /* 隠面消去処理を無効にする */
  glDisable(GL_DEPTH_TEST);
  
  glFlush();
}
 
...

最後にシェーダプログラムを変更します. バーテックスシェーダにおいて vec2 型の attribute 変数 texture を宣言し, それを varying 変数 t に代入します. ただし, 現在のフラグメントシェーダの処理では, そのまま代入したのでは図形が表示されないので, 16 倍くらいします (これは多分あとで元に戻します).

#version 120
//
// simple.vert
//
invariant gl_Position;
attribute vec3 position;
attribute vec2 texture;
uniform mat4 projectionMatrix;
varying vec3 diffuseColor;
uniform vec3 lightDirection;
uniform vec3 lightColor;
varying vec2 t;
 
void main(void)
{
  t = texture * 16.0;
  diffuseColor = vec3(dot(lightDirection, position)) * lightColor;
  gl_Position = projectionMatrix * vec4(position, 1.0);
}

フラグメントシェーダは変更しません. これで模様が図形の表面に貼り付いたような形になります. 下のような図形が描かれれば OK です.

表面上の位置を使って模様を付ける

編集 «第12回 模様を付ける 最新 第14回 頂点座標の生成»