OpenTKを使って立方体を描画してみましょう。
OpenGLはC#では使えないのか? 疑問に思い調べてみたところ、OpenTK というライブラリがあることがわかりました。そこでOpenTKを使ってみることにします。
OpenTKはOpenGLのコマンドをそのまま使えるようなシンプルさが特徴です。
まずはここからダウンロードしてインストールしましょう。
opentk-1.0.0-rc1.exeというファイルをダウンロードすることができるのでインストールしてみましょう。
[ソリューション エクスプローラー] の中から [参照設定] を右クリックして、[参照の追加] を選択します。するとダイアログが出てくるので、OpenTK.dll と OpenTK.GLControl.dll の2つを選択して追加します。
しかしこれだけでは自動で [ツールボックス] にOpenTK のコントロールが表示されません。そこでどうするか? Form1.Designer.csを編集する方法もあるのですが、OpenTK.GLControlを継承して新しいクラスを作成します。
1 2 3 |
public class GLControlEx: OpenTK.GLControl { } |
これならForm1.Designer.csを編集しなくても、[ツールボックス] にGLControlExが表示されるようになります。
まずコントロールがロードされたらProjection の設定と視界の設定をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { private void glControlEx1_Load(object sender, EventArgs e) { GL.ClearColor(glControl.BackColor); // Projection の設定 SetProjection(); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); // 視界の設定 SetInitSight(); glControl.Refresh(); } } |
SetProjection()メソッドでやることはビューポートの設定と視体積の設定です。視体積の設定するとき MatrixMode を変更するのですが、終わったらGL.MatrixMode(MatrixMode.Modelview)メソッドで戻します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class Form1 : Form { private void SetProjection() { // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); // 視体積の設定 GL.MatrixMode(MatrixMode.Projection); Matrix4 proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver3, glControl.AspectRatio, 0.01f, 10.0f); GL.LoadMatrix(ref proj); // MatrixMode を元に戻す GL.MatrixMode(MatrixMode.Modelview); } } |
視界の設定をしているのが自作メソッドのSetInitSight()です。視点を(0, 0, 3.0)の位置に設定しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { void SetInitSight() { float x = 0f; float y = 0f; float z = 3.0f; // 視界の設定 Vector3 vector3 = new Vector3(x, y, z); Matrix4 look = Matrix4.LookAt(vector3, Vector3.Zero, Vector3.UnitY); GL.LoadMatrix(ref look); } } |
それからフォームの大きさがかわったときにコントロールの大きさを変更します。クライアント領域にいっぱいに貼り付けるわけです。そこで glControl_Resize メソッドを呼び出し、glControlが全面に表示されるようにしています。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form1 : Form { private void glControl_Resize(object sender, EventArgs e) { glControl.Dock = DockStyle.Fill; // 再描画 glControl.Refresh(); } } |
glControl.Refresh();が実行されるとPaintイベントが発生するのですが、このときに何を表示させるのかを記述する必要があります。ここでは立方体を表示させます。そして立体に見えるように光が当たっている部分と影になっている部分をつくります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public partial class Form1 : Form { private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); Lighting(); // 立方体を描画 DrawCube(); glControl.SwapBuffers(); } void Lighting() { // 光源の使用 GL.Enable(EnableCap.Lighting); // ライト 0 の設定と使用 float[] position = new float[] { 1.0f, 2.0f, 3.0f, 0.0f }; GL.Light(LightName.Light0, LightParameter.Position, position); GL.Enable(EnableCap.Light0); } } |
DrawCube()は立方体を表示させるための自作メソッドです。原点を中心に6つの正方形の平面を描画しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
public partial class Form1 : Form { private void DrawCube() { double w = 0.7, h = 0.7, d = 0.7; GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color.Red); // 右面 GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitX); GL.Vertex3(w, h, -d); GL.Vertex3(w, h, d); GL.Vertex3(w, -h, -d); GL.Vertex3(w, -h, d); } GL.End(); // 左面 GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitX); GL.Vertex3(-w, -h, -d); GL.Vertex3(-w, -h, d); GL.Vertex3(-w, h, -d); GL.Vertex3(-w, h, d); } GL.End(); // 上面 GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitY); GL.Vertex3(w, h, -d); GL.Vertex3(-w, h, -d); GL.Vertex3(w, h, d); GL.Vertex3(-w, h, d); } GL.End(); // 下面 GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitY); GL.Vertex3(-w, -h, d); GL.Vertex3(-w, -h, -d); GL.Vertex3(w, -h, d); GL.Vertex3(w, -h, -d); } GL.End(); // 手前の面 GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(Vector3.UnitZ); GL.Vertex3(w, h, d); GL.Vertex3(-w, h, d); GL.Vertex3(w, -h, d); GL.Vertex3(-w, -h, d); } GL.End(); // 奥の面 GL.Begin(BeginMode.TriangleStrip); { GL.Normal3(-Vector3.UnitZ); GL.Vertex3(w, -h, -d); GL.Vertex3(-w, -h, -d); GL.Vertex3(w, h, -d); GL.Vertex3(-w, h, -d); } GL.End(); } } |
これで実行すると立方体が表示されますが、立方体ではなくただの正方形です。最前面にある面の向こう側に他の面が隠れているからです。立方体らしくするためには立方体を斜めの位置からみる必要があります。そこで視点を変更できるようにします。
方向キーが押されたら視点をその方向に移動させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public partial class Form1 : Form { [System.Security.Permissions.UIPermission( System.Security.Permissions.SecurityAction.Demand, Window = System.Security.Permissions.UIPermissionWindow.AllWindows)] protected override bool ProcessDialogKey(Keys keyData) { //左キーが押されているか調べる if((keyData & Keys.KeyCode) == Keys.Left) { OnLeft(); //左キーの本来の処理(左側のコントロールにフォーカスを移す)を //させたくないときは、trueを返す return true; } else if((keyData & Keys.KeyCode) == Keys.Right) { OnRight(); return true; } else if((keyData & Keys.KeyCode) == Keys.Up) { OnUp(); return true; } else if((keyData & Keys.KeyCode) == Keys.Down) { OnDown(); return true; } return base.ProcessDialogKey(keyData); } } |
視点を立方体を中心に球面上を移動するように移動させます。どれだけ移動したかはフィールド変数に格納して記憶しておきます。自作メソッドのDraw()では新しい視点の設定とglControl.Refresh()メソッドの呼び出しを行ないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public partial class Form1 : Form { float a = 0; float b = 0; void OnLeft() { a -= 0.1f; Draw(); } void OnRight() { a += 0.1f; Draw(); } void OnUp() { b += 0.1f; Draw(); } void OnDown() { b -= 0.1f; Draw(); } void Draw() { GL.ClearColor(glControl.BackColor); SetOfSight(); glControl.Refresh(); } } |
自作メソッドSetOfSight()で新しい場所に視界を設定するのですが、視界が上下に移動する場合、向こう側に移動してしまうと上下が逆になってしまいます。そのためMath.Cos(b)が負数になる場合は上下を反転しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public partial class Form1 : Form { void SetOfSight() { // x = r×sin(a)cos(b) // y = r×sin(a)sin(b) // z = r×cos(a) float x = 3.0f * (float)Math.Sin(a) * (float)Math.Cos(b); float y = 3.0f * (float)Math.Sin(b); float z = 3.0f * (float)Math.Cos(a) * (float)Math.Cos(b); string str = String.Format("x={0} y={1} z={2}", x, y, z); Console.WriteLine(str); // 視界の設定 Vector3 vector3 = new Vector3(x, y, z); if((float)Math.Cos(b) >= 0) { Matrix4 look = Matrix4.LookAt(vector3, Vector3.Zero, Vector3.UnitY); GL.LoadMatrix(ref look); } else { Matrix4 look = Matrix4.LookAt(vector3, Vector3.Zero, -Vector3.UnitY); GL.LoadMatrix(ref look); } } } |
質問させて下さい。
openTK(私の場合はVB.net+WPF)で立方体を描画するテストプログラムを作りましたが、大量の描画をするとメモリーオーバーになります。
描画につれて空きメモリーは減少していきます。立方体を塗りつぶすと、ワイヤーフレームだけの描画よりもメモリーの消費が増えるので、OpenTKによる描画がメモリーオーバーの原因だと考えています。
ネットで示されているあらゆる方法を試しましたが、メモリーを解放できません。GC.Collectも効果がありません。
筆者さんの条件では、大量描画でメモリーの消費はどうなりますか?
先回の投稿に誤りがありました。
描画を大量に行うのではなく、御提示のglControlEx1_Paintを何度も繰り返してみてください。
シェーダーに送った頂点データーや色データーはその都度上書きされて参照が消えるはずですが、GCの対象とはならず、メモリーが消費されるだけになってしまいます。
解決方法はあるのでしょうか。
コメントありがとうございます。
glControlEx1_Paintを何度も繰り返すとメモリーが消費されるだけ。
GC.Collectも効果なし
とのことですが、このサイトで公開しているプログラムではタイマーをセットして0.05秒おきに何度もglControlEx1_Paintを呼び出すとということをしていますが、メモリーが足りなくなるということはありません。
「シェーダーに送った頂点データーや色データー」をそのままフィールド変数に保存し、いらなくなってもそのままにしているということはないでしょうか?
これではGCの対象にはなりません。
それから物理メモリーはどれくらい用意していますか? 当方が使用しているのはネットショップで購入した中古のパソコンでメモリは8ギガバイトです。メモリーが少なすぎると問題がおきるかもしれません。