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



画像の表示

図形の次は画像の表示でもしてみましょうか。
正直、早く文字表示について書いてしまいたいので、説明が少し駆け足になるかもしれません。
なので、適宜、必要な場合は別の資料も参考にしてみてください。
図形や画像表示についてなら、他のサイトでもそれなりに説明が書かれているので。


プログラム その1

「駆け足ぎみ」といいつつ、説明すべき事はたくさんあるんだよな……。
まあさておき。BMP 画像を表示するプログラムでも書きたいのですが、最初に BMP ファイルの扱い方について説明します。
ファイル入出力」、特に「バイナリファイル」についてあやふやな方は、別のサイトで勉強し直して下さい。

すでに知っている方もそれなりにいるかと思いますが、BMP ファイルとは画像形式の1つで、「ビットマップ画像」(格子状のマス目(ピクセル)に、色や濃度を割り当てた画像)を保存するためのファイル形式です。
イメージとしてはこんな感じですかね。

例) BMP ファイル内部での、色数 24 ビットのカラー画像の表現形式

BMP にも厳密に言うと 2 種類あるのですが、今回は 「Windows Bitmap」形式の BMP 画像を扱います。
一般的な BMP ファイルの場合、以下のようなファイル構造となっています。

  • ファイルヘッダ : 14バイト
  • 情報ヘッダ : 40バイト(一番多く使われる基本的な形式の場合)
  • カラーパレット : インデックスカラーを使用する場合のみ。今回は関係ない
  • 画像データ : (横×高さ×色深度) バイト

情報ヘッダには、新しいものだとガンマ値を格納しておけるファイルヘッダもあるようなので、もっと知りたい方はWikipediaなどを見てみると良いでしょう。
Windows Bitmap にはまだ細かい規約もあったりするのですが(横幅のピクセルは 4 で割り切れないといけない、とか)、今回は BMP ファイルの入出力がメインではないので、ファイルヘッダなどに関する内容は省略させてください。

画像の方はこちらで用意したものを使います。
テスト画像
幅 128px, 高さ 128px の、1 ピクセル辺り R, G, B 各色に8 ビット(0 ~ 255)の、合計 24 ビットで表現されている BMP 画像です。
これを OpenGL を使って表示します。


gl3_1.c (128 x 128の BMP 画像を表示) [表示・非表示]
 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.
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>

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

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


void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT);
  glClearColor (1.0, 1.0, 1.0, 1.0);

  /* 画像の表示 */
  glRasterPos2i(0, 0);
  glDrawPixels(width, height, GL_RGB, GL_UNSIGNED_BYTE, image);

  glutSwapBuffers();
}

/* 個々のプログラム独自に行なうべき初期化 */
void myInit(void)
{
  loadImage(IMGFILE);

  /* ウィンドウ生成前に行なうべき設定 */
  glutInitWindowSize(800, 600);                  // ウィンドウサイズの設定
  glutInitWindowPosition(200, 200);              // 表示位置の設定
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);  // ディスプレイモードの設定
  glutCreateWindow("OpenGL");                    // ウィンドウを生成

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

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

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

  return 0;
}


/* BMP画像の場所を引数にとり、image配列に画像データを格納していく */
void loadImage(const char* filepath)
{
  int i;
  int size;  // 画像のサイズ(幅 * 高さ * 色数)
  FILE* fp;

  /* ファイルの読込 */
  fp = fopen(filepath, "rb");
  if (fp == NULL)
    {
      fprintf(stderr, "%sのロードに失敗\n", filepath);
      exit(EXIT_FAILURE);
    }

  /* 画像の幅, 高さを取得 */
  fseek(fp, 18, SEEK_SET);  // 画像の幅が格納されている場所までfpを移動
  fread(&width, 4, 1, fp);
  fread(&height, 4, 1, fp);

  /* 画像データの読み込み */
  fseek(fp, 54, SEEK_SET);  // 画像データが格納されている場所までfpを移動
  size = width * height * CHANNEL;
  image = (GLubyte* ) malloc(size);
  if (image == NULL)
    {
      fprintf(stderr, "メモリ確保に失敗\n");
      exit(EXIT_FAILURE);
    }

  for (i = 0; i < size; ++i)
    fread(&image[i], sizeof(GLubyte), 1, fp);

  /* 作業が終わったのでファイルを閉じる */
  fclose(fp);
}

実行結果はこちら。

画像の表示(glDrawPixels)

ソースコードが長くなってしまいましたが、太字で示した関数の解説をしましょう。

  • void glRasterPos2[d, f, i, s] (TYPE x, TYPE y)
  • void glRasterPos3[d, f, i, s] (TYPE x, TYPE y, TYPE z)
  • void glRasterPos4[d, f, i, s] (TYPE x, TYPE y, TYPE z, TYPE w)
    ビットマップ画像の描画位置を設定する。glRasterPos4 は「同次座標」を用いる場合に使う。
    なお、x, y, z の値域は、初期設定では -1.0 ~ 1.0。

  • void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)
    メモリ上に置かれているデータから、画像を「フレームバッファ」(ディスプレイに表示する画像を一時的に保存しておくためのメモリ領域)に書きこむ。
    width, height はそれぞれ画像の幅と高さ, format は画像データ内の色情報の保存形式, type は色情報 1 つに割り当てられたデータ型, pixels は画像データが格納されている配列へのポインタを指定する。
    なお、type に GL_BITMAP を指定すると、白(1)か黒(0)の二値で画像が表現されていると解釈される。

    format で指定できる定数
    GL_COLOR_INDEXカラーインデックスを使用
    GL_RGB赤, 緑, 青の順番に格納
    GL_RGBA赤, 緑, 青, アルファの順番に格納
    GL_RED赤の情報のみ
    GL_GREEN緑の情報のみ
    GL_BLUE青の情報のみ
    GL_ALPHAアルファの情報のみ
    GL_LUMINANCE輝度の情報のみ
    GL_LUMINANCE_ALPHA輝度, アルファの順番に格納
    GL_STENCIL_INDEXステンシル値のみ
    GL_DEPTH_COMPONENTデプス(z 座標)の値のみ

    type で指定できる定数
    GL_UNSIGNED_BYTE符号なし 8 ビット整数型
    GL_BYTE符号つき 8 ビット整数型
    GL_BITMAP符号なし 8 ビット整数型(単独のビット値を使用)
    GL_UNSIGNED_SHORT符号なし 16 ビット整数型
    GL_SHORT符号つき 16 ビット整数型
    GL_UNSIGNED_INT符号なし 32 ビット整数型
    GL_INT符号つき 32 ビット整数型
    GL_FLOAT単精度の浮動小数点型

プログラム その2

「プログラム その1」では、glDrawPixels という関数を使用してフレームバッファに直接画像のピクセル値を書き込んでいました。
しかし、この方法だと画像データがフレームバッファに転送されるまでにいくつかの手順を間に挟むため、基本的に動作が遅いようです。
処理を速くするためには画像サイズを小さくする, 色数を少なくするなどの手法もありますが、もう 1 つ、特定のメモリ領域に画像データを保存しておくことで、フレームバッファに転送されるまでの段階をいくつか減らす方法があります。
それが「テクスチャ」というものを使用する方法です。

さて、何のためにテクスチャを使うのかは説明したと思うので、早速使ってみましょう。


gl3_2.c (128 x 128の BMP 画像をテクスチャを使って表示) [表示・非表示]
 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.
~インクルードファイル 変更なし~

/* 画像についての情報を保存しておく変数・定数 */
#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_TEXTURE_2D);
    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();
  glDisable(GL_TEXTURE_2D);

  glutSwapBuffers();
}

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

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

  /* テクスチャの作成 */
  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);
}

~main, loadImage関数 変更なし~

実行結果はこちら。
テクスチャを使って画像を表示

それでは、太字で示した関数を紹介していきます。

  • void glEnable(GLenum cap)
  • void glDisable(GLenum cap)
    cap で指定した機能を、glEnable で有効化, glDisable で無効化する。
    引数に使える定数の一覧はこちらを参照して下さい。

  • void glGenTextures(GLsizei n, GLuint* textureID)
    テクスチャ領域を割り当てるための関数。
    textureID 配列先頭から n 個分の要素に、確保したテクスチャ領域を指す識別番号を個別に与える。
    なお、0 は ID として予約されているため、glGenTextures が 0 を返すことはない。

  • void glBindTexture(GLenum target, GLuint textureID)
    target は常に、GL_TEXTURE_1D, または GL_TEXTURE_2D である。
    glBindTexture 関数の作業には 3 種類あり、未割当の ID が渡された場合は、新しいテクスチャ・オブジェクトが作成される。
    以前に作成したテクスチャを指す ID が渡された場合、そのテクスチャ・オブジェクトを使用可能にする。
    ID が 0 の場合はテクスチャオブジェクトの使用をやめて、初期設定のテクスチャに戻る。

  • void glPixelStorei(GLenum pname, GLint param)
  • void glPixelStoref(GLenum pname, GLfloat param)
    ピクセル格納の方法を設定する。
    pname にはパラメータの種類, param には値を指定する。
    この関数は、glDrawPixels, glReadPixels, glBitmap, glTexImage2D などの関数の処理に影響する。
    特に、2 の累乗で割り切れない解像度の画像を扱う場合、「アライメント」(データをメモリなどへ転送する際のデータサイズの単位)の設定が必要となることがあるので、注意してください。
    なお、アライメントはバイト数が大きいほど高速に動作ができます。

    glPixelStore 関数のパラメータに関する情報
    パラメータ名初期値有効範囲
    GL_(UN)PACK_SWAP_BYTESGLbooleanFALSETRUE/FALSE
    GL_(UN)PACK_LSB_FIRSTGLbooleanFALSETRUE/FALSE
    GL_(UN)PACK_ROW_LENGTHGLint00 を含む任意の自然数
    GL_(UN)PACK_SKIP_ROWSGLint00 を含む任意の自然数
    GL_(UN)PACK_SKIP_PIXELSGLint00 を含む任意の自然数
    GL_(UN)PACK_ALIGNMENTGLint41, 2, 4, 8

  • void glTexParamater[i, f](GLenum target, GLenum pname, TYPE param)
  • void glTexParamater[i, f]v(GLenum target, GLenum pname, TYPE* param)
    テクスチャの処理方法などに関する制御を行なうための、各種パラメータを設定する。
    target は、常に GL_TEXTURE_1D, または GL_TEXTURE_2D にする必要がある。
    pname, param に使用できる値は以下の表で示す。

    glTexParameter 関数のパラメータ
    パラメータ
    GL_TEXTURE_WRAP_SGL_CLAMP, GL_REPEAT
    GL_TEXTURE_WRAP_TGL_CLAMP, GL_REPEAT
    GL_TEXTURE_MAG_FILTERGL_NEAREST, GL_LINEAR
    GL_TEXTURE_MAG_FILTER GL_NEAREST, GL_LINEAR,
    GL_NEAREST_MIPMAP_NEAREST,
    GL_NEAREST_MIPMAP_LINEAR,
    GL_LINEAR_MIPMAP_NEAREST,
    GL_LINEAR_MIPMAP_LINEAR
    GL_TEXTURE_BORDER_COLOR0.0~1.0 内の任意の 4 つの値
    GL_TEXTURE_PRIORITY0.0~1.0

  • void glTexImage2D(GLenum target, GLint level, GLint interFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels)
    glBindTexture 関数によって現在有効となっているテクスチャ ID に、テクスチャを定義する。
    target は基本的に GL_TEXTURE_2D にする必要がある。
    level は、「ミップマップ」というものを使用する場合に定義するもので、テクスチャの解像度が 1 種類しかない場合は 0 にする。
    internalFormat は 画像データ内の色情報の保存形式を指定するもので、1~4 の整数, もしくは 38 個の定数のいずれかを選ぶ。internalFormat で指定できる定数についてはこちらを参照して下さい。
    width, height はそれぞれ、テクスチャに用いる画像の幅, 高さを指定する。
    border は、テクスチャの境界の幅(単位はピクセル)。境界線がなければ 0 にする。
    format は、色情報 1 つに割り当てられたデータ型, type は色情報 1 つに割り当てられたデータ型を指定する。
    そして pixels には、画像データが格納されている配列へのポインタを指定する。
    なお、format, type はそれぞれ、glDrawPixels における format, type と同じ値を使用できる。

  • void glTexCoord1[s, i, f, d] (TYPE s)
  • void glTexCoord2[s, i, f, d] (TYPE s, TYPE t)
  • void glTexCoord3[s, i, f, d] (TYPE s, TYPE t, TYPE r)
  • void glTexCoord4[s, i, f, d] (TYPE s, TYPE t, TYPE r, TYPE q)
    現在のテクスチャ座標(s, t, r, q)をセットする。
    テクスチャ左下の座標は(0, 0), 右上は(1, 1)である。
    通常使うテクスチャ座標の値は 0.0~1.0 だが、範囲外を指定した場合の動作は glTexParameter の「GL_TEXTURE_WRAP_S(T)」の設定に影響する。

上のサンプルプログラムその 2 でやっていることは、

  1. テクスチャ画像(長方形)の角の座標を 1 つ選択
  2. glVertex で、1. で選んだテクスチャ画像(長方形)の角と対応する点を選択
  3. 1, 2 を繰り返して、長方形の領域にテクスチャ画像を貼りつける

この手法を「テクスチャ・マッピング」といいます。
例えば、テクスチャ・マッピングによって、立体オブジェクトの面にテクスチャを貼りつけることも出来ます。
この項で紹介する内容からは外れるので説明はしませんが、気になる方は各自で調べてください。

なお、上の説明ではわかりにくいと思ったので、こんな画像を作って見ました。
テクスチャ座標と図形座標の対応関係
テクスチャ画像と長方形面どうしの関係図が掲載されているので、理解の助けになると思います。

後、重要なことが一つ。
テクスチャを生成するのは、ウィンドウを作成した後でないといけないようです。
このテストプログラムを作成する際、最初は glutCreateWindow の前でテクスチャ生成を行なう記述をしていました。
そのせいなのかテクスチャは生成されず、ただの白い長方形領域が表示されてしまい、「なぜだろう?」としばらく悩み続けていました。
原因は不明。freeglut か OpenGL の仕様が問題なんですかね?

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


4. 画像の表示 応用編へ