タイトルCocoaはやっぱり!出張版》8. OpenGLを使う(4)カテゴリーグラフィックス, Cocoa, 鶴薗賢吾のCocoaはやっぱり!出張版
作成日2002/2/19 14:1:42作成者新居雅行
NSOpenGLViewには、defaultPixelFormatというデフォルトのピクセルフォーマットを返すメソッドがあります。デフォルトで構わない場合は、これを使うと今までの部分を次の一文に置き換えられます。

self = [ super initWithFrame : frameRect
pixelFormat : [ NSOpenGLView defaultPixelFormat ] ];

 ★ NSOpenGLView : デフォルトのピクセルフォーマット
  [書式] + (NSOpenGLPixelFormat *) defaultPixelFormat
  [出力] 返り値 : デフォルトのピクセルフォーマットのインスタンス。

その後、カレントコンテキストをmakeCurrentContextメソッドで自分自身に変更して、glClearColorで背景色を設定しています。背景色をずっと変更しないということであれば、drawRect : の中ではなく、初期化時に実行する方が効率的ということで、こちらに移動しました。

[ [ self openGLContext ] makeCurrentContext ];
// カレントコンテキストを設定
glClearColor( 1.0, 1.0, 1.0, 1.0 ); // 背景色を黒にする

AppleのサンプルプログラムのSimple AppKitのコードを見ると、この部分をちょっと特殊な実装をしています。initGLというOpenGLの初期化を行うメソッドを定義して、これを一度だけdrawRect : メソッド内から呼ぶようになっています。

//プロジェクト:Simple AppKit、ソース:GLView.m、メソッド:initWithFrame :

- (id) initWithFrame: (NSRect) frameRect {
: 省略
processFunc = @selector( initGL ); // OpenGLの初期化メソッドを記憶しておく

return self;
}

initWithFrame : メソッド内でprocessFuncにinitGLを覚えさせておいて、drawRect : メソッドから実行して、nilに置き換えるという方法です。nilにすることで、一度だけ実行されるようになります。

//プロジェクト:Simple AppKit、ソース:GLView.m、メソッド:drawRect :

- (void) drawRect : (NSRect) rect {

if ( processFunc ) {
[ self performSelector : processFunc ]; // initGLを呼ぶ
processFunc = nil; // 一度だけ呼ばれるようにする
}
: 省略
}

描画メソッドから初期化メソッドを呼ぶというのは、ちょっと変則的な感じがします。このようにしているのは、drawRect : が、このビューに対しての描画環境が整った状態で呼ばれるためだと思われます。例えば、drawRect : が呼ばれたときには、このNSOpenGLViewのOpenGLのコンテキストはカレントになっているために、カレントのコンテキストの切り替えを意識しなくてよいというようなメリットがあります。そういった準備をCocoaのフレームワーク側でやってくれているため安全ということでしょう。

この記事では先程説明したOpenGLのコンテキストを切り替えて初期化するというスタイルを採用しています。この方法で問題ないかの裏はまだ取れていないのですが、問題は今のところ発生していません。もしかすると、[ self lockFocus ] 〜 [ self unlockFocus ] で初期化処理を挟むのが正解なのかもしれません。

■ 画像ファイルの表示
続いては、画像の表示の方法です。OpenGLで画像を表示する方法はいくつかありますが、ここでは、テクスチャーとして画像を読み込んで、四角形にテクスチャーをマッピングして表示することにします。この方法を採用すると、マッピングをした四角形を回転すると画像も一緒に回転しますし、四角形の透明度を変えることでフェーディングもできるようになります。

◎ テクスチャーの読み込み
まずは、画像の読み込み部分から。loadImageToTexture : というメソッドを作ります。画像のファイルパスを渡すとテクスチャーを作成して、OpenGLが管理しているテクスチャーのIDを返してくれるというものです。

以下のソースを見ると、前半は、Cocoaのフレームワークを主に使っていて、後半はOpenGLの関数を主に使っていることが分かります。前半では、NSImageに画像を読み込んで、OpenGLへそのビットマップデータを渡すための加工を行っています。特に、OpenGLのテクスチャーは、ピクセル数が ( 2のn乗 ) × ( 2のn乗 ) の値しか取れない ( nは整数 ) ということもあって、ここでは、固定的に512×512ドットに変形しています。

//ソース:MyOpenGLView.m、メソッド:loadImageToTexture :

#define TEX_SIZE 512 // テクスチャーのサイズ

- (GLuint) loadImageToTexture : (NSString*) imgPath {

NSImage* imgFile; // ファイルから読み込んだ画像
NSImage* imgTex; // テクスチャー用に変形した画像
NSBitmapImageRep* imgTexRep; // テクスチャーのビットマップ抽出用
GLuint texId; // テクスチャーID

// テクスチャーサイズのNSImageを作成 ( Cocoaを主に使用 )

imgFile = [ [ [ NSImage alloc ] initWithContentsOfFile: imgPath ]
autorelease ]; // ファイルから読み込み
imgTex = [ [ [ NSImage alloc ] initWithSize :
NSMakeSize( TEX_SIZE, TEX_SIZE ) ] autorelease ];
[ imgTex lockFocus ];
[ imgFile drawInRect : NSMakeRect( 0, 0, TEX_SIZE, TEX_SIZE )
fromRect : NSZeroRect
operation : NSCompositeSourceOver
fraction : 1.0 ]; // imgFileをimgTex内にテクスチャーサイズで描画
[ imgTex unlockFocus ];

imgTexRep = [ [ NSBitmapImageRep alloc ] initWithData :
[ imgTex TIFFRepresentation ] ];


// テクスチャーのセットアップ ( OpenGLを主に使用 )

glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
// テクスチャーのフォーマットを指定
glGenTextures( 1, &texId ); // 空のテクスチャーを作成
glBindTexture( GL_TEXTURE_2D, texId ); // 対象のテクスチャーを指定
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA,
TEX_SIZE, TEX_SIZE, // テクスチャーのサイズ (幅、高さ)
0, // テクスチャーの枠の太さ
[ imgTexRep hasAlpha ] ? GL_RGBA : GL_RGB,
GL_UNSIGNED_BYTE,
[ imgTexRep bitmapData ] ); // テクスチャーのビットマップを作成
glBindTexture( GL_TEXTURE_2D, 0 ); // 対象のテクスチャーをなしに指定

return( texId );

}

initWithContentsOfFile : メソッドを使ってimgFileというNSImageのインスタンスに画像ファイルを読み込んだ後、512×512ドットのNSImageのインスタンスimgTexを生成して、そこにimgFileを変形して描画しています。NSImageの中に何かを描画するには、描画先をlockFocusしてから描画系のメソッドを実行します。描画が終わったらunlockFocusします。

 ★ NSImage : 描画準備処理を行う
  [書式] - (void) lockFocus
  [備考] 描画前に実行すること

 ★ NSImage : 描画完了処理を行う
  [書式] - (void) unlockFocus
  [備考] lockFocusとペアで描画後に実行すること

NSImageを描画するには、drawInRect : fromRect : operation : fraction : を使っています。このメソッドの第1パラメータで描画先の矩形を指定できますので、これで変形が出来ます。第2パラメータのfromRect : のパラメータは、画像の部分切り出しの指定ですが、NSZeroRectを指定することで、画像全体の指定になります。

 ★ NSImage : 指定位置に指定透明度、指定合成方法で画像を表示する
  [書式] - (void) drawInRect : (NSRect) inRect
     fromRect : (NSRect) fromRect
     operation : (NSCompositingOperation) op
     fraction : (float ) delta
  [入力] inRect : 表示位置
     fromRect : 画像の切り出し範囲 ( NSZeroRectで全体を指定 )
     op : 合成方法
     delta : 透明度

‥‥‥‥‥‥‥この項、続く‥‥‥‥‥‥‥[鶴薗賢吾]‥‥‥‥‥‥‥
関連リンクCocoaはやっぱり!