【プロ実II】openGLで透過画像(BB)を表示する


はじめに

この記事は筑波大学情報メディア創成学類「プログラミング実習II」履修者向けの記事です。
授業で配布されたサンプルTextureImageAnimationを改変して透過画像を表示できるようにする方法を解説しています。

効果音を付ける方法の記事も書きました。よければこちらもどうぞ
全学計算機でC言語のプログラムから音を出す

透過画像とは?


1つ目が透過していない画像、2つ目が透過処理された画像です。
いわゆるBB画像ですね。今回の例では透明部分を完全な青(R=0,G=0,B=255)としています。

RGBが並ぶデータにA(Alpha. 不透明度)の値を挿入し、RGBAという並びのデータに変換することで、ピクセルを透明にすることができます。

サンプルコードを書き換える

書き換えは非常に簡単で、2行のコードを追加した上で、関数を一つまるっと置き換えるだけで画像を透過できるようになります。

テクスチャの合成を有効にする

まず、以下の二行をプログラムの初期化処理に加えてください。(授業のサンプルコードではmain.cinit()関数の中が適当と思われます)

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

この二行がないと画像の透過処理が行われず、枠付きの画像しか表示できません。

画像データに不透明度を加え入れる

TextureImage.cMakeTextureFromImage() 関数を以下のように書き換えてください。書き換えというか置き換えるだけですが。

TextureImage.cの一部
int MakeTextureFromImage(TextureImage *tex, ImageData *img)
{
#if 0
  if ( ! (img->width == img->height && IsPowerOfTwo(img->width)) )
    {
      fprintf(stderr, "MakeTextureFromImage: image size must be power-of-two: %dx%d\n",
        img->width, img->height);
      return 0;
    } 
#endif

  if ( img->channels != 1 && img->channels != 3 )
    {
      fprintf(stderr, "MakeTextureFromImage: invalid channels: %d\n",
        img->channels);
      return 0;
    }

  glError();

  glGenTextures(1, &tex->texID);

  if (tex->texID == 0)
    {
      fprintf(stderr, "MakeTextureFromImage: texture cannot be created\n");
      return 0;
    }

  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);


  glBindTexture(GL_TEXTURE_2D, tex->texID);
  if (img->channels == 1)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, img->width, img->height, 0,
         GL_LUMINANCE, GL_UNSIGNED_BYTE, img->data);

  /* ===== 書き換え前のコードここから ===== */
  /*
  else if (img->channels == 3)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img->width, img->height, 0,
         GL_RGB, GL_UNSIGNED_BYTE, img->data);
  */
  /* ===== 書き換え前のコードここまで ===== */

  /* ===== 書き換え後のコードここから ===== */
  else if (img->channels == 3) {
    unsigned char *newImage;
    unsigned char *oldImage;
    unsigned char aR = 0, aG = 0, aB = 255; // 透過色指定. 青を透明にする.
    int i;

    oldImage = img->data;
    // RGBA4チャンネル分の領域を確保
    newImage = (unsigned char *)malloc(sizeof(unsigned char) * img->width * img->height * 4);

    if ( NULL == newImage ) {
      // 4チャンネル分の画像領域の確保に失敗したら諦めて普通のRGBのテクスチャを渡す
      perror("malloc");
      fprintf(stderr, "Failed to add transparency");
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img->width, img->height, 0,
         GL_RGB, GL_UNSIGNED_BYTE, img->data);
    } else {
      // RGBAの画像領域にRGBのデータをコピーしつつAも設定していく
      for (i = 0; i < img->width * img->height; i++) {
        newImage[4*i + 0] = oldImage[3*i + 0]; // Red
        newImage[4*i + 1] = oldImage[3*i + 1]; // Green
        newImage[4*i + 2] = oldImage[3*i + 2]; // Blue
        // Alpha(不透明度: 0から255まで. 0=完全に透明, 255=完全に不透明)
        // (R,G,B) == (0,0,255) ならアルファチャンネルを0(透明)にする
        newImage[4*i + 3] = ( oldImage[3*i]     == aR &&
                              oldImage[3*i + 1] == aG &&
                              oldImage[3*i + 2] == aB ) ? 0 : 255;
      }

      // img->dataをアルファチャンネル付きのデータに取り替える
      img->data = newImage;
      // アルファチャンネルなしのデータはもう要らないので解放
      free(oldImage);

      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img->width, img->height, 0,
                     GL_RGBA, GL_UNSIGNED_BYTE, img->data);
    }
  }
  /* ===== 書き換え後のコードここまで ===== */

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

  glBindTexture(GL_TEXTURE_2D, 0);

  glError();

  fprintf(stderr, "MakeTextureFromImage: texture created\n");

  return 1;
}

ポイントは以下の一文です。

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img->width, img->height, 0,
             GL_RGBA, GL_UNSIGNED_BYTE, img->data);

元のサンプルコードではGL_RGBと書いてあるのをGL_RGBAと書き換えます。

このglTexImage2D()という関数は、画像データ(img->data)をopenGLに渡す関数です。元のサンプルコードではimg->dataにはRGBの値が並んだデータが格納されているので、この関数が実行される前に、RGBが並んだデータをRGBAが並んだデータと交換する必要があります。

RGBAAとはAlphaチャンネル(不透明度)のことであり、0から255までの値をとります。0は完全に透明、255は完全に不透明を意味します。

元のデータはRGBのみで不透明度の情報は含まれていないので、「ある色のピクセルは透明である」という風に決めます。
今回はR = 0, G = 0, B = 255(青)のピクセルのAの値を0に、それ以外のピクセルのA255にすることで透明・不透明の部分を分けています。

おまけ - パラパラ漫画のようなアニメーションをするには

以下のような、スプライト画像と呼ばれる、コマをつなげた画像を用意して、表示する領域を順番に切り替えるとパラパラ漫画のようなアニメーション(スプライトアニメーション)ができますよ(小声)


64x32を縦に6枚つなげた画像です