web-dev-qa-db-ja.com

OpenGLでオフスクリーンをレンダリングする方法は?

私の目的は、ウィンドウなしでOpenGLシーンをファイルに直接レンダリングすることです。シーンは私の画面の解像度よりも大きい場合があります。

これどうやってするの?

可能な場合、10000x10000など、任意のサイズにレンダリング領域のサイズを選択できるようにしたいのですが?

35
Rookie

すべては glReadPixels で始まり、GPUの特定のバッファーに保存されているピクセルをメインメモリ(RAM)に転送するために使用します。ドキュメントでわかるように、どのバッファを選択するかという議論はありません。 OpenGLの場合と同様に、現在の読み取りバッファは状態であり、glReadBufferで設定できます。

したがって、非常に基本的なオフスクリーンレンダリング方法は次のようになります。私はc ++擬似コードを使用しているため、エラーが含まれる可能性がありますが、一般的なフローを明確にする必要があります。

//Before swapping
std::vector<std::uint8_t> data(width*height*4);
glReadBuffer(GL_BACK);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]);

これにより、現在のバックバッファー(通常は描画先のバッファー)が読み取られます。バッファを交換する前にこれを呼び出す必要があります。上記の方法でバックバッファーを完全に読み取ってクリアし、スワップする前にまったく異なるものを描画することもできます。技術的にはフロントバッファも読み取ることができますが、フロントバッファにゴミが含まれる可能性がある最適化を理論的に実装することが許可されているため、これは推奨されません。

これにはいくつかの欠点があります。まず第一に、オフスクリーンレンダリングは実際には行いません。画面バッファーにレンダリングし、それらから読み取ります。バックバッファをスワップしないことでオフスクリーンレンダリングをエミュレートできますが、それは正しくないと感じます。その次に、フロントバッファーとバックバッファーは、ピクセルを表示するように最適化されており、ピクセルを読み取らないようになっています。 Framebuffer Objects が出番です。

基本的に、FBOを使用すると、スクリーンバッファの代わりにメモリバッファに描画できるデフォルト以外のフレームバッファ(FRONTおよびBACKバッファなど)を作成できます。実際には、テクスチャまたは renderbuffer に描画できます。前者は、OpenGL自体のピクセルをテクスチャとして再利用する場合に最適です(たとえば、ゲームの単純な「セキュリティカメラ」)。後者は、単にレンダリング/リードバックする場合に最適です。これにより、上記のコードはこのようなものになり、再び擬似コードになるので、タイプミスやステートメントを忘れても私を殺さないでください。

//Somewhere at initialization
GLuint fbo, render_buf;
glGenFramebuffers(1,&fbo);
glGenRenderbuffers(1,&render_buf);
glBindRenderbuffer(render_buf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_BGRA8, width, height);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,fbo);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_buf);

//At deinit:
glDeleteFramebuffers(1,&fbo);
glDeleteRenderbuffers(1,&render_buf);

//Before drawing
glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,fbo);
//after drawing
std::vector<std::uint8_t> data(width*height*4);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]);
// Return to onscreen rendering:
glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,0);

これは簡単な例です。実際には、深度(およびステンシル)バッファー用のストレージも必要になる可能性があります。テクスチャにレンダリングすることもできますが、これは演習として残しておきます。いずれの場合でも、実際のオフスクリーンレンダリングを実行し、バックバッファーを読み取るよりも速く動作する可能性があります。

最後に、 pixel buffer objects を使用して、読み取りピクセルを非同期にできます。問題は、ピクセルデータが完全に転送されるまでglReadPixelsがブロックすることです。これにより、CPUが停止する可能性があります。 PBOでは、実装はとにかくバッファーを制御するため、すぐに戻ることがあります。パイプラインがブロックするのは、バッファーをマップするときだけです。ただし、PBOはRAMのみにデータをバッファリングするように最適化されている場合があるため、このブロックにかかる時間は大幅に短縮されます。ピクセルの読み取りコードは次のようになります。

//Init:
GLuint pbo;
glGenBuffers(1,&pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, width*height*4, NULL, GL_DYNAMIC_READ);

//Deinit:
glDeleteBuffers(1,&pbo);

//Reading:
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,0); // 0 instead of a pointer, it is now an offset in the buffer.
//DO SOME OTHER STUFF (otherwise this is a waste of your time)
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); //Might not be necessary...
pixel_data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);

キャップの部分は不可欠です。 glReadPixelsをPBOに発行し、その後にそのPBOのglMapBufferを発行するだけで、多くのコードしか得られません。 glReadPixelsがすぐに戻ることはありますが、データを読み取りバッファからPBOおよびメインRAMのメモリブロックに安全にマップする必要があるため、glMapBufferはストールします。

また、私はGL_BGRAをあらゆる場所で使用していることに注意してください。これは、多くのグラフィックスカードが内部的にこれを最適なレンダリング形式(またはアルファなしのGL_BGRバージョン)として使用しているためです。これは、このようなピクセル転送の最速のフォーマットでなければなりません。私はこれについて数ヶ月前に読んだNVIDIAの記事を見つけようとします。

OpenGL ES 2.0を使用する場合、GL_DRAW_FRAMEBUFFERは使用できない可能性があります。使用するのはGL_FRAMEBUFFERその場合。

87
KillianDS

メインコンテキストを作成するダミーウィンドウを作成する(レンダリングしないで、APIで作成する必要があるためにそこにある)ことは、受け入れ可能な実装戦略であると想定します。

オプションは次のとおりです。

ピクセルバッファー

ピクセルバッファー、またはpbuffer( ピクセルバッファーオブジェクト ではありません)は、何よりもまずOpenGLコンテキストです。基本的に、通常どおりウィンドウを作成してから、 wglChoosePixelFormatARB からピクセル形式を選択します(ここからpbuffer形式を取得する必要があります)。次に、wglCreatePbufferARBを呼び出して、ウィンドウのHDCと使用するピクセルバッファー形式を指定します。ああ、そして幅/高さ;実装の最大幅/高さを照会できます。

Pbufferのデフォルトのフレームバッファは画面に表示されません。最大幅/高さは、ハードウェアで使用できるものであれば何でもかまいません。そのため、そこにレンダリングし、glReadPixelsを使用してそれから読み戻すことができます。

ウィンドウコンテキストでオブジェクトを作成した場合は、指定されたコンテキストとコンテキストを共有する必要があります。それ以外の場合は、pbufferコンテキストを完全に個別に使用できます。ウィンドウコンテキストを破壊しないでください。

ここでの利点は、実装のサポートが大きいことです(ただし、代替をサポートしないほとんどのドライバーは、サポートされなくなったハードウェアの古いドライバーでもあります。またはIntelハードウェアです)。

欠点はこれらです。 Pbufferは、 コアOpenGLコンテキスト では機能しません。互換性のために機能する場合もありますが、OpenGLのバージョンとプロファイルに関するwglCreatePbufferARB情報を提供する方法はありません。

フレームバッファオブジェクト

Framebuffer Objects は、pbufferよりも「適切な」オフスクリーンレンダーターゲットです。 FBOはコンテキスト内にあり、pbufferは新しいコンテキストの作成に関するものです。

FBOは、レンダリング先の画像の単なるコンテナです。実装で許可される最大のディメンションを照会できます。 GL_MAX_VIEWPORT_DIMS(FBOがバインドされているかどうかに基づいて変化するため、これをチェックする前にFBOがバインドされていることを確認してください)と想定できます。

これらからテクスチャをサンプリングしていないため(値を読み込んでいるだけです)、テクスチャの代わりにレンダーバッファを使用する必要があります。それらの最大サイズは、テクスチャのものよりも大きい場合があります。

利点は使いやすさです。ピクセル形式などを扱う必要はなく、glRenderbufferStorage呼び出しに適切な image format を選択するだけです。

唯一の本当の欠点は、それらをサポートするハードウェアの狭い帯域です。一般に、AMDまたはNVIDIAが引き続きサポートするもの(現在、GeForce 6xxx以上(xの数に注意)、および任意のRadeon HDカード)は、ARB_framebuffer_objectまたはOpenGL 3.0+(コア機能である場合)にアクセスできます。 )。古いドライバーでは、EXT_framebuffer_objectのサポートのみが可能です(いくつかの違いがあります)。 Intelハードウェアは持ち寄りです。 3.xまたは4.xのサポートを要求している場合でも、ドライバーのバグが原因で失敗する可能性があります。

18
Nicol Bolas

GL実装 libtr ]の最大FBOサイズを超えるものをレンダリングする必要がある場合は、かなりうまく機能します。

TR(タイルレンダリング)ライブラリは、タイルレンダリングを行うためのOpenGLユーティリティライブラリです。タイルレンダリングは、大きな画像を断片(タイル)で生成する手法です。

TRはメモリ効率に優れています。メインメモリにフルサイズの画像バッファを割り当てることなく、任意の大きな画像ファイルを生成できます。

3
genpfault

最も簡単な方法は、フレームバッファオブジェクト(FBO)と呼ばれるものを使用することです。ただし、openglコンテキストを作成するにはウィンドウを作成する必要があります(ただし、このウィンドウは非表示にすることができます)。

2
Andreas Brinck

目標を達成する最も簡単な方法は、FBOを使用してオフスクリーンレンダリングを行うことです。そして、テクスチャにレンダリングする必要はありません。それからteximageを取得します。バッファにレンダリングして関数glReadPixelsを使用するだけです。このリンクは役に立ちます。 Framebuffer Object Examples を参照してください

1
rtrobin