バッファの画像をキャプチャする

(2010.07.04 更新)

OpenGLで描画したら、やっぱり描画結果を画像として保存したいとか思うわけで。
ということで、バッファの内容(=描画結果)を取得して、Bitmapオブジェクトに格納する方法。



おおざっぱな手順は次の通り。
  1. 画像のコピー先となるBitmapオブジェクトを作成する。
  2. バッファの内容をBitmapオブジェクトへコピー。
  3. Bitmap.Saveメソッドでファイルとして保存。
2 については、使用言語によらずOpenGL一般で共通な作業です。
1 と 3 はC#での作業になります。
 

1. 画像のコピー先となるBitmapオブジェクトを作成する

要は、バッファのデータのコピー先となるBitmapオブジェクトを作成するわけです。
この辺は、とくにややこしいことは無いです。
画像の幅と高さ、フォーマットを指定して初期化するだけです。

// RGBAならFormat32bppArgb、RGBならFormat24bppRgb。
PixelFormat bmpPixelFormat = PixelFormat.Format32bppArgb;

// Bitmapオブジェクトを作成。
// これにバッファのデータをコピーします。
Bitmap bmp = new Bitmap( width, height, bmpPixelFormat );

バッファの内容をBitmapオブジェクトへコピーする

手順としては、
まず、BitmapDataというクラスを使ってBitmapのピクセルデータを直接いじれる状態に。
で、読み取るバッファをglReadBuffer(...)という関数で指定。
それから、OpenGLのglReadPixels(...)関数でバッファの内容をコピー。

が、注意点。
glReadPixels(...)の5つ目の引数で指定する値について。
取得したい色情報の形式を指定するわけだが、
RGBで取得したい場合は、GL_BGR、
RGBAで取得したい場合はGL_BGRAを指定しておくこと。
理由は、Windowsではメモリ上での各ピクセルの色情報の並び方がBRGとかBGRAの順になっているから。
ちなみに、これを間違えると、実行時にエラーがでるか、画像の色がヒドい事になります。

具体的なコードは、こんな感じ。
 
// bmpの画像データを直接扱うために、こういうことをします。
// LockBits()メソッドを呼び出したら、あとで必ずUnlockBits()を呼び出す必要があります。
System.Drawing.Imaging.BitmapData bmpData
    = bmp.LockBits( new Rectangle( 0, 0, bmp.Width, bmp.Height ),
                    System.Drawing.Imaging.ImageLockMode.WriteOnly,
                    bmpPixelFormat );

// 読み取るOpneGLのバッファを指定。
// ここでは、バックバッファを読み取る。
// フロントバッファを読み取りたい場合は、GL_FRONT を指定する。
glReadBuffer( GL_BACK );

// バッファの内容を
// bmpオブジェクトのピクセルデータが格納されている領域に直接コピーする。
glReadPixels(
              x,                 //読み取る領域の左下隅のx座標
              y,                 //読み取る領域の左下隅のy座標
              width,             //読み取る領域の幅
              height,            //読み取る領域の高さ
              GL_BGRA,           //取得したい色情報の形式
              GL_UNSIGNED_BYTE,  //読み取ったデータを保存する配列の型
              bmpData.Scan0      //ビットマップのピクセルデータ(実際にはバイト配列)へのポインタ
            );

//LockBits()でロックしたものは、
//必ずUnlockBits()でアンロックすること。
bmp.UnlockBits( bmpData );

これで、バッファから読み取った画像がbmpオブジェクトに入っているはずです。

実は、もう一手間、必要だったりします。

このままでは、
OpenGLとWindowsでは画像の座標原点の位置が違う(OpenGLでは左下、Windowsでは左上。)ために、
bmpは上下逆さまな画像になっているので、
Bitmap.RotateFlip()
メソッドを使って上下を反転させます。

bmp.RotateFlip( RotateFlipType.RotateNoneFlipY );

これでOK。


3. 画像をファイルとして保存する

あとは、Bitmap.Save()メソッドで、
ファイル名とかフォーマットとかを指定して、画像ファイルとして保存してやればOK。
ただし、画像ファイルのフォーマットには、ちょっと注意すべし。
ビットマップ形式(*.bmp)は、アルファチャンネルは保存できない、かつ、非圧縮なのでファイルサイズが大きい。
JPEG形式(*jpg)は、アルファチャンネルは保存できない、かつ、不可逆圧縮なので画像にちょっとだけノイズが混じる。
GIF形式(*.gif)は、256色のみ。うち一色を透明色として指定可能。
PNG形式(*.png)は、アルファチャンネルまで保存可能。しかし、色数が多いとJPEGよりファイルサイズが大きくなる傾向有り。

形式によってそれぞれ特徴があるので、場合によって使い分けるべし。
おすすめは、
アルファチャンネルが必要ない、または、あまり画質が良くなくても良いのであれば、JPEG、
アルファチャンネルが必要なら、PNG形式が適していると思います。


サンプルプログラム

サンプルプログラムなんぞも用意してみました。
「Capture」というクラスの中に、「CaptureBufferAsBitmapRGBA( ... )」というメソッドがあるだけですので、
各自のプログラムに組み込んで使ってみて下さい。
あと、OpenGLの関数名は、ラッパークラスの定義に応じて各自書き換えて使ってください。
サンプルプログラム(CaptureGLBuffer.zip)


<back to OpenGL menu>