・・・ぶっちゃけ、テクスチャってけっこうめんどくさいです。
が、まぁ、使えると使えないとでは、見た目がかなり違ってくるし、
いちど使えるようになれば、
あとはオブジェクト指向の利を生かして
テクスチャ用のクラスとしてまとめ込んでしまえばたやすいモノ(に、なるはず)。
と、いうことで、ちょっとここらで頑張りましょうかね。
そうそう、
ここでの説明は、基本的にC#でOpenGLのテクスチャを作成する手順にのみ
焦点を当てていますので、
始めに、あらかじめこれに先立って、
他のサイトなどで資料やサンプルプログラムを見て
OpenGLのテクスチャ生成の手順を確認しておくことを、おすすめします。
(必ずしも、とはいいませんが。)
さて、
まずは、テクスチャ生成の手順をおおざっぱに確認。
こんな感じです。
OpenGLでは特定の形式の画像の読み込みはサポートされていないので、
bmp、jpg、pngなどの形式の画像をテクスチャなどとして使う場合には、
画像を読み込むための手段を、別途にDLLなどとして用意する必要があり、
結構面倒だったらしい(やったこと無いのでわかりませんが・・・)。
しかし、.net Frameworkでは、Bitmapクラスとして標準で
bmp、jpg、png、gif
これら4つの形式の画像ファイルを読み書きするための手段が用意されていているので、
C#ではとても簡単に扱うことができます。
つまり、C#+OpenGLでテクスチャを使おうとした時に、
これらの手間をぐっと省いて簡単に画像ファイルからテクスチャを作成できる、
ということになります。
では、
Bitmapクラスを使って、画像ファイルを読み込みます。
Bitmap bmp = new Bitmap( "texture.png" );
これだけ。
まぁ、この辺は特に問題ないはず。
OpenGLでは、画像を扱うための特定の形式は用意されておらず、
1.で作成したbmpを直接渡すことはできません。
OpenGLでは、画像データはもっぱらbyte[]型の配列などとして扱うことになります。
そこで、bmpの画像データをbyte[]型に移し替えます。
BitmapクラスにはGetPixelという、画像の色を取得するメソッドが用意されていますが、
しかし、これを使うと処理が非常に遅くなってしまうので、
bmpがメモリ上に持っている画像データを直接コピーします。
そのためには、
Bitmap.LockBits
System.Runtime.InteropServices.Marshal.Copy
という、2つのメソッドを使うことになります。
ところが、です。
このBitmapクラス、画像のピクセルデータを
左上から右方向に順に並だ状態で持っているのですが、
OpenGLでは、画像は左下から右方向に順に並んでいるものとして扱うことになっています。
なので、 このまま作業を続けていくと最終的に
上下逆さまのテクスチャができあがってしまいます。
そこで、始めのうちにBitmapクラスに用意されている便利なメソッド
Bitmap.RotateFlip
を使ってさっさと上下を反転させておきます。
ま、あらかじめ上下ひっくり返った画像を用意しておくとか、
上下ひっくり返ったままにしておいて、
テクスチャを参照するときの座標のほうを上下ひっくり返すとか、
いくつか方法があるのですが、
今回は、一番わかりやすいこの方法ということで。
//画像の上下を反転。 bmp.RotateFlip( RotateFlipType.RotateNoneFlipY ); //bmpが持っている画像データをメモリ上に固定。 //読み取り専用・色要素は32ビットARGB(4色・各8ビット)。 BitmapData bmpData = bmp.LockBits( new Rectangle( 0, 0, bmp.Width, bmp.Height ), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb ); //画像データを格納するための配列。 //配列の長さは、(幅)*(高さ)*(色要素の数)。 byte[] imageByteArray = new byte[ bmp.Width * bmp.Height * 4 ]; //画像データのコピーをおこなう。 System.Runtime.InteropServices.Marshal.Copy( bmpData.Scan0, imageByteArray, 0, imageByteArray.Length ); //メモリ上の固定を解除する。 bmp.UnlockBits( bmpData );
「画像データをメモリ上に固定」というのは、
C#ではガーベジコレクタなるモノが裏で動いているから必要になってくる作業らしいが、
詳しいことは省略。
よくわからない場合は、まぁ、とりあえずこういうもんだと思っておいて下さい。
・・・ここで、まためんどくさいのが、ちょっと、ありまして。
このままでは配列内の色要素の並び方が、「B, G, R, A, B, G, R, A ・・・」となっている
(Bitmapの画像データの並びがそうなってるので)のですが、
OpenGLでは「R, G, B, A, ・・・」という並び方が一般的なので、色要素の並び替えをしておきます。
つまり、各ピクセルのRとBを入れ替えれば良いわけです。
byte r, b; int n; for( int i = 0; i < bmp.Width * bmp.Height; ++i ) { n = i * 4; b = imageByteArray[n]; r = imageByteArray[n + 2]; imageByteArray[n] = r; imageByteArray[n + 2] = b; }
これで、各ピクセルのRとBが入れ替わります。
ちなみに、bmp.GetPixelメソッドで1ピクセルずつ読み取って、配列の各要素に代入していく、
という方法も可能ですが、処理が非常に遅いので、おすすめしません。
*上のコードにもあるように、ここでは、画像を32ビットRGBA(4色・各8ビット)として扱います。
アルファ値はいらないからRGB(3色・各8ビット)でよい、という場合は、
bmp.LockBits( ... );
の、3つめの引数を「Format24bppRgb」に、
色要素の数は3つ、
後で出てくるGL_RGBAをGL_RGBに変えます。
float log_width = Math.Log( bmp.Width, 2 ); float log_height = Math.Log( bmp.Height, 2 );
//スケーリング後の画像のサイズ。 //条件に合うように拡大する。 int newWidth = (int)Math.Pow( 2, (int)log_width + 1 ); int newHeight = (int)Math.Pow( 2, (int)log_height + 1 ); //スケーリング後の画像を格納する配列 byte[] scaledImage = new byte[newWidht * newHeight * 4]; //画像のスケーリング。 gluScaleImage( GL_RGBA, width, height, GL_UNSIGNED_BYTE, imageByteArray, newWidth, newHeight, GL_UNSIGNED_BYTE, scaledImage ); //スケーリングされた画像と入れ替える。 imageByteArray = scaledImage;
//OpenGLから割り当てられる識別番号を保持しておく変数。 uint[] name = new uint[1]; //テクスチャオブジェクトの作成。 glGenTextures( 1, name ); //テクスチャのバインド。 glBindTexture( GL_TEXTURE_2D, name[0] ); //メモリ上の画像データの並び方を指定。 glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); //テクスチャへ画像データを割り当てる。 glTexImage2D( GL_TEXTURE_2D, 0, 4, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageByteArray ); //テクスチャパラメータの設定をお忘れ無く。 //最低限これをしておかないと、テクスチャが表示されない場合アリ。 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glDeleteTextures( 1, name );
[DllImport( GLUDLLName, EntryPoint = "gluScaleImage" )] public static extern void ScaleImage( int format, int widthin, int heightin, inttypein, byte[] datain, int widthout, int heightout,int typeout, byte[] dataout );
[DllImport( DLLName, EntryPoint = "glDeleteTextures" )]
public static extern void DeleteTextures( int n, uint[]textures );