MDOnlineのサイトのうち、mdonline.jp側ですが、すみませんが何か変なことがあったら遠慮なく教えて下さい。ちょっとまだ言えないアレを使ってみています。それで、PDFのダウンロードの部分ですが、MDOnlineのパスワードが必要になっています。昨日まではマスターとなるパスワードファイルを手作業で持ってきていますが、今日からは、cronで毎日1回、パスワードファイルのコピーをしますので、少なくとも、数日前に変更したパスワードはPDFのダウンロードでも利用できるでしょう。ちなみに、Linuxのcron.dailyにスクリプトを入れたので、午前4時にパスワードファイルのコピーをしてくるはずです。
さて、あと1か月でMacworld Expoですね〜。1か月とはいいながら、今月は2月なので、あと4週間です。2月は短いのですが、重要なのは3月が以外に早く押し寄せるということなんですよね。昨年はセミナーをしたり、展示をしたMDOnlineですが、今年は特にありません。ただし、主催者のセミナーの一部を私の方で仕切っています。そろそろ今日辺りには公表されていいはずなんだけど…。公表してよくなったら、内容については記事でお届けします。それから、去年は展示ブースは出しましたけど、何もできなかったですし、実は展示を出しても読者数は増えないという過去の実績もあるので、今年は何もだしません。ただし、User Groupの方では、OMEで出展をしますので、そちらのブースに詰めているかもしれません。
実は、小池さんからExpoのときにMDOnlineの読者を集めて、食事会とかパーティとかしたらどうかと提案を受けていますがいかがでしょうか? と言いながら、22日、23日とも夜は私はダメなんです。22日は恒例のイベント、23日はUGブースの撤収と打ち上げです。21日にしますか? しかし、東京ビッグサイト周辺だと会場探しがちょっと難航しそうですね。だけど、21日なら筆者の方々はある程度はいらっしゃるかな? せっかく提案いただいているのですが、どうしようかと考えてしまいます…。申し訳ないです。
(新居雅行 msyk@mdonline.jp)
この記事のPDFファイル(1,180KB)は以下のアドレスにあります。
ダウンロードには、MDOnlineのアカウントが必要です。
pdfs/MDOnline_2002.02_OpenGL.pdf
今回の記事のソース一式が含まれるプロジェクトは以下のアドレスです。
画像等と同じく、ダウンロードにはパスワード等は設定していません。
http://mdonline.jp/figs/02/022/SampleCode.sit
‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
■ 今回のテーマ
今回は、いつものサンプルコードの解説を一旦お休みして、CocoaアプリケーションからOpenGLを利用する方法をテーマとして取り上げます。OpenGLというと、3Dグラフィックス用という印象が強くて、一般のプログラマーには縁遠い感じもしますが、最近では様々なビジュアルエフェクトのためにOpenGLを使用するというケースも増えてきています。
例えば、iPhotoのスライドショーや、Mac OS Xに付属のBeachやCosmosなどのスライドショーを行うスクリーンセーバーでは、2つの画像をクロスフェードする ( 2つの画像の片方が消えながら、もう一方の画像が現れてくる ) ビジュアルエフェクトが使われています。あれらはOpenGLの機能を使って実現しています。iTunesに標準搭載のビジュアルエフェクトもOpenGLによって実現されています。このように、3Dグラフィックと関係のないソフトウェアでもOpenGLが使われるようになってきています。
そこで、本格的な3Dグラフィックの実現のためのOpenGLの利用ではなく、ビジュアルエフェクトのためのOpenGLの利用という観点からの説明を行っていきます。もちろん、3DグラフィックアプリケーションをCocoaで作成するという方にも、第一歩を踏み出すのには役に立つのではないかと思います。
◎ 推奨環境
この解説は、以下の環境を前提にしていますので、ご確認ください。
関連リンク:Cocoaはやっぱり!
カテゴリ:グラフィックス, Cocoa, 鶴薗賢吾のCocoaはやっぱり!出張版
続いて、MainMenu.nibウィンドウのClassesタブの中でNSOpenGLViewのサブクラスを作成します。クラス名はMyOpenGLViewとします。これを、先程のCustom Viewのクラスに割り当てて、ソースの出力を行います。この辺りの流れは、自前のビューを作成するのと同じです。ビューの形状ですが、今回は、以下のように正方形にしてください。
◇正方形にビューを配置する
そうしましたらコーディングに入ります。まず、ビューの真ん中にOpenGLを使って黄色い四角形を書いてみましょう。これもいつもと同様にdrawRect : メソッドに描画処理を書いていきます。
//ソース:MyOpenGLView.m、メソッド:drawRect :
- (void) drawRect : (NSRect) rect {
glClearColor( 1.0, 1.0, 1.0, 1.0 ); // 背景色指定
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// 色とデプスバッファをクリア
glColor4f( 1.0, 1.0, 0.0, 1.0 ); // 描画色を指定
glRectf( -0.6, -0.6, 0.6, 0.6 ); // 矩形を描画
glFinish(); // 描画コマンド実行
[ [ self openGLContext ] flushBuffer ]; // 画面更新
}
glで始まっているのがOpenGLの関数です。このように、OpenGLに大きく依存したコードになります。そして、OpenGL.frameworkのヘッダーを読み込むために、MyOpenGLView.hに以下の記述を足してください。
//ヘッダー:MyOpenGLView.h
#import
#import
#import
この3つが、必要なヘッダーです。これを実行すると、このようになります。
◇黄色の四角の描画結果
では、コードを簡単に説明します。glClearColorで背景色を黒に指定して、glClearで実際にクリアしています。glClearColorのパラメータは、4つありますが、透明度を含んだRGBAの4つを指定できるようになっています。値の範囲は、0.0〜1.0です。
glClearのパラメータは、どのバッファをクリアするかという情報です。OpenGLは、画面に表示される各ピクセルの色情報を覚えるバッファ以外にもいくつかのバッファを持っています。陰面処理を行うためのデプスバッファ ( Zバッファともいいます ) や、全ピクセルに対する一括演算を行うためのアキュムレーションバッファなどです。今回は、色情報のバッファ ( GL_COLOR_BUFFER_BIT ) とデプスバッファ ( GL_DEPTH_BUFFER_BIT ) をクリアするために、これらの論理和を取ってパラメータとしています。
ちょっと補足:OpenGLは、3Dのグラフィックを描画する際の陰面処理をデプスバッファで行っています。デプスバッファとは各ピクセルの視点からの距離を記憶しているバッファです。ピクセルを描画するときには、そのピクセルの視点からの距離をデプスバッファへも書き込んでいきます。次回、同じピクセルへの書き込みが発生したときは、視点からの距離がそれよりも近い場合にのみ書き込むようにすることで陰面処理を行います。デプスバッファをクリアするということは、全ピクセルを最も遠い値で埋めることになります。また、Zバッファと呼ばれるのは、視線方向にZ軸があるためです。
glColor4fでは、描画色を指定します。RGBAの値をパラメータとします。glRectfでは、パラメータで指定された四角形を描画します。デフォルト状態の座標系は、左下が ( -1.0, -1.0 ) で、右上が ( +1.0, +1.0 ) になっています。OpenGLは、3Dグラフィックのエンジンですが、2Dの座標指定も出来ます。2Dで座標指定すると3D空間の中の特定の面に対しての描画になります。ちなみに、Z軸は、ビューの面に垂直で手前に向かって伸びています。
◇OpenGLのデフォルトでの座標軸
最後のglFinishで、これらの処理を実行させます。
OpenGLにはダブルバッファの機能がついていて ( オフにすることも出来ます )、画面に表示されているフロントバッファと、OpenGLが直接描画するバックバッファの2つのバッファを持っています。バックバッファに描画しておいて、描画が一段落したところでバックバッファからフロントバッファへコピーする ( バックバッファとフロントバッファをスイッチする場合もあるようです ) ことで、描画過程を見せなくしたり、描画時のちらつきを抑えたりするわけです。
この、バックバッファからフロントバッファへのコピーを行うのが、NSOpenGLContextクラスのflushBufferメソッドです。NSOpenGLViewであるselfのopenGLContextメソッドで、OpenGLのコンテキストが取得できますので、それに対してflushBufferを実行します。
★ NSOpenGLView : OpenGLのコンテキストを取得
[書式] - (NSOpenGLContext *) openGLContext;
[入力]
[出力] 返り値 : 割り付けられているOpenGLのコンテキストのインスタンス。
★ NSOpenGLContext : バックバッファを画面に表示する
[書式] - (void) flushBuffer
[備考] ダブルバッファを使用しているときのみ有効。
[出力] 返り値 :
OpenGLの関数の名前は、glで始まっていることはお話しましたが、関数名の末尾の部分にも意味のある文字が付いています。glColor4fの末尾の4fは、パラメータが4つの浮動小数値 ( Float ) であるということ表しています。ここには出てきていませんが、glColor3iという関数もあります。こちらは、透明度を指定しないRGBの3つの整数値 ( Integer ) で色を指定することになります。このように、OpenGLには同じ機能でパラメータだけが違うという関数が沢山あります。そして、関数名からそれらを見分けることが出来ます。
■ グラデーション付きの描画
先程のサンプルはちょっと地味でしたので、もう少し派手なものにしてみましょう。グラデーション付きの四角形を書いてみます。以下のメソッドを新たに書いて、drawRect : メソッド内から [ self drawGradRect ]; で呼び出してください。
//ソース:MyOpenGLView.m、メソッド:drawGradRect
- (void) drawGradRect {
glBegin( GL_QUADS ); { // 四角形列の定義
glColor4f( 1.0, 0.0, 0.0, 1.0 ); // 赤
glVertex3f( -0.5, -0.5, 0.0 ); // 左下の頂点
glColor4f( 0.0, 1.0, 0.0, 1.0 ); // 緑
glVertex3f( 0.5, -0.5, 0.0 ); // 右下の頂点
glColor4f( 0.0, 0.0, 1.0, 1.0 ); // 青
glVertex3f( 0.5, 0.5, 0.0 ); // 右上の頂点
glColor4f( 1.0, 0.0, 1.0, 1.0 ); // 紫
glVertex3f( -0.5, 0.5, 0.0 ); // 左上の頂点
} glEnd();
}
glRectfよりももっと凝った四角形を描く場合は、glBegin( GL_QUADS ); 〜 glEnd(); を使用します。この2つの間で様々な指定が出来ます。glVertex3fは、各頂点の座標を指定しています ( Vertexとは頂点のこと )。3fですので、3Dでの座標指定になります。glVertex2fを使えば、2Dでの指定も出来ます。頂点の色指定は、頂点の座標指定の前に行います。glColor4fがそれです。頂点毎に別の色を指定することでグラデーションを作ることが出来ます。
GL_QUADSと複数形になっているのは、複数個の四角形を一気に描けることを意味しています。もし、直方体などの立体を描画したい場合は、glBegin( GL_QUADS ); 〜 glEnd(); の間に座標指定を4つずつ増やして6つの四角形を指定すればよいのです。GL_QUADS以外にも、GL_POINTS、GL_LINES、GL_POLYGON、GL_TRIANGLESなどがあり、様々な図形を描画することが出来ます。
実行結果は以下のようになります。
◇グラデーション付きの四角の描画結果
ちょっと補足:四角形の頂点の指定の順番は、このサンプルでは左回りになっています。OpenGLでは、左回りに見える側が表 ( おもて ) 側になります。立方体のような閉じた物体を描画する場合は、裏面を描画することはあり得ないので、裏面を描画しないことで高速化を行うという描画オプションがあります。
‥‥‥‥‥‥‥この項、続く‥‥‥‥‥‥‥[鶴薗賢吾]‥‥‥‥‥‥‥
関連リンク:Cocoaはやっぱり!
カテゴリ:グラフィックス, Cocoa, 鶴薗賢吾のCocoaはやっぱり!出張版
■ 座標変換
CocoaのApplication Kitには、NSAffineTransformという2次元の座標変換のクラスがありますが、OpenGLにも3次元の座標変換の機能があります。これを使って、平行移動・拡大縮小・回転 ( ロール・ヘッド・ピッチ ) が簡単にできます。これらを実現するためのメソッドは以下のようになります。ただし、座標変換の関数は実行の順番で結果が変わりますので、汎用のメソッドというわけではありません。
//ソース:MyOpenGLView.m、
//メソッドtransformMoveX : moveY : moveZ : zoomX : zoomY : zoomZ : ...
- (void) transformMoveX : (float) mx
moveY : (float) my
moveZ : (float) mz
zoomX : (float) zx
zoomY : (float) zy
zoomZ : (float) zz
roll : (float) roll
head : (float) head
pitch : (float) pitch {
glMatrixMode( GL_MODELVIEW ); // 操作する座標系をモデルにする
glLoadIdentity(); // 座標系をリセットする
glTranslatef( mx, my, mz ); // 移動
glScalef( zx, zy, zz ); // 拡大縮小
glRotatef( roll , 0, 0, 1 ); // ロール
glRotatef( head , 0, 1, 0 ); // ヘッド
glRotatef( pitch, 1, 0, 0 ); // ピッチ
}
まず、座標変換を行う対象をglMatrixModeで選択します。GL_MODELVIEWで、これから描画するオブジェクトの座標が変換されることになります。glLoadIdentityで、座標変換を行うマトリックスを初期化します。そして、glTranslatefで平行移動、glScalefで拡大縮小、glRotatefで回転になります。
このメソッドをdrawRect : 内から以下のように呼び出します。
//ソース:MyOpenGLView.m、メソッド:drawRect :
[ self transformMoveX : 0 moveY : 0 moveZ : 0
zoomX : 1 zoomY : 1 zoomZ : 1
roll : 30 head : 0 pitch : 0 ]; // 30度ロール
glColor4f( 1.0, 1.0, 0.0, 1.0 ); // 描画色を指定
glRectf( -0.6, -0.6, 0.6, 0.6 ); // 矩形を描画
[ self transformMoveX : 0 moveY : 0 moveZ : 0
zoomX : 1 zoomY : 1 zoomZ : 1
roll : 45 head : 0 pitch : 0 ]; // 45度ロール
[ self drawGradRect ]; // グラデーションの四角形を描画
すると、以下のような結果になります。
◇座標変換したときの実行結果
ちょっと補足:ロールは「 手を前に伸ばして手首を回転させたときの動き 」、ヘッドは「 首を左右に振ったときの動き 」、ピッチは「 お辞儀をしたときの動き 」に相当します。
■ 初期化について
先程、initWithFrame : メソッドのためにCustom Viewを配置するようにしましたが、実装しなくてもとりあえずOpenGLを呼び出して描画を行うことが出来ました。これは、NSOpenGLViewの初期化メソッドがデフォルトの状態を決めて初期化を行ってくれているためです。
ただし、このデフォルトの状態だと必要のないバッファが取られている可能性もありますし、逆に必要なものが取られていない可能性もあります。そのため、自分で初期化を行いたい場合もあるでしょう。その場合は、initWithFrame : を記述していきます。サンプルを以下に書きます。
//ソース:MyOpenGLView.m、メソッド:initWithFrame :
- (id) initWithFrame : (NSRect) frameRect {
NSOpenGLPixelFormatAttribute attr[] = {
NSOpenGLPFADoubleBuffer, // ダブルバッファを使う
NSOpenGLPFAAccelerated , // ハードウェアアクセラレーションを使う
NSOpenGLPFAStencilSize , 32, // ステンシルバッファのビット数を32bitにする
NSOpenGLPFAColorSize , 32, // 画像用バッファのビット数を32bitにする
NSOpenGLPFADepthSize , 32, // デプスバッファのビット数を32bitにする
0 // ターミネータ
};
NSOpenGLPixelFormat* pFormat;
pFormat = [ [ [ NSOpenGLPixelFormat alloc ] initWithAttributes : attr ]
autorelease ];
self = [ super initWithFrame : frameRect
pixelFormat : pFormat ]; // スーパークラスによる初期化
[ [ self openGLContext ] makeCurrentContext ];
// カレントコンテキストを変更
glClearColor( 1.0, 1.0, 1.0, 1.0 ); // 背景色を黒にする
return( self );
}
最初でNSOpenGLFormatAttributeの配列を定義しています。OpenGLでどういうバッファを使用するかとか、どういう属性を持つかの定数を定義しているのがNSOpenGLFormatAttributeです ( enumです )。沢山の指定がありますので、この定数のうち必要なものを配列に詰めて、それをパラメータとしてメソッドに渡すという形になっています。
NSOpenGLPixelFormatAttribute attr[] = {
NSOpenGLPFADoubleBuffer, // ダブルバッファを使う
NSOpenGLPFAAccelerated , // ハードウェアアクセラレーションを使う
NSOpenGLPFAStencilSize , 32, // ステンシルバッファのビット数を32にする
NSOpenGLPFAColorSize , 32, // 画像用バッファのビット数を32bitにする
NSOpenGLPFADepthSize , 32, // デプスバッファのビット数を32bitにする
0 // ターミネータ
};
NSOpenGLPFADoubleBufferのように定数単独のものと、NSOpenGLPFColorSizeのように数値を伴うものがありますが、前者は論理値の指定になります。存在しているとYES、存在しない場合はNOという指定になります。配列の最後には、ターミネータとして0を入れます。なお、NSOpenGLPixelFormatAttributeの定数の意味を理解するには、OpenGLの知識が必要になりますので、ここでは使用方法のみの説明にとどめます。
この配列を使って、NSOpenGLPixelFormatクラスのインスタンスを以下のように生成します。
NSOpenGLPixelFormat* pFormat;
pFormat = [ [ [ NSOpenGLPixelFormat alloc ] initWithAttributes : attr ]
autorelease ];
そして、このインスタンスを使って、NSOpenGLViewを初期化するという流れになります。
self = [ super initWithFrame : frameRect
pixelFormat : pFormat ]; // スーパークラスによる初期化
★ NSOpenGLContext : ピクセルフォーマット指定での初期化
[書式] - (id) initWithAttributes : (NSOpenGLPixelFormatAttribute *) attribs
[入力] attribs : ピクセルフォーマット。
[出力] 返り値 : NSOpenGLContextのインスタンス。
★ NSOpenGLView : ピクセルフォーマット指定での初期化
[書式] - (id) initWithFrame : (NSRect) frameRect
pixelFormat : (NSOpenGLPixelFormat *) format
[入力] frameRect : ビューのサイズ指定。
format : ピクセルフォーマット。
[出力] 返り値 : NSOpenGLViewのインスタンス。
‥‥‥‥‥‥‥この項、続く‥‥‥‥‥‥‥[鶴薗賢吾]‥‥‥‥‥‥‥
関連リンク:Cocoaはやっぱり!
カテゴリ:グラフィックス, Cocoa, 鶴薗賢吾のCocoaはやっぱり!出張版
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はやっぱり!
カテゴリ:グラフィックス, Cocoa, 鶴薗賢吾のCocoaはやっぱり!出張版
その後、texImgRepというNSBitmapImageRepクラスのインスタンスを生成していますが、これは、このNSBitmapImageRepを経由することで、圧縮などが行われていない生のビットマップデータをbitmapDataメソッド取り出すことが出来るためです。テクスチャー生成のためには、OpenGLに生のビットマップデータを渡す必要があるのです。
glPixelStoreiでメモリへの格納形式を指定し、glGenTexturesで空のテクスチャーを生成します。ここでテクスチャーのIDが返ってきます。glBindTextureはテクスチャー操作関数の対象を変更する関数なので、生成したテクスチャーに変更します。そして、glTexImage2Dでテクスチャーのビットマップイメージを送り込みます。
glTexImage2Dのパラメータのところに [ imgTexRep hasAlpha ] ? GL_RGBA : GL_RGB という記述があります。TIFFフォーマットのようにアルファチャンネル ( 透明度情報 ) を画像に含んでいる場合がありますので、その場合のために、データの並びをこのような記述で切り替えています。
★ NSImageRep : アルファチャンネルを持っているか
[書式] - (BOOL) hasAlpha
[出力] 返り値 : アルファチャンネルを持っている場合 - YES、ない場合 - NO
最後のパラメータで、生のビットマップデータを渡しますが、これは、NSBitmapImageRepのbitmapDataメソッドを使います。
★ NSBitmapImageRep : ビットマップの生データを取得
[書式] - (unsigned char *) bitmapData
[入力]
[出力] 返り値 : ビットマップデータの生データの存在する先頭のアドレスを返す
なお、アルファチャンネルを画像に含んでいる場合は、テクスチャー上データもアルファチャンネルを持つことになり、そのテクスチャーが貼られた四角形上のピクセルも、そのアルファチャンネルに従って透明になります。Mac OS Xに標準添付のAqua Iconsスクリーンセーバーでは、アプリケーションアイコンが飛び交っていますが、ここに書いているのと同様の方法で実現していると思われます。
◎ テクスチャーの描画
続いては、テクスチャーを貼り付けた四角形の描画です。以下のような、drawRectTextureId : alpha : というメソッドを定義します。指定したIDのテクスチャーを指定の透明度で表示するものです。
//ソース:MyOpenGLView.m、メソッド:drawRectTextureId : alpha :
- (void) drawRectTextureId : (GLuint) texId
alpha : (float ) alpha {
glColor4f( 1.0, 1.0 ,1.0, alpha ); // 四角の色を透明度付きで指定
glBindTexture( GL_TEXTURE_2D, texId ); // テクスチャー指定
glBegin( GL_QUADS ); {
glTexCoord2f( 0.0, 1.0 ); // テクスチャーの左上をマッピング
glVertex2f( -1.0, -1.0 ); // 左下の頂点
glTexCoord2f( 1.0, 1.0 ); // テクスチャーの右上をマッピング
glVertex2f( +1.0, -1.0 ); // 右下の頂点
glTexCoord2f( 1.0, 0.0 ); // テクスチャーの右下をマッピング
glVertex2f( +1.0, +1.0 ); // 右上の頂点
glTexCoord2f( 0.0, 0.0 ); // テクスチャーの左下をマッピング
glVertex2f( -1.0, +1.0 ); // 左上の頂点
} glEnd();
glBindTexture( GL_TEXTURE_2D, 0 ); // テクスチャー指定解除
}
glColor4fの4つ目のパラメータでこの四角形の透明度を指定します。そして、対象のテクスチャーをglBindTextureで指定してから四角形の描画になります。グラデーション付きの四角形の時には、各頂点の色を指定していましたが、テクスチャーの場合は、テクスチャー上のどこの座標をこの頂点に割り付けるかをglTexCoord2fで指定します。テクスチャーのイメージ全体には、左下 ( 0.0, 0.0 ) 〜 右上 ( 1.0, 1.0 ) という座標系が割り当たっています。
よく見ると、テクスチャーを上下反転してマッピングしていることが分かると思います。実は、そのままマッピングすると画像が上下反転して表示されてしまうためです。多分、ビットマップのデータの上下の並びが、OpenGLが期待しているものとNSBitmapImageRepのbitmapDataメソッドが返すものとが逆だからだと思いますが、このようにして回避しています。
◎ テクスチャーとアルファチャンネルのための初期化
テクスチャーとアルファチャンネル を使うためには、OpenGLに対していくつかの初期化が必要です。そこで、initWithFrame : の末尾に以下のコードを追加します。
//ソース:MyOpenGLView.m、メソッド:initWithFrame :
- (id) initWithFrame : (NSRect) rect {
: 省略
// テクスチャー関連
glEnable( GL_TEXTURE_2D ); // 2Dテクスチャーを使用可能する
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 ); // ブレンド時の演算を選択
return( self );
}
glEnableは、OpenGLの機能のどれかをオンにするための関数です。パラメータにGL_TEXTURE_2Dにすることでテクスチャー機能が使えるようになります。glDisableというオフにする関数もあります。glTexParameteriは、テクスチャーに関する設定を行う関数で、ここでは、テクスチャーの拡大や縮小で用いる画像補間アルゴリズムを設定しています。GL_TEXTURE_MAG_FILTERで拡大時の指定、GL_TEXTURE_MIN_FILTERで縮小時の指定が出来ます。GL_LINEARは直線補間になります。GL_NEARESTを使うと一番近いピクセルを使って補関することができます。このアルゴリズムの場合、画質は低下しますが、描画は高速になります。用途やマシンスピードに応じて使い分けをすることができます。
glEnable( GL_BLEND ) でアルファブレンディング ( 透明度を考慮した画像描画処理 ) を可能にします。glBlendFuncは、ブレンド処理の時の演算種別を指定します。通常はこのままで構いません。そして、この実行結果のサンプルは以下のようになります。カエルの絵をテクスチャーとして貼り付けた四角形を描いています。
◇テクスチャー付きの四角の描画結果
アルファチャンネルを持たせてあるので、カエルの形状に切り抜かれた四角形として描画されています。
ここまでの内容で、画像のクロスフェードまではできるようになります。次に現れるべき画像を最初に描いておいて、消えていく画像を透明度を時間とともに高くしながらかぶせて描画していくだけです。回転なども先程のメソッドで出来ます。
‥‥‥‥‥‥‥この項、続く‥‥‥‥‥‥‥[鶴薗賢吾]‥‥‥‥‥‥‥
関連リンク:Cocoaはやっぱり!
カテゴリ:グラフィックス, Cocoa, 鶴薗賢吾のCocoaはやっぱり!出張版
■ アニメーション
クロスフェードさせるためには、OpenGLでアニメーションを表示させる必要があります。この場合は、単純にdisplayメソッドを繰り返し呼ぶだけで実現できます。例えば、以下のようになります。
//ソース:MyOpenGLView.m、メソッド:drawAnimation : 〜 setupTimer
- (void) drawAnimation : (NSTimer*) aTimer {
: アニメーションのための処理
[ self display ]; // 表示要求
}
NSTimer* animTimer; // アニメーションタイマー
- (void) setupTimer {
animTimer = [ NSTimer scheduledTimerWithTimeInterval : 0.03
target : self
selector : @selector( drawAnimation : )
userInfo : nil
repeats : YES ];
}
drawAnimation : メソッドで、アニメーションをワンステップ進める処理 ( 透明度を変えるなど ) を書いて、その後にdisplayメソッドを呼ぶようにしておきます。それをNSTimerを使って定期的に呼びます。setupTimerメソッドは、タイマーの初期化メソッドですので、initWithFrame : から呼ぶようにします。このサンプルでは、タイマーの間隔が0.03秒になっていますので、約1/30秒刻みで呼ばれますが、描画処理がさほど重くなければ、これくらい頻繁に呼び出しても問題なくなめらかに動いてくれます。
■ ビューのサイズが変わったとき
ビューのサイズが変化したときには、glViewport関数を使うことで、OpenGLに描画エリアが変化したことを伝えます。具体的には、以下のresizeView : メソッドに書いてあるように、ビューの位置を渡すだけです。
//メソッド:MyOpenGLView.m、メソッド:drawAnimation : 〜 setupTimer
- (void) resizeView : (NSRect) rect {
// 画面上の描画範囲を変更
glViewport( (GLint) rect.origin.x , (GLint) rect.origin.y,
(GLint) rect.size.width, (GLint) rect.size.height );
{ // 描画する座標の範囲を変更
float fRate = rect.size.width / rect.size.height;
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if ( 1 <= fRate ) gluOrtho2D ( -fRate, fRate, -1, 1 );
else if ( 0 < fRate ) gluOrtho2D ( -1, 1, -1 / fRate, 1 / fRate );
}
}
ただし、ビュー内のOpenGLの座標系は、 ( -1.0, -1.0 ) 〜 ( +1.0, +1.0 ) のままですので、ビューが横に伸びた場合、表示されるイメージも横に伸びてしまいます。OpenGLの座標系を変えるには、gluOrto2D、glOrthoなどの関数を呼びます。このサンプルでは、短い方の辺の座標の範囲を -1.0 〜 +1.0 に固定して、長い方を比率で計算して求めています。
このメソッドをdrawRect : の先頭で呼んでおけばよいでしょう。以下が実行結果です。
◇リサイズ時の実行結果
■ おわりに
最近では、OpenGLは主要OSの大半に標準で搭載されていますが、他のOSでもOpenGLの扱いはCocoaと同様で、OpenGLの関数を直接呼ぶものが多いようです。そのため、Cocoa以外のOSのOpenGLのコードでもOpenGLの部分はそのまま流用できます。これは、サンプルコードは大量に入手できることを意味しますし、Cocoa上でOpenGLを勉強したとしても、OpenGLの知識は他のプラットホーム上でのそのまま通用することを意味します。
冒頭でも触れましたが、OpenGLが使われるケースというのは3Dソフトに限られなくなってきました。最近のPCにはほとんど3Dアクセラレーターが積まれるようになっていることも要因でしょう。従来では困難だったグラフィックの表現が比較的簡単にできますし、Cocoaからも簡単に使用できることも分かっていただけたと思います。他のソフトとの差別化にも、OpenGLは強力な味方になってくれるでしょう。
∽∽∽∽∽∽∽この項、以上∽∽∽∽∽∽∽[鶴薗賢吾]∽∽∽∽∽∽∽
関連リンク:Cocoaはやっぱり!
カテゴリ:グラフィックス, Cocoa, 鶴薗賢吾のCocoaはやっぱり!出張版
WebObjects 5.1のトライアル版の無償配付がWebObjectsのページで掲載されている。Webページ上で申し込むと、無償で送付されてくる。Mac OS X版、Windows版に加えて、Mac OS XのDeveloper Tools Dec 2001版も含まれている。Mac OS XあるいはWindows 2000 Professional等で、WebObjectsを利用したWebアプリケーション開発を実際に行い、稼働させるようすなどを確認することができる。トランザクション数の制限や分散処理ができない以外は製品版との差異はなく、機能や動作についてのチェックは十分に行える評価版である。Mac OS XはVer.10.1.2での利用が推奨される。なお、評価目的での利用に限定されており、実開発や稼働にはライセンスの上で利用できなくなっている。ただし、後日に製品版を購入し、キーを入力しなおすだけで、製品版として使えるようになっている。
関連リンク:WebObjects 5.1評価版お申し込み
カテゴリ:WebObjects
Mac OS XのStickiesについて、その内容のバックアップを取る方法が紹介されている。ユーザのホームにあるLibraryフォルダにある.StickiesDatabaseにデータが保存されているが、このファイルはFinderからは見えない。通常の操作でバックアップするには、ノート1つずつになるが、ファイルへの書き出しや読み込みなどの操作を行う必要がある。
関連リンク:Mac OS X: How to Back up Contents of Stickies Notes
カテゴリ:Knowledge Base(旧TIL), 文書作成アプリケーション
PowerBookシリーズを、Mac OS X 10.1を使って、ディスプレイを閉じた状態で使う方法が紹介されている。システムを終了させて、液晶ディスプレイを閉じた状態にし、Apple USB Keyboardなど電源ボタンのあるキーボードを取り付けて、電源をオンにすることで、閉じたままで起動することができる。
関連リンク:Mac OS X 10.1: Using a PowerBook With Display Closed
カテゴリ:Knowledge Base(旧TIL), PowerBook
液晶ディスプレイタイプのiMacで、正しくPC-133のメモリを装備しているにもかかわらず、Apple System Profilerで、PC-100のメモリであると認識される。これは動作の上では問題はない。ただしくハードウエアの状態を知るには、Apple Hardware Testを利用すれば良い。
関連リンク:iMac (Flat Panel): Apple System Profiler and Memory
カテゴリ:Knowledge Base(旧TIL), iMac
Multiprocesing ServiceにあるMPRemoteCallでのコンテキストパラメータに2つの定数があることについての回答が掲載された。kMPInterruptRemoteContextはMac OS 9.1で導入されたもので、Technical Note 2010で説明されている。同じように機能するkMPAsyncInterruptRemoteContextはMac OS 9.2.1で導入されたが、割り込みの実行待ちをブロックしない。MPタスクからのコールバック等での利用では便利かもしれない。Mac OS Xではいずれのオプションもサポートしていない。
関連リンク:QA1122: MPRemoteCall Contexts
カテゴリ:Technical Q&A, Mac OS 9
――――この原稿は、MacWIRE様に掲載していただくための原稿で、MDOnline読者様向けにはすでに周知の事実も含まれていますが、あらかじめご了承ください。
Macintosh関連の開発情報や技術情報をお届けしている有償の日刊メールマガジン「Macintosh Developer Online(MDOnline)」で、2002年2月第3週にお届けした内容のダイジェストを紹介しよう。
QuickTime Live!に合わせて、AppleはQuickTime 6を発表した。MPEG-4対応が大きな話題であるが、AppleはMPEG-4のライセンスに異議をとなえる目的で、ライセンスに修正が行われるまで出荷をしないと宣言した。また、ストリーミングライブを行うアプリケーションのQuickTime Broadcasterを発表したが同じ理由でリリースはされていない。Appleは、MEPG-4でのストリーミングを行うコンテンツ供給者からのライセンス料徴集に関して、市場拡大を阻害するとして反対している。
2002/2/13●MPEG-4対応を発表するもライセンスに異議を唱えリリースは行わず
REALbasic 4.0の日本語版についても、ライセンスの販売が開始された。3.5からのバージョンアップはもちろんだが、今回はより以前のバージョンからのバージョンアップにも対応している。
2002/2/13●REALbasic 4.0の日本語版のダウンロードとライセンス販売を開始
Mac OS X関連では、Carbon File Managerのファイルアクセスの仕組みをTechnical Q&Aで紹介しており、そこではなぜUFSボリュームでエイリアスが正しく機能しないかといったことも理解できるような説明がなされている。また、Applicationsフォルダの修正ができなくなったときに手軽に対処できるユーティリティも配付されている。
2002/2/16●Technical Q&A》Carbon File Managerのファイルアクセスの仕組み
2002/2/13●KBase》Applicationsフォルダが修正できないときの対処ユーティリティ
小池邦人氏の『プログラミング日記』のコーナーでは、Carbonアプリケーションでのウインドウのグループ化の機能と、それをつかえばどんなことができるのか、そしてプログラムでの実現方法についての説明が始まった。
2002/2/15●小池邦人のプログラミング日記》2002/2/15 _ ウィンドウのグループ化 その1
新しく始まった『Around the System and Development』のコーナーは、システムや開発周りの基本的なことを、読み物的に解説するコーナーだ。これからシステムについて勉強をしたいとか、あるいは開発をしてみたいという人に向けての基礎を解説したい。今週はとりあえず、なんで0と1というふうになったかというところから、16進数が使われる理由までの解説を行った。
2002/2/12●Around the System and Development》新コーナーをはじめました
2002/2/13●Around the System and Development》0と1
2002/2/14●Around the System and Development》0000から1111
2002/2/16●Around the System and Development》16進数に慣れよう
『AppleScript Working』のコーナーは、AppleScript Studioベースの開発でlog、start log、stop logコマンドを実現するH.Abe氏作のHAStudioLoggerについての説明を行った。スクリプト編集プログラムのようなログ機能がないProject Builderでもログ出力を標準出力に出せるようになり、デバッグの効率を高めてくれるだろう。
2002/2/15●AppleScript Working》6 _ AppleScript Studioでlogコマンドを使う
★MDOnlineは、ローカスが発行するオンライン情報メディアで、日刊のメールマガジンを中心に、Macintoshの開発情報や技術情報をお届けしている。過去の記事はWebサイトですべて参照できるようになっている。Mac OS Xの話題を積極的に取り上げるとともに、Knowledge Baseの公開情報を要約するなどAppleからの情報をフォローし、開発ツールやシステム関連ユーティリティなどのサードパーティ情報も扱う。購読料は有償で、年間8,000円、半年5,000円、1か月1,000円(消費税込みの基準価格、支払い方法により金額が増減する)。購読はWebページから申し込むことができ、郵便振替、クレジットカード(QQQシステム)、WebMoneyやBitCash(プリペイドカードやクレジットカードでのオンライン購入)での支払いに対応している。
Macintosh Developer Online <http://mdonline.jp/>
カテゴリ:MDOnline Digest