● ToolBox API徒然草(2003/08/22)

  このニュースは、MOSAの会員にのみ配布されているデベロッパー向けの
  デジタルマガジンMOSADeNのに掲載された記事です。ほぼ一ヶ月遅れで
  ここに掲載されて行きます

 〜 OpenGLで描画した画像を抽出する 〜


以前、フルスクリーンでOpenGLによる3Dオブジェクトの描画を実行する方法について解説しました。その時にも簡単に説明しましたが、Mac OS Xにおいて、CarbonにはAGL(Apple OpenGL Library)が、CocoaにはNSOpenGL Classが存在しており、共にOpenGLとMac OS Xの描画環境(ウィンドウなど)を関連付ける役割を担っています。その両モジュールのさらに下層には、CGL(Core OpenGL)と呼ばれるAPI群が存在しており、フルスクリーンで3Dオブジェクトを描画した時には、こちらのAPIを使いました。

現在、ビデオカードに搭載されているGPU(Graphics Processing Unit)の演算能力は絶大であり、特定の演算についてはCPUよりはるかに高速に処理します。また、最近では「Vertex Programming」や「Fragment Programming」など、GPUに対してプログラミング可能なビデオカードも登場しています。機種依存が大きいことから、どんな状況でもこうした機能を利用可能と言うわけにはいきませんが、このような優れた演算能力を3Dオブジェクト描画のみに利用していては宝の持ち腐れとなります。そこで、GPUのありあまるパワーを2D描画にうまく転用したのが、Apple社がMac OS X 10.2から採用した「Quartz Extreme」と言うわけです。

GPUをプログラミングして使うような複雑な処理でなくとも、OpenGLで描画した画像を抽出し別の画像と合成するとか、描画結果を何らかの指標として使うとか、本来の用途以外の使い道も沢山あるはずです。「そんなのウィンドウに描画してCopyBits()で持ってくればOKでは?」と思われるかもしれませんが、残念ながらMac OS Xでは、そうした方法は使えません。OpenGLで描画された画像は、設定されているウィンドウのCGrafPtrではなく、ビデオカードのフレームバッファ(ビデオメモリ)に保存されているからです。ですから、CopyBits()でウィンドウ画像をコピーしてくると、描画された3Dオブジェクトの代わりに真っ白な画像が得られることになります。

これを解決するのには2つの方法があります。「3Dオブジェクトの描画先をフレームバッファから主メモリに切り替える」と「3Dオブジェクトが描画されているフレームバッファのデータを主メモリへ転送する」の、どちらかです。まずは最初の方法を解説しますが、その前に、OpenGLの描画先をウィンドウへと指定する方法をcreateAGLWidnowContext()ルーチンでおさらいしておきましょう。



ウィンドウを描画先に指定する場合には、AGLContexをaglCreateContext()で作成してから、対象となるウィンドウのCGrafPortをaglSetDrawable()に渡します。ウィンドウの代わりに主メモリ(オフスクリーン)への描画を指定する場合には、aglSetOffScreen()を追加し、次のようにソースコードを変更します。



最初の引き数のwxとwyは、描画矩形領域の横と縦のピクセル数です。次のbyteは1ピクセルのバイト数(フルカラーは4Byte)です。ここで注意する点は、aglSetDrawable()の2番目の引き数にNULLを渡すことです。では、NewGWorld()で確保したGWorldをオフスクリーンバッファとして指定するにはどうすれば良いでしょうか?「aglSetDrawable()の2番目の引き数にGWorldPtrを代入すれば良いのでは?」と思われるかもしれませんが、残念ながらそれではうまく行きません。以下のcreateAGLGWorldContext()が、GWorldPtrを描画用オフスクリーンとして指定するルーチンです。同時に、矩形領域とピクセルサイズ(これはBit)からGWorld作成するためのcreateGWorld()ルーチンも記載しておきます。こちらで重要な点は、ハンドラとして確保されているメモリ領域をLockPixels()でロックし、ポインタとしてアクセスしても問題ないように準備することです。



オフスクリーンへの描画は手軽で扱いやすいのですが、ひとつ大きな問題があります。それは、OpenGLによる3Dオブジェクトの描画スピードが極端に遅くなってしまうことです。まあ、フレームバッファを利用しておらず、GUPのよるアクセラレートも機能していないわけですから当然なのですが...。簡単な処理でも、10倍から20倍、複雑な処理であれば数百倍遅くなるケースも出てきます。この欠点を補うには、もう一つの方法「3Dオブジェクトが描画されているフレームバッファのデータを主メモリへ転送する」を実行するしかありません。

最初に紹介したcreateAGLWidnowContext()ルーチンでウィンドウを描画先に指定したら、そのウィンドウをオープンせずにバッファとして利用します。ウィンドウ自体をオフスクリーン(ちょと違うのだが...)として使用するわけです。ウィンドウに対してOpenGLの描画を実行したら、続いて以下の2つのglコマンドを実行します。これにより、フレームバッファ内の画像データがアドレスで指定された主メモリ(buf)に読み込まれます。glReadBuffer()では、読み込み先のフレームバッファの種類を選択し、glReadPixels()では、読み込みの原点、矩形の横と縦のピクセル数、ピクセルタイプ、カラーの値タイプ、読み込み先のバッファのアドレスなどを指定します。

glReadBuffer( GL_FRONT );
glReadPixels( 0,0,wx,wy,GL_RGBA,GL_UNSIGNED_BYTE,buf );

「ウィンドウをオープンしなくても描画できるのか?」という心配は無用です。ちゃんと問題なく描画されます(笑)。ただし、OpenGLでの描画は矩形領域の左下が原点となっていますので(QuickDrawは左上原点)、それに対処した前処理をせず単純にデータを読み込む場合には注意してください(上下逆になる)。

Mac OS X特有なOpneGLの技術内容(特にCGL)に関するドキュメントは、以下のサイトで参照することができます。

http://developer.apple.com/documentation/GraphicsImaging/Conceptual/OpenGL/index.html

ちなみに、AGLの代わりにCGLを利用してOpenGLの描画先にオフスクリーンを割り当てるサンプルが以下のURLに記載されています。

http://developer.apple.com/documentation/GraphicsImaging/Conceptual/OpenGL/chap3/index.html

このソースを実際に試してみましたが、何故かうまく行きませんでした。ページの最後には「ハードウェア・アクセラレーションは利かないよ!」とは記述されていますが、「オフスクリーンへの描画はまだできないよ」とは書かれていません(涙)。Mac OS X 10.2では、機能が実装されていないのでしょうか?謎です?

Mac OS XとOpenGLに関するトピックス、ドキュメント、仕様、拡張仕様、サンプルソースコードなど、あらゆるリソースには以下のURLからアクセスすることが可能です。

http://developer.apple.com/opengl/

copyright 2003 Ottimo, Inc. All rights reserved
無断転載・引用禁止
Contact us: koike@ottimo.co.jp