CSharpで始めるOpenGLプログラミング/立方体を表示する

出典: フリー教科書『ウィキブックス(Wikibooks)』

プロジェクトを作成するで作成したMainWindow.csを次のように書き換えます。

using System;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
namespace Test3DProject
{
    public class MainWindow : GameWindow
    {
        public MainWindow(int width, int height, GraphicsMode mode, string title)
            : base(width, height, mode, title)
        {
            this.VSync = VSyncMode.On;
        }


        int vBufName;
        double[] vBufData = new double[]
        {60.0, 120.0, 60.0, //面1
        120.0, 120.0, 60.0,
        120.0, 120.0, 0.0,
        60.0, 120.0, 0.0,
        60.0, 180.0, 60.0, //面2
        120.0, 180.0, 60.0,
        120.0, 180.0, 0.0,
        60.0, 180.0, 0.0,
            60.0, 120.0, 60.0, //面3
            60.0, 180.0, 60.0,
            60.0, 180.0, 0.0,
            60.0, 120.0, 0.0,
            120.0, 120.0, 60.0, //面4
            120.0, 180.0, 60.0,
            120.0, 180.0, 0.0,
            120.0, 120.0, 0.0,
            60.0, 180.0, 0.0, //面5
            120.0, 180.0 ,0.0,
            120.0, 120.0, 0.0,
            60.0, 120.0, 0.0,
            60.0, 180.0, 60.0, //面6
            120.0, 180.0, 60.0,
            120.0, 120.0, 60.0,
            60.0, 120.0, 60.0,
        };
		protected override void OnLoad(EventArgs e)
		{
            base.OnLoad(e);
            GL.Enable(EnableCap.DepthTest);
            //カメラ
            //Matrix4 camera = Matrix4.LookAt(0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
            Matrix4 camera = Matrix4.LookAt(0.0f, 0.0f, 200.0f, 0.3f, 0.3f, -1.0f, 0.0f, 0.0f, 1.0f);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref camera);

            //視体積を設定する
            Matrix4 perspective = Matrix4.CreatePerspectiveFieldOfView(1.8f, 1.0f, 1.0f, 200.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref perspective);

            //MatrixModeをModelviewに戻す (これがないと回転等の動作が正しく行なえません。)
            GL.MatrixMode(MatrixMode.Modelview);

            //立方体の頂点
            vBufName = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ArrayBuffer, vBufName);
            GL.BufferData<double>(BufferTarget.ArrayBuffer, sizeof(double) * vBufData.Length, vBufData, BufferUsageHint.StaticDraw);
		}

		protected override void OnResize(EventArgs e)
		{
            base.OnResize(e);
            GL.Viewport(0, 0, this.Width, this.Height);
		}

		protected override void OnUpdateFrame(FrameEventArgs e)
		{
            base.OnUpdateFrame(e);
		}

		protected override void OnRenderFrame(FrameEventArgs e)
		{
            base.OnRenderFrame(e);
            GL.ClearColor(0.0f, 1.0f, 0.0f, 1.0f); //緑色をセットする
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); //バッファ一面を緑色にする

            GL.EnableClientState(ArrayCap.VertexArray);
            GL.BindBuffer(BufferTarget.ArrayBuffer, vBufName);
            GL.VertexPointer(3, VertexPointerType.Double, 0, 0);

            //色を変えて2面ずつ描画する
            GL.Color4(1.0f, 0.0f, 0.0f, 1.0f);
            GL.DrawArrays(PrimitiveType.Quads, 0, 4);
            GL.DrawArrays(PrimitiveType.Quads, 4, 4);
            GL.Color4(0.0f, 0.0f, 1.0f, 1.0f);
            GL.DrawArrays(PrimitiveType.Quads, 8, 4);
            GL.DrawArrays(PrimitiveType.Quads, 12, 4);
            GL.Color4(1.0f, 1.0f, 0.0f, 1.0f);
            GL.DrawArrays(PrimitiveType.Quads, 16, 4);
            GL.DrawArrays(PrimitiveType.Quads, 20, 4);

            SwapBuffers();
		}

		protected override void OnUnload(EventArgs e)
		{
            base.OnUnload(e);
		}
	}
}

プログラムを書き換えたら、「▶」ボタンをクリックしてプログラムを起動します。3色の立方体が画面上部に描画されるはずです。

プログラム解説[編集]

コンストラクタ[編集]

       public MainWindow(int width, int height, GraphicsMode mode, string title)

            : base(w height, mode, title)

        {

            this.VSync = VSyncMode.On;

        }

VSyncプロパティをOnに設定し、垂直同期を有効にしています。垂直同期とは、画面の更新頻度にバッファの更新頻度を合わせることです。たとえば、1秒間に60回しか画面を書き換えることしかできないディスプレイを使用しているのに、1秒間に180回バッファを書き換えても意味がないばかりか、テアリングと呼ばれる現象を発生させることがあります。これを抑制するために垂直同期を有効にします。

フィールド[編集]

        int vBufName;
        double[] vBufData = new double[]
        {60.0, 120.0, 60.0, //面1
        120.0, 120.0, 60.0,
        120.0, 120.0, 0.0,
        60.0, 120.0, 0.0,
        60.0, 180.0, 60.0, //面2
        120.0, 180.0, 60.0,
        120.0, 180.0, 0.0,
        60.0, 180.0, 0.0,
            60.0, 120.0, 60.0, //面3
            60.0, 180.0, 60.0,
            60.0, 180.0, 0.0,
            60.0, 120.0, 0.0,
            120.0, 120.0, 60.0, //面4
            120.0, 180.0, 60.0,
            120.0, 180.0, 0.0,
            120.0, 120.0, 0.0,
            60.0, 180.0, 0.0, //面5
            120.0, 180.0 ,0.0,
            120.0, 120.0, 0.0,
            60.0, 120.0, 0.0,
            60.0, 180.0, 60.0, //面6
            120.0, 180.0, 60.0,
            120.0, 120.0, 60.0,
            60.0, 120.0, 60.0,
        };

vBufNameは、VRAM(ビデオRAMの略で、グラフィックボードの中に存在します)上に配置するデータを識別する番号です。ここでは初期化していませんが、後で代入します。 vBufDataは立方体の頂点データです。3次元空間では1つの座標につき3つの数値が必要(x, y, z)で、立方体を正方形6個に分けて描画しているため、全部で3 x 4 x 6 = 72個の数値が必要です。

OnLoadメソッド[編集]

		protected override void OnLoad(EventArgs e)
		{
            base.OnLoad(e);
            GL.Enable(EnableCap.DepthTest);
            //カメラ
            //Matrix4 camera = Matrix4.LookAt(0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
            Matrix4 camera = Matrix4.LookAt(0.0f, 0.0f, 200.0f, 0.3f, 0.3f, -1.0f, 0.0f, 0.0f, 1.0f);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref camera);

            //視体積を設定する
            Matrix4 perspective = Matrix4.CreatePerspectiveFieldOfView(1.8f, 1.0f, 1.0f, 200.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref perspective);

            //立方体の頂点
            vBufName = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ArrayBuffer, vBufName);
            GL.BufferData<double>(BufferTarget.ArrayBuffer, sizeof(double) * vBufData.Length, vBufData, BufferUsageHint.StaticDraw);
		}

OnLoadメソッドは、ウィンドウがロードされるときに呼び出されます。GL.Enable(EnableCap.DepthTest);でデプステストと呼ばれるOpenGLの機能を有効化しています。デプステストとは、物体の前後関係を意識して描画する機能です。デプステストが無効な状態で奥に存在する物体を手前に存在する物体より後に描画すると、奥の物体に手前の物体が上書きされてしまうという現象が発生します。

カメラは、3次元空間を「どの位置から」「どの方向に」「どこを上方向として」眺めるかを設定します。この例では、x=0.0, y=0.0, z=200.0の位置から、x=0.3, y=0.3, z=-1.0の方向を見て、x=0.0, y=0.0, z=1.0を上方向として3次元空間を眺めることにしています。

視体積は、3次元空間をどのように切り取って表示するか設定しています。3次元空間は広く、すべての物体を描画することは不可能なために設定する必要があります。Matrix4.CreatePerspectiveFieldOfView()メソッドでプロジェクション行列と呼ばれるものを作成iし、GL.MatrixMode()メソッドとGL.LoadMatrix()メソッドでOpenGLにプロジェクション行列を読み込ませます。Matrix4.CreatePerspectiveFieldOfViewメソッドは、第一引数が視野角(ラジアン)、第二引数がアスペクト比(横の長さ/縦の長さ)、第三引数がニア(カメラからどれだけ離れた面から描画するか。0に設定することは不可。)、第四引数がファー(カメラからどれだけ離れた面まで描画するか。)となっています。

最後に立方体の頂点をメインメモリからVRAMに転送しています。まず、GL.GenBuffer()でVRAM上のデータを識別する番号を作成しています。次にGL.BindBuffer()メソッドで、GL.BufferData()メソッドでデータを書き込む先(vBufNameが指す場所)を指定しています。そしていよいよGL.BufferData()メソッドでVRAMに頂点データを転送します。GL.BufferData()は、第二引数がデータの大きさ(sizeof(double)=8バイト x 72個分)、第三引数が頂点データ、第四引数が領域の使い方です。第四引数は何を指定しても良いのですが、領域の使い方にあったものにしましょう。今回、頂点データは1度書き込んだら変更することはしないので、BufferUsageHint.StaticDrawにしています。

OnRenderFrame()メソッド[編集]

		protected override void OnRenderFrame(FrameEventArgs e)
		{
            base.OnRenderFrame(e);
            GL.ClearColor(0.0f, 1.0f, 0.0f, 1.0f); //緑色をセットする
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); //バッファ一面を緑色にする

            GL.EnableClientState(ArrayCap.VertexArray);
            GL.BindBuffer(BufferTarget.ArrayBuffer, vBufName);
            GL.VertexPointer(3, VertexPointerType.Double, 0, 0);

            //色を変えて2面ずつ描画する
            GL.Color4(1.0f, 0.0f, 0.0f, 1.0f);
            GL.DrawArrays(PrimitiveType.Quads, 0, 4);
            GL.DrawArrays(PrimitiveType.Quads, 4, 4);
            GL.Color4(0.0f, 0.0f, 1.0f, 1.0f);
            GL.DrawArrays(PrimitiveType.Quads, 8, 4);
            GL.DrawArrays(PrimitiveType.Quads, 12, 4);
            GL.Color4(1.0f, 1.0f, 0.0f, 1.0f);
            GL.DrawArrays(PrimitiveType.Quads, 16, 4);
            GL.DrawArrays(PrimitiveType.Quads, 20, 4);

            SwapBuffers();
		}

GL.ClearColor()でバッファの背景色を指定しています。赤、緑、青、不透明度の順に指定します。ここでは不透明度100%の緑色を指定しています。 次にGL.Clear()でバッファをクリアしています。GL.ClearColor()を呼び出してもGL.Clear()を呼び出さなければバッファはクリアされないので注意してください。 また、GL.EnableClientState()で頂点配列を有効化します。GL.VertexPointer()は頂点配列がどういうデータなのかをOpenGLに通知します。第一引数が座標あたりの次元数(x,y,zで3)、第二引数がデータ型、第三引数が頂点データと頂点データの間隔(単位:バイト)です。0を指定すると、頂点データは隙間なく配列に格納されていることになります。 第四引数は、配列の最初の頂点データの場所です。vBufDataは要素0から頂点データが格納されているため、0です。

最後に立方体を2面ずつ色を分けて描画しています。 GL.Color4()で描画色を設定し、GL.DrawArrays()で長方形を描画しています。GL.DrawArrays()の第一引数は図形の形、第二引数は頂点データの先頭、第三引数は頂点の数を表します。たとえばGL.DrawArrays(PrimitiveType.Quads, 12, 4)の場合、長方形を頂点データの12個目の頂点から4つ分の頂点を取り出して描画する、という意味になります。

SwapBuffers()については筆者もまだあまり理解していませんが、必ずOnRenderFrame()の最後に書くようにしてください。