床井研究室

卒研発表だー

もう大変なことになってますけど、とにかくなんとかなりそうです。よかったよかった。まだ終わってないけど。発表練習だけ聞いてると、まともな研究をしているように聞こえる! いや、ちゃんとまともな研究をしてるんだけどね、みんな。卒業研究/卒業論文無用論 (博士前期ですらいらんという話もでてるけど), 私は卒業研究やってる学生さんを見るのが好きだなぁ。頭かきむしりながら、答えが用意されていない問題に取り組むあたり。自分で問題を見つけて、自分なりの解決法を考えるっていう楽しみ (苦しみ?) は、どこかに用意されている答えを見つけることでは得られんよね。

ビルボード処理

というわけで、小ネタ。今日、学生さんがつぶやいてたことに対する、私のボヤキに補足します。たとえば、次のようにして図形が表示されていたとします。

static void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // モデルビュー変換行列の初期化
  glLoadIdentity();

  // 視点の移動
  gluLookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  // グリッドの描画
  grid();

  // オブジェクトの描画
  quad();

  glFlush();
}

これで、たとえば次のような図形が表示されたとします。quad() は xy 平面上に四角形を描きます。

ビルボード処理前

この quad() を描画する時点のモデルビュー変換行列を取り出し、その左上の 3x3 要素を単位行列にします。

static void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // モデルビュー変換行列の初期化
  glLoadIdentity();

  // 視点の移動
  gluLookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  // グリッドの描画
  grid();

  // モデルビュー変換行列の保存
  glPushMatrix();
  {
    // モデルビュー変換行列の操作用
    GLdouble m[16];

    // 現在のモデルビュー変換行列を取り出す
    glGetDoublev(GL_MODELVIEW_MATRIX, m);

    // 左上 3x3 要素を単位行列にする
    m[0] = m[5] = m[10] = 1.0;
    m[1] = m[2] = m[4] = m[6] = m[8] = m[9] = 0.0;

    // 書き換えた行列を書き戻す
    glLoadMatrixd(m);

    // オブジェクトの描画
    quad();
  }
  // モデルビュー変換行列の復帰
  glPopMatrix();

  glFlush();
}

すると、四角形がこっち向きます。「ハーイ!」って感じで。

ビルボード処理後

まあ、これはスクリーンに対して平行にしただけですし、モデルビュー変換行列がスケーリングを含んでないとか (スケーリングを含むときは左上 3x3 要素からスケールファクタを抽出する必要があります) 素直な場合に限りますけど、なんでこういうことになるかは変換行列がどういう形をしてるか調べればわかります。

視点側に向ける

ちゃんと視点側を向くビルボード処理をするなら、オブジェクトのローカル座標系の原点の視点座標系での位置 $(m[12], m[13], m[14])^T$ を z 軸にして基底ベクトルを求める必要があります。$m$ を次のように設定します (2月22日ポリゴンが裏返ってたので修正)。

    // モデルビュー変換行列の操作用
    GLdouble m[16], l;

    // 現在のモデルビュー変換行列を取り出す
    glGetDoublev(GL_MODELVIEW_MATRIX, m);

    // Z 軸
    m[8] = -m[12];
    m[9] = -m[13];
    m[10] = -m[14];
    l = sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]);
    m[8] /= l;
    m[9] /= l;
    m[10] /= l;

    // X 軸 = (0, 1, 0) × Z 軸
    m[0] = -m[14];
    m[1] = 0.0;
    m[2] = m[12];
    l = sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]);
    m[0] /= l;
    m[1] /= l;
    m[2] /= l;

    // Y 軸 = Z 軸 × X 軸
    m[4] = m[9] * m[2] - m[10] * m[1];
    m[5] = m[10] * m[0] - m[8] * m[2];
    m[6] = m[8] * m[1] - m[9] * m[0];

    // 書き換えた行列を書き戻す
    glLoadMatrixd(m);

これはオブジェクトが視点の真上や真下にある ($m[12]$ と $m[14]$ がともに 0 になる) と具合の悪いことになりますが、そんなところにあるものは多分見えません。

ほんとにこれでいいんだっけ?