はじめに
この記事は筑波大学情報メディア創成学類「プログラミング実習II」履修者向けの記事です。
授業で配布されたサンプルTextureImageAnimation
を改変して透過画像を表示できるようにする方法を解説しています。
効果音を付ける方法の記事も書きました。よければこちらもどうぞ
全学計算機でC言語のプログラムから音を出す
透過画像とは?
1つ目が透過していない画像、2つ目が透過処理された画像です。
いわゆるBB画像ですね。今回の例では透明部分を完全な青(R=0,G=0,B=255)としています。
RGB
が並ぶデータにA
(Alpha. 不透明度)の値を挿入し、RGBA
という並びのデータに変換することで、ピクセルを透明にすることができます。
サンプルコードを書き換える
書き換えは非常に簡単で、2行のコードを追加した上で、関数を一つまるっと置き換えるだけで画像を透過できるようになります。
テクスチャの合成を有効にする
まず、以下の二行をプログラムの初期化処理に加えてください。(授業のサンプルコードではmain.c
のinit()
関数の中が適当と思われます)
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
この二行がないと画像の透過処理が行われず、枠付きの画像しか表示できません。
画像データに不透明度を加え入れる
TextureImage.c
の MakeTextureFromImage()
関数を以下のように書き換えてください。書き換えというか置き換えるだけですが。
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
が並んだデータと交換する必要があります。
RGBA
のA
とはAlphaチャンネル(不透明度)のことであり、0から255までの値をとります。0は完全に透明、255は完全に不透明を意味します。
元のデータはRGB
のみで不透明度の情報は含まれていないので、「ある色のピクセルは透明である」という風に決めます。
今回はR = 0, G = 0, B = 255
(青)のピクセルのA
の値を0
に、それ以外のピクセルのA
は255
にすることで透明・不透明の部分を分けています。
おまけ - パラパラ漫画のようなアニメーションをするには
以下のような、スプライト画像と呼ばれる、コマをつなげた画像を用意して、表示する領域を順番に切り替えるとパラパラ漫画のようなアニメーション(スプライトアニメーション)ができますよ(小声)