オフスクリーン・レンダリング

ビューを表示しないでバックグラウンドで描画処理を行う方法について説明します。

MGL.CreateOffscreenContext による方法

LINQPad を立ち上げ、次の設定をします。

  • 参照設定に MoNo.dll, MoNo.OpenGL.dll を加える。
  • インポートする namespace に MoNo, MoNo.OpenGL を加える。
  • Language を F# Expression にする。

次のコードを実行すると直線が描画されたビットマップが保存されます。

let con, hbmp = MGL.CreateOffscreenContext (256, 128)
MGL.ClearBuffers (Color4f(0.5f, 0.5f, 0.5f, 1.0f), 1.0, 0)
MGL.DrawArrays (GLPrimType.Lines, [| Point3d.Zero; Point3d(0.5, 0.7, 0.) |])
GL.glFlush()
let bmp = System.Drawing.Image.FromHbitmap(hbmp)
bmp.Save(@"c:\...\test.bmp")

この方法では投影行列もモデルビュー行列も設定されません。 これらの設定が必要な場合は、全て自前で設定コードを記述する必要があります。

このAPIは内部でFBO(Framebuffer object)を利用しません。 CreateCompatibleDC(NULL) によって作成したデバイスコンテキストをビットマップに関連付け、そこに OpenGL 描画を行います。

この方法では、グラフィックボードのアクセラレーション機能が利用できません。 弊社の環境で glGetString によって検証すると、次の結果が得られます。

Key value
GL_VERSION 1.1.0
GL_VENDOR Microsoft Corporation
GL_RENDERER GDI Generic

GraphicsUT.CreateOffscreenSceneContext による方法

LINQPad を立ち上げ、次の設定をします。

  • 参照設定に MoNo.dll, MoNo.OpenGL.dll, MoNo.Basics.dll, MoNo.Framework.dll を加える。
  • インポートする namespace に MoNo, MoNo.OpenGL を加える。
  • Language を F# Expression にする。

次のコードを実行すると直線が描画されたビットマップが保存されます。

let sc, hbmp = GraphicsUT.CreateOffscreenSceneContext(Sphere3d.Unit, Size2i (300, 200))
sc.InitProjection()  // 投影行列の設定
use scope = sc.BeginWorldScene()  // モデルビュー行列の設定等
MGL.ClearBuffers (Color4f(0.5f, 0.5f, 0.5f, 1.0f), 1.0, 0)
MGL.DrawArrays (GLPrimType.Lines, [| Point3d.Zero; Point3d(0.5, 0.7, 0.) |])
GL.glFlush()
let bmp = System.Drawing.Image.FromHbitmap(hbmp)
bmp.Save(@"c:\...\test.bmp")

この方法では投影行列およびモデルビュー行列が sc.Camera に沿って設定されます。

MGL.IFramebuffer による方法

次のコードを実行すると直線が描画されたビットマップが保存されます。

if MGL.FBOIsAvailable then
  let fb = MGL.GenFramebuffer(Size2i (256, 128))
  fb.AssignRenderBuffer (OpenGL.GLAttachment.Depth) |> ignore
  fb.AssignTexture (OpenGL.GLAttachment.Color00, OpenGL.GLTextureType.RGBA8) |> ignore

  let sc = fb.CreateSceneContext(Sphere3d.Unit)
  sc.Camera.ViewingPos <- CodSys3d (-Vector3d.Ey, Vector3d.Ex)

  let bmp =
    use __ = fb.Bind()
    sc.InitProjection()  // 投影行列の設定
    use scope = sc.BeginWorldScene()  // モデルビュー行列の設定等
    MGL.ClearBuffers (Color4f(0.5f, 0.5f, 0.7f, 1.0f), 1.0, 0)
    MGL.DrawArrays (GLPrimType.Lines, [| Point3d.Zero; Point3d(-0.5, 0.7, 0.) |])
    GL.glFlush()
    MGL.ReadColorPixelsToBitmap24bppRgb()
bmp.Save @"c:\...\test.bmp"

ただし、LINQPad やコンソールアプリケーションでは FBOIsAvailablefalse となってしまい正常に動作しないようです。 通常の MoNo.RAIL のウィンドウが起動した状態で上記コードを実行すると、意図したとおりに動作しました。 現状では、FBO が動作する条件についてきちんと絞り込めていません(申し訳ありません)。

FbArgs による方法

MoNo.Framework.FSharp.dll に定義されている MoNo.Graphics.FbArgs を利用する方法です。 次のコードを実行すると直線が描画されたビットマップが保存されます。

open MoNo
...
let bmp =
  let fba = Graphics.FbArgs.New (Size2i (256, 128), Sphere3d.Unit)
  use con = fba.Bind ()
  con.Scope.Color <- Drawing.Color.YellowGreen
  con.Scope.Lighting <- false
  MGL.DrawArrays (GLPrimType.Lines, [| Point3d.Zero; Point3d(0.5, 0.7, 0.) |])
  MGL.ReadColorPixelsToBitmap24bppRgb()
bmp.Save @"c:\...\test.bmp"
  • FbArgs は内部で MGL.IFramebuffer を使用します。
  • 投影行列やモデルビュー行列などが FbArgs の値に従って自動的に設定されます。

FramebufferImage による方法

MoNo.Framework.FSharp.dll に定義されている MoNo.Graphics.FramebufferImage を利用する方法です。 次のコードを実行すると直線が描画されたビットマップが保存されます。

open MoNo
...
let args = Graphics.FbArgs.New (Size2i (256, 128), Sphere3d.Unit)
let image = Graphics.FramebufferImage.ofColor args (fun sc ->
  sc.Color <- Drawing.Color.Yellow
  MGL.DrawArrays (GLPrimType.Lines, [| Point3d.Zero; Point3d(0.5, 0.7, 0.) |]))
let bmp = Graphics.FramebufferImage.toBitmap image
bmp.Save @"c:\...\test.bmp"

FramebufferImage<'T> は次のように定義されています。

type FramebufferImage<'T when 'T : equality> = {
    Camera  : ICamera
    Image   : Immutarray<'T>
  } with
  member this.At (x, y) = this.Image.[x + this.Camera.ScreenSize.X * (this.Camera.ScreenSize.Y - 1 - y)]
  member this.At (p : Point2i) = this.At (p.X, p.Y)
  member this.At (p : System.Drawing.Point) = this.At (p.X, p.Y)
  member this.Items =
    seq {
      let size = this.Camera.ScreenSize
      for y = 0 to size.Y - 1 do
        let index = size.X * (size.Y - 1 - y)
        for x = 0 to size.X - 1 do
          yield Point2i (x, y), this.Image.[x + index]
    }

この定義からわかるように、FramebufferImage を使うと FBO に描画して得られた画像を配列データとして取り出すことができます。 サンプルコードではカラーバッファの画像を取り出していますが、デプスバッファやステンシルバッファの画像を取り出すことも可能です。

描画結果をテクスチャとして取り出す

これまでのサンプルでは描画結果をビットマップ画像として取り出していました。 テクスチャオブジェクトとして取り出したい場合は、次のように MGL.IFramebufferDetachTexture メソッドを使用します。

let tex =
  let fba = Graphics.FbArgs.New (Size2i (512, 256), Sphere3d(2.0), Vector3d (1.0, 2.0, 3.0))
  use con = fba.Bind()
  con.Scope.Color <- Drawing.Color.Orange
  Cube3d(Point3d.Zero, 1.0).Draw con.SceneContext
  con.Framebuffer.DetachTexture GLAttachment.Color00