今回は前回のポリゴンに画像をペタ~リと貼り付けてやろうという算段です。
OpenGL ESで画像を描画する流れとしては
【画像読み込み】
↓
【テクスチャに画像を貼り付け】
↓
【ポリゴンにテクスチャを貼り付け】
という感じになります。
画像とはお馴染みの jpg や png ファイル形式のこと。
そのままの状態ではOpenGL ESでは使用できないのでOpenGL ES のテクスチャに貼り付けます。
そして作成したテクスチャをポリゴンに貼り付けることで画面に現れる仕掛けになっています。
早速テクスチャを作成しみようとぉ~~~ 思います。
前回はViewControllerにてポリゴンを描画したので
画像の読み込み、テクスチャの作成、描画もViewControllerにて行うことにします。
まずは描画したい画像をプロジェクトに追加。
形式、大きさは適当で。。
わたくしはわたくしが5分という長時間をかけて描いた超大作の一品、
『アーボッ子さん (32歳♀)』を描画します。
【アーボッ子.png 320x480pix 】
完璧な出来栄えに自分でさえ足がすくんだわ・・・
プロジェクトに画像を追加する時、
ちゃんと Copy ・・・ にはチェックを入れてプロジェクト内に画像を複製する。
追加した。
※名前は日本語でもok です。
次に、
ViewController.h にテクスチャを保持する変数を記述。
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
{
/** 画面のフレッシュレートと同期する為のクラス **/
CADisplayLink* mpDisplayLink;
/** 描画する? **/
bool mIsDraw;
/** テクスチャ **/
GLuint mTexture;
}
/**
* スイッチが押された
*/
- (IBAction)PressedSwitch:(id)sender;
/**
* ゲームループ
*/
- ( void )GameLoop;
@end
@interface ViewController : UIViewController
{
/** 画面のフレッシュレートと同期する為のクラス **/
CADisplayLink* mpDisplayLink;
/** 描画する? **/
bool mIsDraw;
/** テクスチャ **/
GLuint mTexture;
}
/**
* スイッチが押された
*/
- (IBAction)PressedSwitch:(id)sender;
/**
* ゲームループ
*/
- ( void )GameLoop;
@end
いつものごとく GLuint とは、ただの unsigned int なので
コイツ自体がテクスチャを持つわけではありません。
こいつが持っている識別子をたどることでテクスチャ領域にたどり着くことが出来るわけです。
ここからテクスチャを作成していく流れになります。
テクスチャを作成するのは一回でいいので
ViewController.m の ViewDidLoad 関数に記述していきます。
--------------------------------
① 画像の読み込み。
--------------------------------
これは UIImage を使わせてもらいます。
UIImageとはiPhoneプログラムでお馴染みの、画像を保持してくれる変数の型。
/** テクスチャを作成する **/
// 画像の読み込み
UIImage* pImage = [ UIImage imageNamed:@"アーボッ子.png" ];
// 画像の読み込み
UIImage* pImage = [ UIImage imageNamed:@"アーボッ子.png" ];
これで アーボッ子さんが pImage に保持されました。 簡単ですね。
--------------------------------
② 画像サイズを貰う
--------------------------------
こちらも簡単にできており、 UIImage の size を取得するだけでおkです。
ViewController.h に
/** 読み込んだ画像の大きさ **/
CGSize mImageSize;
CGSize mImageSize;
を追加して、
先ほどの読み込みの記述の下に
// サイズを調べてみる
mImageSize = pImage.size;
mImageSize = pImage.size;
を書く。
これだけで画像の幅高さが取得できます。
このサイズを利用してテクスチャを作成するわけなのですが
ここで鉄の掟が。
-------------------------------------------------------------------------------------------------------------------------
テクスチャは2のべき乗でなければならない。
-------------------------------------------------------------------------------------------------------------------------
この掟によりテクスチャを生成するときは
2, 4, 8, 16, 32, 64, 128, 256, 512,・・・・
という決められたサイズでしか生成できません。
今回のアーボッ子さんの大きさは 「320 x 480 」
2のべき乗ではありません。
普通ならば2のべき乗で作ってから読み込ませるのが定石ではありますが、
2のべき乗な都合のいい画像なんてそうそう転がっていませんし、
面 倒 くさい( ゚д゚ )
ならばプログラムで対処してやればいいじゃん。 ということです。
読み込んだ画像をプログラムで
2のべき乗の画像に作り直し,そこからテクスチャを生成すれば
無問題です。
--------------------------------
③ 画像を作り替える
--------------------------------
ViewController.h に変数を追加。
/** テクスチャの大きさ **/
CGSize mTexSize;
CGSize mTexSize;
こちらはテクスチャの大きさ( 2のべき乗 )を保持します。
.m の方で初期化。
// サイズを調べてみる
// 【注意】 : テクスチャは2のべき乗じゃないと作成できない!
mTexSize = mImageSize = pImage.size;
// 【注意】 : テクスチャは2のべき乗じゃないと作成できない!
mTexSize = mImageSize = pImage.size;
読み込んだ画像サイズが元々から2のべき乗である可能性は大いにあるので、
画像サイズが2のべき乗かどうか調べてみます。
そのための関数を作成 ↓
ViewController.m の上の方にでも書いて置く。
/**
* 引数が2のべき乗か調べる。
*/
static bool IsPowerOfTwo( int x )
{
return ( x & ( x - 1 ) ) == 0;
}
* 引数が2のべき乗か調べる。
*/
static bool IsPowerOfTwo( int x )
{
return ( x & ( x - 1 ) ) == 0;
}
中で行なっていることは、
引数と引数の数から - 1した数とをビット演算( AND )して
その結果が 0 ならば true を、 それ以外ならば false を返すようにしています。
2のべき乗を2進数で見てみると、
と、桁が1つずつ増えていく形になっています。
この法則と、
両方のビットが1の時、1を出力するANDの性質を使うことで
引数が2のべき乗なのか調べます。
試しに 32 が 2のべき乗かどうか上記の方法でを調べてみます。
見事に 0 になりました。
自分と自分の - 1の数のAND演算で 0 になるのは 2のべき乗だけです。
これで引数が2のべき乗か、
そうでないかを調べることが出来ます。
で、画像サイズが2のべき乗ではなかった時
このままではテクスチャを作成することは出来ません。
仕方ないので
自分のサイズより大きく、一番近い2のべき乗で
画像を作りなおしてからテクスチャを作成します。
まずは作りなおすサイズを調べる。
こちらはふつ~に2を掛け合わせて行き、元の画像サイズより大きくなった時点での数を返す関数を作ります。
/**
* 引数より上で一番近い2のべき乗を計算する
*/
static int Exponent( int num )
{
int r = 2;
while ( num > r )
r *= 2;
return r;
}
* 引数より上で一番近い2のべき乗を計算する
*/
static int Exponent( int num )
{
int r = 2;
while ( num > r )
r *= 2;
return r;
}
アーボッ子さん( 320 x 480 )では幅高さどちらも2のべき乗ではないので、
サイズを計算で求め( 512x512 )の画像を作成することになります。
↓ 今までの処理
/** テクスチャを作成する **/
// 画像の読み込み
UIImage* pImage = [ UIImage imageNamed:@"アーボッ子.png" ];
// サイズを調べてみる
// 【注意】 : テクスチャは2のべき乗じゃないと作成できない!
mTexSize = mImageSize = pImage.size;
// 2のべき乗?
if ( !IsPowerOfTwo( mImageSize.width ) )
mTexSize.width = Exponent( mImageSize.width );
if ( !IsPowerOfTwo( mImageSize.height ) )
mTexSize.height = Exponent( mImageSize.height );
// 画像の読み込み
UIImage* pImage = [ UIImage imageNamed:@"アーボッ子.png" ];
// サイズを調べてみる
// 【注意】 : テクスチャは2のべき乗じゃないと作成できない!
mTexSize = mImageSize = pImage.size;
// 2のべき乗?
if ( !IsPowerOfTwo( mImageSize.width ) )
mTexSize.width = Exponent( mImageSize.width );
if ( !IsPowerOfTwo( mImageSize.height ) )
mTexSize.height = Exponent( mImageSize.height );
これで mImageSize には画像本来のサイズが、
mTexSizeには2のべき乗のサイズが格納されます。
これを使い、
テクスチャ用に新しく2のべき乗で画像を作り直します。
iPhoneはこういう時に便利な関数、 CGBitmapContextCreate があります。
これは指定したサイズのビットマップ画像をメモリ上に作成してくれる超絶有り難い関数です。
使い方としては、
まず、2のべき乗画像サイズ分のメモリを取ります。
1ピクセルあたり "RGBA = 4バイト" 必要になるので
【 幅 x 高さ x 4 = 画像データサイズ 】となります。
アーボッ子さんでは 512x512x4 = 1,048,576 バイト
次に
ビットマップグラフィックスコンテキストを作成。
コンテキストとはOpenGL ES 初期化の時にも登場しましたが、
描画に必要な情報を保持してくれるありがたい入れ物です。
今回は ビットマップ専用のコンテキストを作成し、ビットマップに必要な情報を保持してもらいます。
先ほどのメモリをビットマップグラフィクコンテキスト作成時に指定します。
そしてこのコンテキストに対して画像を描画をすると
先ほど取得したメモリにピクセル情報が格納されます。
そのピクセル情報をテクスチャに貼り付けるわけです。
コンテキストに画像を描画するのは CGContextDrawImage という関数です。
UIImageが持っている画像情報を、指定したコンテキストに描画します。
今回の描画コンテキストはビットマップコンテキストなので
指定した範囲のビットマップにUIImageをぺたりとします。
範囲はアーボッ子さんの画像サイズです。
// ビットマップのデータを用意する
// 1ピクセルあたりRGBA = 4バイト必要
GLubyte* pImageData = ( GLubyte* )calloc( mTexSize.width * mTexSize.height * 4, sizeof( GLubyte ) );
// 描画先のグラフィックスコンテキストを作成
CGColorSpaceRef pColor = CGImageGetColorSpace( pImage.CGImage );
CGContextRef pImageContext = CGBitmapContextCreate( pImageData, mTexSize.width, mTexSize.height, 8, mTexSize.width * 4, pColor, kCGImageAlphaPremultipliedLast );
// コンテキストに書き込み = ビットマップに書き込み
CGContextDrawImage( pImageContext, CGRectMake( 0, 0, mImageSize.width, mImageSize.height ), pImage.CGImage );
// いらないものを削除する
CGContextRelease( pImageContext );
CGColorSpaceRelease( pColor );
// 1ピクセルあたりRGBA = 4バイト必要
GLubyte* pImageData = ( GLubyte* )calloc( mTexSize.width * mTexSize.height * 4, sizeof( GLubyte ) );
// 描画先のグラフィックスコンテキストを作成
CGColorSpaceRef pColor = CGImageGetColorSpace( pImage.CGImage );
CGContextRef pImageContext = CGBitmapContextCreate( pImageData, mTexSize.width, mTexSize.height, 8, mTexSize.width * 4, pColor, kCGImageAlphaPremultipliedLast );
// コンテキストに書き込み = ビットマップに書き込み
CGContextDrawImage( pImageContext, CGRectMake( 0, 0, mImageSize.width, mImageSize.height ), pImage.CGImage );
// いらないものを削除する
CGContextRelease( pImageContext );
CGColorSpaceRelease( pColor );
==================================================
calloc → malloc と同じ。 しかしコチラはメモリを全て0で初期化してくれる。
pImage.CGImage → UIImageのままでは細かい操作が出来ないので、
UIImageが持っているCGImageという画像情報を使用します。
==================================================
これで2のべき乗サイズで画像を作りなおせた・・・・・
とおもいきや、
ここで問題があります!
実は CGImage, bitmap にも座標系が存在し、これを正しく補強してあげなければ
描画位置がおかしくなってしまいます。
CGImage, bitmap 双方とも座標系は左下が原点.....
なのでそのまま bitmap に CGImage を描画すると
左下によったビットマップ画像が出来上がる
という恐ろしゅうことになります。
望んでいるのは左上にアーボッ子さんがよった画像です。
これではテクスチャに貼り付けても座標指定がめんどくさいです。
ならばということで CTM という iPhoneの救済処置を使います。
これは指定したコンテキストの原点を色々変更してくれる機能で
今回であればビットマップコンテキストの原点を少し上にあげてやれば問題無さそうです。
これを行うCTMが
『CGContextTranslateCTM』 関数。
指定したコンテキストの原点の位置を平行移動します。
指定したコンテキストの原点の位置を平行移動します。
今回であれば上の空白を埋めたいので、
Y 方向へ 512( bitmapの高さ ) - 320( CGImageの高さ ) = 192( 差分 ) ずらします。
// 左下からになってしまうため
CGContextTranslateCTM( pImageContext, 0.0, mTexSize.height - mImageSize.height );
CGContextTranslateCTM( pImageContext, 0.0, mTexSize.height - mImageSize.height );
これでアーボッ子さんが左上によった2のべき乗の画像が作成できました。
============================================
【本当に左上になったのかどうか確かめたい場合。。】
画像を描画したコンテキストを UIImage に変換し、
デスクトップにでも画像作っちゃいましょう。
CGImageRef pdI = CGBitmapContextCreateImage( pImageContext );
UIImage* pTes = [UIImage imageWithCGImage:pdI ];
NSData *data = UIImagePNGRepresentation( pTes );
NSString *filePath = [NSString stringWithFormat:@"/Users/ **ここはユーザー名** /DeskTop/アーボッ子Test.png" ];
if ( [data writeToFile:filePath atomically:YES] )
NSLog(@"生成されたよ。");
else
NSLog(@"なにかおかしいっすよ。");
UIImage* pTes = [UIImage imageWithCGImage:pdI ];
NSData *data = UIImagePNGRepresentation( pTes );
NSString *filePath = [NSString stringWithFormat:@"/Users/ **ここはユーザー名** /DeskTop/アーボッ子Test.png" ];
if ( [data writeToFile:filePath atomically:YES] )
NSLog(@"生成されたよ。");
else
NSLog(@"なにかおかしいっすよ。");
これを CGContextDrawImage の下にでも噛ませてやることで
UIImage から png 画像が簡単にデスクトップにできちゃいます。
============================================
あとはコレをテクスチャに貼り付けて、
さらにポリゴンにテクスチャを張り付ければ画面に出てくれるはずです。
長くなった・・・
↓ ViewController の今回書いた分
【 (URL) 20121227_ブログ.txt 】
(URL)・・・次回!!!