OpenGLでPNGファイルを使う話
PNGファイルを読み込んで、OpenGLで描画する話
大体の手順
- zlibのダウンロード/コンパイル
- libpngのダウンロード/コンパイル
- VisualStudioにzlib/libpngを設定
- libpngを使ってPNGを読み込む
- 読み込んだPNGをOpenGLに登録
- 四角形に貼り付けて描画
OpenGLはデフォルトでPNGが読めない
そうです、読めないんです
なので、適当にライブラリを拾ってきてそれを使います
libpng
libpng
http://www.libpng.org/pub/png/libpng.html
PNGファイルを読み込むライブラリ
PNGの開発者が開発した公式なライブラリらしいです
ただ、このlibpngはソースコードのみが提供されており、
自分でコンパイルして利用する必要があります
libpngのコンパイル
libpngのダウンロード
バージョンが色々あるみたいですが、なんか1.6.18が安定版っぽいのでそれを落とします
ページのちょっと下あたりにある"Source code:"っていう項目のところから一番右の.zipをダウンロード
※まぁ.tar.xzとか.tar.gzが解凍できるツールを持ってる人はそれでもいいかと
libpngのコンパイル
...といきたいところですが、さっきのlibpngのページの先頭当たりにこんな記述が
libpng is available as ANSI C (C89) source code and requires zlib 1.0.4 or later
libpngは ANSI C (C89) のソースコードとして利用可能であり、そして、zlib1.0.4以降を必要とします
zlib1.0.4以降を必要とします
libpngの前に、zlibをダウンロードしてコンパイルしましょう
zlibのコンパイル
zlibは、データを圧縮/解凍するときに利用されるライブラリです
PNG形式では、zlibを画像データの圧縮に使用しているため、zlibが別途必要になるようです
zlibのダウンロード
zlib
http://www.zlib.net/
zlibの最新バージョンは1.2.8のようです
libpngの条件である1.0.4を満たしているため、これを使います
ページの真ん中辺りの"The current release is publicly available here:"からダウンロード
たぶんUSでもPick a mirrorでもどっちでも落とせます
※.tar.xzとか.tar.gzを解凍できる人は(ry
開発者コマンドプロンプト
開発者コマンドプロンプトは、VisualStudioのコマンドを利用できるように
あらかじめ設定が行われるコマンドプロンプトです
スタートメニューからMicrosoft Visual Studio 2012
→ Visual Studio Tools → VS2012の開発者コマンドプロンプトと辿ることで起動できます
zlibのコンパイル
以下開発者コマンドプロンプトで次のようにコマンドを打っていきます
# cdコマンドでzlibのフォルダへ移動 # 自分がダウンロードしたフォルダに合わせてパスは変更してください > cd D:\Temp\zlib-1.2.8 # zlibのコンパイル > nmake /f win32/Makefile.msc # ここからなんかだーーって出力されてコンパイルが自動で行われる # プロンプトが返ってきたらコンパイル完了 >
すると、zlibのフォルダにzlib.libというファイルが作られています
これがzlibのライブラリファイルで、これをVisualStudioに設定することで
zlibの機能が利用できるようになります
libpngのコンパイル
zlibが用意できたので、libpngのコンパイルを行います
zlibを所定の場所に移動
libpngはzlibに依存しているため、zlibをlibpngが分かる場所に置いておく必要があります
libpngのフォルダと同じ場所にzlibというフォルダを作り、
その中にヘッダやライブラリファイルを入れることで、zlibを認識することができるようです
また、zlibフォルダの中に入れるファイルは
- zconf.h
- zlib.h
- zlib.lib
の3つだけでいいようです
コンパイル
開発者コマンドプロンプトで次のように実行
# libpngのフォルダへ移動 # 自分がダウンロードしたフォルダに合わせてパスは変更してください > cd D:\Temp\lpng1618 # libpngのコンパイル > nmake /f scripts/makefile.vcwin32 # ここからなんかだーーって出力されてコンパイルが自動で行われる # プロンプトが返ってきたらコンパイル完了 >
すると、libpngのフォルダにlibpng.libというファイルが作られています
ここまででzlibとlibpngの用意ができました
あとはこれらのライブラリをVisualStudioに登録することで
PNGを読み込む関数を利用することができます
VisualStudioにzlibとlibpngを設定
ライブラリファイルのコピー
glut.libがあるフォルダと同じ場所に
- libpng.lib
- png.h
- pngconf.h
- pngdebug.h
- pnginfo.h
- pnglibconf.h
- pngpriv.h
- pngstruct.h
- zlib.lib
をコピーします
これらのファイルは、ダウンロードしてきたzlibとlibpngのフォルダの中に入ってるので、
それを探してきてコピーします
includeとライブラリの設定
次のようなコードをソースファイルの先頭に追加します
#include "png.h" #pragma comment(lib, "zlib.lib") #pragma comment(lib, "libpng.lib")
#pragma comment(lib, "zlib.lib")
は、「こういう名前のライブラリを使います」っていう宣言です
もちろん、pragma comment構文を使用せずに、プロジェクトの設定を用いてライブラリを設定してもいいです
libpngでPNGファイルを読み込む
やっとOpenGLでのPNGの読み込みです
PNGファイルからOpenGLのテクスチャを作成する関数を作成してみました
GLuint createTextureFromPNGFile(const char *filename) { FILE *fp; png_structp pPng = NULL; png_infop pInfo = NULL; int depth, colorType, interlaceType; unsigned int width, height; int rowSize, imgSize; unsigned int i; unsigned char *data; GLuint texture; //PNGファイルを開く fopen_s(&fp, filename, "rb"); if( !fp ) { fprintf(stderr, "createTextureFromPNGFile: Failed to fopen."); return 0; } //PNGファイルを読み込むための構造体を作成 pPng = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); pInfo = png_create_info_struct(pPng); //初期化 png_init_io(pPng, fp); //画像情報を読み込み //画像の幅、高さ、ビット深度、色の表現方法、インターレースの情報を取得する png_read_info(pPng, pInfo); png_get_IHDR(pPng, pInfo, &width, &height, &depth, &colorType, &interlaceType, NULL, NULL ); //RGBとRGBAのみに対応 if( colorType != PNG_COLOR_TYPE_RGB && colorType != PNG_COLOR_TYPE_RGBA ) { fprintf(stderr, "createTextureFromPNGFile: Supprted color type are RGB and RGBA."); return 0; } //インターレースは非対応 if( interlaceType != PNG_INTERLACE_NONE ) { fprintf(stderr, "createTextureFromPNGFile: Interlace image is not supprted."); return 0; } //1行のデータサイズと画像の高さから必要なメモリ量を計算して、メモリ確保 rowSize = png_get_rowbytes(pPng, pInfo); imgSize = rowSize * height; data = malloc(imgSize); //ピクセルの読み込み for( i = 0; i < height; i++ ) { png_read_row(pPng, &data[i * rowSize], NULL); } png_read_end(pPng, pInfo); //OpenGLテクスチャの作成 glGenTextures(1, &texture); //テクスチャを選択 glBindTexture(GL_TEXTURE_2D, texture); //テクスチャにPNGファイルから読み込んだピクセルを書き込む glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); //後片付け free(data); png_destroy_info_struct(pPng, &pInfo); png_destroy_read_struct(&pPng, NULL, NULL); fclose(fp); return texture; }
OpenGLでテクスチャを描画
テクスチャの読み込み
これはさっき作ったcreateTextureFromPNGFile関数で読み込みます
テクスチャの読み込みは一回でいいので、初期化関数の中にでも書いておけばOKです
texture = createTextureFromPNGFile("ship.png"); if( !texture ) { fprintf(stderr, "Failed to createTextureFromPNGFile\n"); }
ship.pngのところは、自分が実際に読み込みたいテクスチャの名前に変更してください
戻り値が0でなければ、読み込み成功です
テクスチャフィルタ設定
テクスチャを拡大・縮小したときに、どのように処理を行うかを設定します
これも一回だけ行えばいいので初期化関数の中にでも書いておけばOKです
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GL_TEXTURE_MAG_FILTERは拡大するとき、GL_TEXTURE_MIN_FILTERは縮小するときのフィルタを設定します
また、GL_LINEARは、線形補間を行うという意味です
GL_LINEAR以外にもGL_NEARESTがあり、これは補間を行わずに一番近いピクセルの色をそのまま使用するという意味です
GL_LINEAR | GL_NEAREST |
---|---|
ちなみに、フィルタを設定しないと環境によってはテクスチャが全く表示されないことがあるので
必ず設定しておいたほうが良いでしょう
これを設定してなかったばっかりに、テクスチャが出ないどうしてだろうって小1時間くらい悩まされた...
透過設定
PNGファイルは背景を透過させることができるため、半透明の処理をOpenGLで有効化しておきます
glBlendFuncは半透明の計算方法を設定しており、ここを変更すると加算合成とか乗算構成とか
そういったことも出来るようになります
今回は通常の半透明の計算を行います
//透過設定 glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
描画
いよいよテクスチャの描画です
通常OpenGLで四角形を描画するときは、こんな感じで描画しますよね
glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glVertex2f(-0.2f, 0.2f); glVertex2f(0.2f, 0.2f); glVertex2f(-0.2f, -0.2f); glVertex2f(0.2f, -0.2f); glEnd();
※glBeginでGL_TRIANGLE_STRIPじゃなくてGL_POLYGON使ってる人もいるかも知れないですが、
そこはあまり大きな違いではないので構わないのです
テクスチャは、この四角形に貼り付けるようなイメージで描画します
実際にテクスチャを貼り付けるソースコードは次のようなものです
glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2f(-0.2f, 0.2f); glTexCoord2f(1.0f, 0.0f); glVertex2f(0.2f, 0.2f); glTexCoord2f(0.0f, 1.0f); glVertex2f(-0.2f, -0.2f); glTexCoord2f(1.0f, 1.0f); glVertex2f(0.2f, -0.2f); glEnd(); glDisable(GL_TEXTURE_2D);
まず、描画を行う前に
glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture);
を書くことでテクスチャを有効にし、使用するテクスチャを設定します
次に、座標を設定します
glTexCoord2f(0.0f, 0.0f); glVertex2f(-0.2f, 0.2f);
今まで使っていたglVertex2fに加えて、glTexCoord2f関数でテクスチャ上の座標も設定します
※パソコンで描くのが面倒だった...
ここで、テクスチャは左上が(0.0, 0.0)、右下が(1.0, 1.0)である点が注意です
- 画面上の(-0.2, 0.2)はテクスチャ上の(0.0, 0.0)に対応
- 画面上の(0.2, 0.2)はテクスチャ上の(1.0, 0.0)に対応
- 画面上の(-0.2, -0.2)はテクスチャ上の(0.0, 1.0)に対応
- 画面上の(0.2, -0.2)はテクスチャ上の(1.0, 1.0)に対応
ということをglVertex2fとglTexCoord2fで指定します
最後に、
glDisable(GL_TEXTURE_2D);
でテクスチャを無効状態に戻して終了です
実際に実行すると、テクスチャが表示されます
完成ソースコード
#include <stdio.h> #include <stdlib.h> #include "glut.h" #include "png.h" #pragma comment(lib, "zlib.lib") #pragma comment(lib, "libpng.lib") GLuint texture; GLuint createTextureFromPNGFile(const char *filename) { //長いので省略 //「libpngでPNGファイルを読み込む」のソース見てね } void display(void) { glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0.0f, 0.0f); glVertex2f(-0.2f, 0.2f); glTexCoord2f(1.0f, 0.0f); glVertex2f(0.2f, 0.2f); glTexCoord2f(0.0f, 1.0f); glVertex2f(-0.2f, -0.2f); glTexCoord2f(1.0f, 1.0f); glVertex2f(0.2f, -0.2f); glEnd(); glDisable(GL_TEXTURE_2D); glFlush(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutCreateWindow(argv[0]); glutDisplayFunc(display); texture = createTextureFromPNGFile("ship.png"); if( !texture ) { fprintf(stderr, "Failed to createTextureFromPNGFile\n"); } glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //テクスチャサンプリング設定 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //透過設定 glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glutMainLoop(); return 0; }
終わり
半日くらいで調べて作ったので、たぶん色々間違ってると思います
過度な信用はしないでくださいね
なんか下手な実験のレポートよりも書くの大変だったかも