トップコンテンツプログラムノベルゲーム>画像の表示(応用編)



画像の表示 応用編

画像表示についての説明の 2 ページ目です。
本当は早く文字表示についてのページを書いてしまいたかったため、最初は「画像の表示」の説明を 1 ページだけで終わらせるつもりでした。
ですが、今まで説明していなかったこと, 画像の表示に関する最低限必要な技術(個人的に)の紹介について、今まで全く書いていなかったので、ここでページを割いて説明します。
今回は説明を少し多めに、1 つサンプルプログラムを載せるだけなので、前のページよりは短くなると思います。


座標

これまで説明していなかった(忘れてたとも言う)事柄の中でかなり大切なことの 1 つが、「座標」についてです。
前回までのプログラムで頂点の座標を指定した時、何気なく(0.0, 1.0), (-1.0, 0.0)といった数値を使っていましたが、なぜその数値を使うとああいった結果になるのかをこれから説明します。

OpenGL では、最初からウィンドウ上の座標を指定して立体や画像オブジェクトを配置するのではなく、オブジェクトごとに専用の空間と座標系を用意した後、いくつかの段階を経て、最終的に共通の座標系(ウィンドウ)へと投影する仕組みをとっています。
わざわざ個別の座標系を用意して、なんて煩雑なことをやっているのはもちろん理由があります。
特定の座標系を使ってオブジェクトを配置すると、物体を座標系内で移動させるたびに、形状を定義し直す必要が出るからです。。
「画像などのオブジェクト=コンピュータ上のデジタルデータ」だから、まとまったデータのコピー, 移動などといった作業はコンピュータの得意分野ですからね。
さて、下にオブジェクトの座標が与えられてからウィンドウ上に投影されるまでの流れ(「ビューイングパイプライン」)を書きます。

  • モデリング座標系(オブジェクトの形状データを個別に保存しておく)
    ↓モデルビュー行列による変換
  • ワールド座標系(ただ 1 つ存在する共通の座標系)
    ↓視野変換
  • カメラ座標系(作成されたジオラマの見る方向、見る時の立ち位置などを調整するイメージ)
    ↓射影変換
  • x, y, z の値域がビューボリューム領域内に制限された座標系
    ↓ビューポート変換
  • ウィンドウ座標系(指定したウィンドウ上の場所に画像が描画される)

モデリング」「射影変換」といった内容について、残念ながら“自分”の知識では詳しく書けないので、真面目に勉強したい方は CG についての参考書(CG-ARTS 協会から出されてる教科書とか?)を読むことをオススメします。

glVertex で与えているのは、オブジェクトの形状データなので、モデリング座標系上での座標です。
同じように glTexCoord も、テクスチャの形状データが定義されているテクスチャ座標系はモデリング座標系の仲間なので、モデリング座標系上の座標を指定していることになります。
ちなみに前々回, 前回のプログラムでは「視野変換」, 「射影変換」, 「ビューポート変換」の設定を全くしていません。
まあ、テクスチャ座標系については少し別物なので省くとして、それでは glVertex に与えた数値の -1.0~1.0 という数字はどこから来たのか?
これはビューボリュームの初期値が、x: -1.0~1.0, y: -1.0~1.0, z: -1.0~1.0、となっているからです。
ビューボリューム(2 次元)
上の画像はビューボリュームの面(2 次元)を見たものです。すでに作ってしまったので一応貼っておきます。
また、ビューポートも、初期設定では描画範囲がウィンドウ全体となっています。
glVertexでの(0.0, 0.0)の位置がウィンドウの真ん中になったり、(1.0, 1.0)がウィンドウの右上隅になったりしたのはそのためです。
当然、ビューボリュームやビューポートの設定を変えてやれば、座標値の有効範囲が変わるので、同じ(0, 0)という座標を glVertex で与えても、ウィンドウ上の位置は変わってしまいます。
後、ここに 射影・ビューポート変換などについて説明された pdf 資料が置いてあったので、こちらも見てみるといいかもです。
なお、ビューイングパイプラインについて書いてあるのは前半だけで、後半以降は3次元物体のモデリングに関する話になっていますが。

あともう 1 つ、座標関連で注意しなければいけないことがあります。
画像座標とOpenGLでのテクスチャ座標の違い
上の画像の例を見ればわかる通り、ファイルに格納されている時点での画像の座標とテクスチャ座標系とで、例えば(4, 4)のピクセルと(1.0, 1.0)のピクセルは色が異なっています。
そのまま「画像ファイル内の座標系の点A(x, y) = テクスチャ座標系の点A'(x/4, y/4)」という関係にはなっていませんよね?
「A(x, y) = A'(x/4, -y/4)」となっています。

画像ファイル内での座標とテクスチャ座標系での座標の対応関係を知っておかないと、「なぜかテクスチャ画像を貼ると上下が反転する」といった現象の原因が特定できません。
次のページの「文字の表示」で文字を表示する際、何も考えずにテクスチャを作成・貼付をすると、文字が上下反転した状態で表示されてしまうので注意が必要です。
確か、テクスチャ作成時の設定でも、左右・上下反転は出来た気はするが。


サンプルプログラム

上の説明がかなり長くなってしまいました。こんなはずではなかったのに……。
ともかく。今度は、実際にサンプルプログラムを書いてみて、ビューボリュームやビューポート変換の設定を変えてみるとどうなるかを確かめてみましょう。
ついでに、アルファ値を用いた半透明色の表示などもやってみます。


gl4_1.c (テクスチャ画像に半透明化などを施す) [表示・非表示]
 1.
 2.
 3.
 4.
 5.
 6.
 7.
 8.
 9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>

/* 画像についての情報を保存しておく変数・定数 */
#define  IMGFILE  "imagetest1.bmp"
#define  CHANNEL  3  // 画像の色数(R, G, B)
int width;           // 画像の幅
int height;          // 画像の高さ
GLuint texture;      // テクスチャの識別番号
GLubyte* image;      // ビットマップ画像データを格納

/* 関数のプロトタイプ宣言 */
void loadImage(const char* filepath);  // BMP 画像を読み込む


void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT);

  glEnable(GL_BLEND);
  glEnable(GL_TEXTURE_2D);

    /* 画像の表示(透明度0.5倍 ver.) */
    glViewport(0, 0, 800, 300);  // ウィンドウ下半分に写像
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glColor4f(1.0, 1.0, 1.0, 0.5);
    glBindTexture(GL_TEXTURE_2D, texture);
    glBegin(GL_QUADS);
      glTexCoord2f(1.0, 0.0);  glVertex2f(1.0, 0.0);
      glTexCoord2f(1.0, 1.0);  glVertex2f(1.0, 1.0);
      glTexCoord2f(0.0, 1.0);  glVertex2f(0.0, 1.0);
      glTexCoord2f(0.0, 0.0);  glVertex2f(0.0, 0.0);
    glEnd();
    glBlendFunc(GL_ONE, GL_ZERO);  // 初期値に戻す

    /* 画像の表示(赤成分だけを抽出) */
    glViewport(0, 300, 800, 300);  // ウィンドウ上半分に写像
    glColor3f(1.0, 0.0, 0.0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glBegin(GL_QUADS);
      glTexCoord2f(1.0, 0.0);  glVertex2f(2.0, 1.0);
      glTexCoord2f(1.0, 1.0);  glVertex2f(2.0, 2.0);
      glTexCoord2f(0.0, 1.0);  glVertex2f(1.0, 2.0);
      glTexCoord2f(0.0, 0.0);  glVertex2f(1.0, 1.0);
    glEnd();

  glutSwapBuffers();
}

/* 個々のプログラム独自に行なうべき初期化 */
void myInit(void)
{
  /* ウィンドウ生成前に行なうべき設定 */
  glutInitWindowSize(800, 600);                  // ウィンドウサイズの設定
  glutInitWindowPosition(200, 200);              // 表示位置の設定
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);  // ディスプレイモードの設定
  glutCreateWindow("OpenGL");                    // ウィンドウを生成

  /* イベント発生時に呼び出される関数の登録 */
  glutDisplayFunc(display);                   // 描画関数(自分自身で定義)を登録

  /* 投影方法の設定 */
  glOrtho(0.0, 2.0, 0.0, 2.0, 0.0, 2.0);  // 平行投影

  /* テクスチャの作成 */
  loadImage(IMGFILE);   // 画像の読み込み
  glPixelStorei(GL_UNPACK_ALIGNMENT, 8);
  glGenTextures(1, &texture);
  glBindTexture(GL_TEXTURE_2D, texture);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height,
               0, GL_RGB, GL_UNSIGNED_BYTE, image);
  free(image);
}

int main(int argc, char* argv[])
{
  /* 初期化 */
  glutInit(&argc, argv);
  myInit();

  /* イベント待機状態に入る */
  glutMainLoop();

  return 0;
}


/* BMP画像の場所を引数にとり、image配列に画像データを格納していく */
void loadImage(const char* filepath)
{
  ~省略(こちらのプログラム下部を参照)~ 
}

実行結果はこちら。


だんだんとソースコードの分量が長くなって来ましたね。
ヘッダファイルとかに define とか include をまとめるなどして、少しでもソースコードを分割した方がいいのだろうか…。
それはともかく、太字で示した関数の解説を始めます。

このプログラムで行なっていることで重要なのは、glColor 関数によってテクスチャ画像の RGBA 成分の割合を調整できてしまう、ということだと思います。
glDrawPixels 関数の場合では、“自分”が試した限りでは同じことが出来なかったです。
まあ、元の画像が 24bit(RGB カラー)であっても、アルファブレンド処理が出来る、というわけです。
もしくは、公式通りに上手に調整すれば、輝度を調節する処理も可能なはず……。

最後に、このページで紹介した関数の一覧を。


5. 文字の表示 その1 へ