OpenGLを使用する場合、
なにはともあれ、まずは、
「初期化」というものを行わなければならないわけです。
で、その具体的な内容はと言うと、
もっぱら、「レンダリングコンテキストの取得・初期化」で、
ウィンドウのハンドルを取得して・・・とか、
デバイスコンテキストのハンドルがうんぬんとか、
ピクセルフォーマットなんとかがなんだかんだとか、
なんだかよくわからなくてややこしそうなものをいじらなければならないのです。
OSに依存せずにウィンドウの作成とOpenGLの初期化をやってくれる
GLUTという便利なものもあるのですが、
簡単なウィンドウしか作れないし、
GLUTが勝手にウィンドウを作成するので、
Visual Studioのデザイナでこれををいじることはできないし、
.NET Frameworkに用意されているFormクラスも使えないので、
.NETとは相性がよろしくない。
で、この初期化を行う他の方法としては、
自前で一通りコードを書くのと、
既存のラッパーなどに用意されている初期化用のルーチンを利用するのと、
2つの選択肢があります。
当然、後者のほうが楽と言えば楽ですが、
自前での初期化方法を知っていて損はないはず。
ということで、
両方の初期化方法について、説明していきたいと思います。
(言語はC#、OSはWindow、Visual Studio(Express Editionも可、2005以降のバージョン)を使用、という環境を前提とします。)
具体的なサンプルコードを書いていこうと思うのですが、
ここでは、ラッパーとして「Tao Framework」(これを書いている時点でのバージョンは2.1.0。)
を使用することにします。
このTao FrameworkはたくさんのDLLがセットになっていますが、
必要なのは、
"Tao.OpenGl.dll" : 名前の通り、OpenGL関数を呼び出すためのラッパー
"Tao.Platform.Windows.dll" : WGLとwin32関数などのラッパー
の2つです。
サンプルプログラムを用意しておきましたので、
ページの最下部においておきます。
で、概要ですが、
今回はユーザーコントロールを作成してそこにOpenGLで描画する、
という方針でいきます。
まずは、
上記の2つのDLLを参照設定に追加し、
(ソリューションエクスプローラで「参照設定」を右クリックして「参照の追加」を選択し、「参照」タブでDLLのファイルを選択する。)
プロジェクトに新規のユーザーコントロールを追加します。
このユーザーコントロールにOpenGLで描画することを目標とします。
で、初期化は、
protected void OnHandleCreated( EventArgs e );
メソッドをオーバーライドして、ここで行います。
ここに、まずは、
ちょっと「オマジナイ」を書き加えておきます。
(コントロールに関する設定をOpenGLを使うのに都合の良いものにしておきます。)
this.SetStyle( ControlStyles.UserPaint, true ); this.SetStyle( ControlStyles.AllPaintingInWmPaint, true ); this.SetStyle( ControlStyles.DoubleBuffer, false ); this.SetStyle( ControlStyles.Opaque, true ); this.SetStyle( ControlStyles.ResizeRedraw, true );
あ、それと、コードの先頭に
using Tao.OpenGl;
using Tao.Platform.Windows;
の2行を追加しておくのをお忘れなく。
using System; using System.Drawing; using System.Windows.Forms; using Tao.OpenGl; using Tao.Platform.Windows; namespace Tao_Sample { public partial class GLControl : UserControl { public GLControl() { InitializeComponent(); } protected override void OnHandleCreated( EventArgs e ) { base.OnHandleCreated( e ); this.SetStyle( ControlStyles.UserPaint, true ); this.SetStyle( ControlStyles.AllPaintingInWmPaint, true ); this.SetStyle( ControlStyles.DoubleBuffer, false ); this.SetStyle( ControlStyles.Opaque, true ); this.SetStyle( ControlStyles.ResizeRedraw, true ); //ここでOpenGLの初期化を行う。 } } }
さて次。
OpenGLの初期化に取りかかります。
まずは、"PIXELFORMATDESCRIPTOR"構造体というものを用意します。
これは、描画するバッファのフォーマット(ピクセルフォーマット)を指定する構造体。
今回は、RGBA32bitで、アルファチャンネル有り、デプスバッファ16bitでダブルバッファ、
というフォーマットにしておきます。
で、この構造体を、コントロールのデバイスコンテキストのハンドルと一緒に、
次の関数に渡すと、指定したものに最も近いピクセルフォーマットの番号が返される。
0が返ってきたら、エラーです。
int Tao.Platform.Windows.Gdi.ChoosePixelFormat( IntPtr deviceContext, ref Gdi.PIXELFORMATDESCRIPTOR pixelFormatDescriptor );
デバイスコンテキストのハンドルは、
IntPtr Tao.Platform.Windows.User.GetDC( IntPtr windowHandle );
という関数に、コントロールのHandleプロパティを渡すことで取得できます。
また、取得したハンドルは、後であちこちで必要になるので、
コントロールに変数として持たせておくことにします。
・・・デバイスコンテキストってなんだ、ってのは、
まぁ、ここでは説明が長くなるので省略ということで。
ピクセルフォーマットの番号が選択されたら、
念のために正しいフォーマット内容を
Tao.Platform.Windows.Wgl.wglDescribePixelFormat 関数で取得し直しておきます。
// デバイスコンテキストのハンドル IntPtr hDC; protected override void OnHandleCreated( EventArgs e ) { base.OnHandleCreated( e ); Gdi.PIXELFORMATDESCRIPTOR pfd = new Gdi.PIXELFORMATDESCRIPTOR(); // OpenGLをサポート、ウィンドウに描画、ダブルバッファ。 pfd.dwFlags = Gdi.PFD_SUPPORT_OPENGL | Gdi.PFD_DRAW_TO_WINDOW | Gdi.PFD_DOUBLEBUFFER; pfd.iPixelType = Gdi.PFD_TYPE_RGBA; //RGBAフォーマット pfd.cColorBits = 32; // 32bit/pixel pfd.cAlphaBits = 8; // アルファチャンネル8bit (0にするとアルファチャンネル無しになる) pfd.cDepthBits = 16; // デプスバッファ16bit // デバイスコンテキストのハンドルを取得。 this.hDC = User.GetDC( this.Handle ); // ピクセルフォーマットを選択。 int pixFormat = Gdi.ChoosePixelFormat( this.hDC, ref pfd ); if( pixFormat <= 0 ) throw new Exception( "ChoosePixelFormat failed." ); // (PIXELFORMATDESCRIPTOR構造体のサイズは40byte。) Wgl.wglDescribePixelFormat( this.hDC, pixFormat, 40, ref pfd ); }
今度は、コントロールのデバイスコンテキストに
先ほどのピクセルフォーマットを設定します。
これには、この関数を使用します。
bool Tao.Platform.Windows.Gdi.SetPixelFormat( IntPtr deviceContext, int pixelFormat, ref Gdi.PIXELFORMATDESCRIPTOR pixelFormatDescriptor );
ちなみに、ピクセルフォーマットの設定は
一つのデバイスコンテキストに一度限りしか使用できないらしいので注意。
ここでもう一つ注意。
なぜかはわからないが、この SetPixelFormat() を呼び出す直前に、
Wgl.wglMakeCurrent( IntPtr.Zero, IntPtr.Zero );
としてwglMakeCurrent()関数を呼び出しておかないと、
SetPixelFormat()が失敗する場合がある様子。
最後に、OpenGLのレンダリングコンテキストを作成して、初期化完了。
レンダリングコンテキストの作成には、次の関数を使用します。
IntPtr Tao.Platform.Windows.Wgl.wglCreateContext( IntPtr hDC );
作成されたレンダリングコンテキストのハンドルが返されるので、
これもコントロールのフィールドとして持っておきます。
以降、このコンテキストを
Tao.Platform.Windows.Wgl.wglCreateContext 関数でカレントに設定して、
これに対して、OpenGLの命令を出していくことになります。
実は、Tao.OpenGl と Tao.Platform.Windows を使用する場合はもうひと手間ありまして、
void Tao.OpenGl.Gl.ReloadFunctions();
void Tao.Platform.Windows.Wgl.ReloadFunctions();
この2つを、レンダリングコンテキストをカレントに設定した後で
1度だけ呼び出しておいた方が良いらしい。
どうも、呼び出しておかないと、
エクステンションの関数を呼び出したときにエラーが発生することがあるっぽい。
// デバイスコンテキストのハンドル IntPtr hDC; // レンダリングコンテキストのハンドル IntPtr hRC; protected override void OnHandleCreated( EventArgs e ) { // ... (省略) ... //よくわからんが、これがないとエラーになることがある。 Wgl.wglMakeCurrent( IntPtr.Zero, IntPtr.Zero ); // デバイスコンテキストにピクセルフォーマットを設定。 bool valid = Gdi.SetPixelFormat( this.hDC, pixFormat, ref pfd ); if( !valid ) throw new Exception( "SetPixelFormat failed" ); // レンダリングコンテキストを作成。 this.hRC = Wgl.wglCreateContext( this.hDC ); if( this.hRC == IntPtr.Zero ) throw new Exception( "wglCreateContext failed." ); Wgl.wglMakeCurrent( this.hDC, this.hRC ); Gl.ReloadFunctions(); Wgl.ReloadFunctions(); }
初期化があれば、終了処理もあるわけで。
OpenGLのレンダリングコンテキストは、
いわゆるアンマネージリソースという部類に入ります。
つまり、自分で明示的にリソースを解放するコードを書かないといけない、ということ。
レンダリングコンテキストの削除・解放には、次の関数を使用します。
bool Tao.Platform.Windows.Wgl.wglDeleteContext( IntPtr oldContext )
引数にレンダリングコンテキストのハンドルを渡せばOK。
で、
アンマネージリソースの解放はDisposeメソッドでやるのがC#のルールなので、
UserControl.Disposeメソッドをオーバーライドしたいところだが、
実は既に、デザイナがこのDisposeをオーバーライドしたコードを自動的に書いてるので、
そこをいじる事になります。
デザイナが自動生成したコードは、
ソリューションエクスプローラのコントロールの左側に表示されている + をクリックすると出てくる
"~.Designer.cs" というファイルです。
このファイルに書かれている
protected override void Dispose( bool disposing ) ...
と書かれているメソッドをいじります。
protected override void Dispose( bool disposing ) { if( disposing && ( components != null ) ) { components.Dispose(); } //レンダリングコンテキストを解放。 Tao.Platform.Windows.Wgl.wglDeleteContext( this.hRC ); base.Dispose( disposing ); }
くれぐれも、
レンダリングコンテキストの解放を忘れないように。
忘れると、
プログラムを終了してもプロセスが残って、さらになぜかCPU使用率が100%になり
タスクマネージャから強制終了させるハメになります。
(Visual Studioのデバッグで実行してるときは、ここまでひどいことにはならないなようですが・・・)
.NET用のOpenGLラッパーには、
上で説明したような初期化の作業を勝手にやってくれる
初期化用のルーチンが用意されているものが幾つかあり、例を挙げると、
・OpenTK
・Tao Framework (Tao.Platform.Windows.dll)
・C# wrapper for OpenGL
・GLSharp
あたりでしょうか。
いずれも、OpenGLで描画するコントロールが用意されていて、
それをメインのフォームに貼り付けるだけでOKだったりしますが、
レンダリングコンテキストの初期化のタイミングとか、指定できるオプションとか、それなりに違いがあるようです。
ちょっと、この4つを私なりに比較してみました。
私としては、おすすめはOpenTK。
これを書いている時点(2008年10月)では、まだバージョンが0.9.1と開発中なので、
今後、未実装の機能が実装される事に期待したい。
GLSharpは私が作っているやつですが、
まぁ、使いやすいことを目標にしているので、
もしお気に召しましたら使ってやってくださいな。
初期化さえできてしまえば、あとはほとんど、
ほかの言語でのOpenGLプログラミングと大差はないです。
ま、ラッパーによってwglSwapBuffersの呼び出し方が微妙に違ったりしますが、
些細なことかと。
ラッパーとしてTaoを使った、
すべて自分でレンダリングコンテキストの初期化を行うサンプルプログラムです。
Visual Studio 2008 Express Editoin のプロジェクトです。
なお、"Tao.OpenGl.dll"と"Tao.Platform.Windows.dll"は含まれていませんので、
各自で入手して組み込んでください。