Thursday, April 16, 2015

OpenGL in C#

Preface


Have you ever want to do some graphics programming but think to yourself: Hey, do I have other options than DirectX/Flash/Silverlight?  Fear not, OpenGL (Open Graphics Library) is an option you've had since the 90's.  Based on research I'd done in regards to generating the world famous glutTeapot via Delphi in the late 90s (don't laugh too hard) it seemed to be a great idea to port over some old school code into C#.

The install


As it's an open source product there are quite a few options to pull from.  For this blog I'm using OpenTK as it was the least hassle install/code conversion over than the other options out there.  This one's a pretty easy install compared to some of the topics I've blogged about in the past.  Click the install below and include the 3 dlls in your project (OpenTK.dll, OpenTK.GLControl, and OpenTK.Compatibility.dll) after you've extracted them from the downloaded file.

Some coding (well, quite a bit)


The glut (GL Users Toolkit) isn't quite available for our purposes, but as luck would have it I found a project out there that used OpenTK and mapped out the necessary vertices to fake the teapot quite well.  The original code within the Teapot class was written by Mark J. Kilgard in 1994.  Below I've created a console application to make use of some gaming capability we have able to us via Visual Studio, plus it looked pretty good in the window :).  Rather than giving a detailed explanation outside of the source code it'll most likely be easier to follow the line by line documentation I did within my project below.



using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;

namespace OpenGLConsoleApp
{
  class MyApplication
  {
    public static class Teapot
    {
      // Rim, body, lid, and bottom data must be reflected in x and
      // y; handle and spout data across the y axis only.
      public static int[,] patchdata = new int[,]
    {
      // rim
      {102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
      // body
      {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27},
      {24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40},
      // lid
      {96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,},
      {0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117},
      // bottom 
      {118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37},
      // handle
      {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56},
      {53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 28, 65, 66, 67},
      // spout
      {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83},
      {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}
    };

      public static float[,] cpdata =
    {
        {0.2f, 0, 2.7f}, {0.2f, -0.112f, 2.7f}, {0.112f, -0.2f, 2.7f}, {0,
        -0.2f, 2.7f}, {1.3375f, 0, 2.53125f}, {1.3375f, -0.749f, 2.53125f},
        {0.749f, -1.3375f, 2.53125f}, {0, -1.3375f, 2.53125f}, {1.4375f,
        0, 2.53125f}, {1.4375f, -0.805f, 2.53125f}, {0.805f, -1.4375f,
        2.53125f}, {0, -1.4375f, 2.53125f}, {1.5f, 0, 2.4f}, {1.5f, -0.84f,
        2.4f}, {0.84f, -1.5f, 2.4f}, {0, -1.5f, 2.4f}, {1.75f, 0, 1.875f},
        {1.75f, -0.98f, 1.875f}, {0.98f, -1.75f, 1.875f}, {0, -1.75f,
        1.875f}, {2, 0, 1.35f}, {2, -1.12f, 1.35f}, {1.12f, -2, 1.35f},
        {0, -2, 1.35f}, {2, 0, 0.9f}, {2, -1.12f, 0.9f}, {1.12f, -2,
        0.9f}, {0, -2, 0.9f}, {-2, 0, 0.9f}, {2, 0, 0.45f}, {2, -1.12f,
        0.45f}, {1.12f, -2, 0.45f}, {0, -2, 0.45f}, {1.5f, 0, 0.225f},
        {1.5f, -0.84f, 0.225f}, {0.84f, -1.5f, 0.225f}, {0, -1.5f, 0.225f},
        {1.5f, 0, 0.15f}, {1.5f, -0.84f, 0.15f}, {0.84f, -1.5f, 0.15f}, {0,
        -1.5f, 0.15f}, {-1.6f, 0, 2.025f}, {-1.6f, -0.3f, 2.025f}, {-1.5f,
        -0.3f, 2.25f}, {-1.5f, 0, 2.25f}, {-2.3f, 0, 2.025f}, {-2.3f, -0.3f,
        2.025f}, {-2.5f, -0.3f, 2.25f}, {-2.5f, 0, 2.25f}, {-2.7f, 0,
        2.025f}, {-2.7f, -0.3f, 2.025f}, {-3, -0.3f, 2.25f}, {-3, 0,
        2.25f}, {-2.7f, 0, 1.8f}, {-2.7f, -0.3f, 1.8f}, {-3, -0.3f, 1.8f},
        {-3, 0, 1.8f}, {-2.7f, 0, 1.575f}, {-2.7f, -0.3f, 1.575f}, {-3,
        -0.3f, 1.35f}, {-3, 0, 1.35f}, {-2.5f, 0, 1.125f}, {-2.5f, -0.3f,
        1.125f}, {-2.65f, -0.3f, 0.9375f}, {-2.65f, 0, 0.9375f}, {-2,
        -0.3f, 0.9f}, {-1.9f, -0.3f, 0.6f}, {-1.9f, 0, 0.6f}, {1.7f, 0,
        1.425f}, {1.7f, -0.66f, 1.425f}, {1.7f, -0.66f, 0.6f}, {1.7f, 0,
        0.6f}, {2.6f, 0, 1.425f}, {2.6f, -0.66f, 1.425f}, {3.1f, -0.66f,
        0.825f}, {3.1f, 0, 0.825f}, {2.3f, 0, 2.1f}, {2.3f, -0.25f, 2.1f},
        {2.4f, -0.25f, 2.025f}, {2.4f, 0, 2.025f}, {2.7f, 0, 2.4f}, {2.7f,
        -0.25f, 2.4f}, {3.3f, -0.25f, 2.4f}, {3.3f, 0, 2.4f}, {2.8f, 0,
        2.475f}, {2.8f, -0.25f, 2.475f}, {3.525f, -0.25f, 2.49375f},
        {3.525f, 0, 2.49375f}, {2.9f, 0, 2.475f}, {2.9f, -0.15f, 2.475f},
        {3.45f, -0.15f, 2.5125f}, {3.45f, 0, 2.5125f}, {2.8f, 0, 2.4f},
        {2.8f, -0.15f, 2.4f}, {3.2f, -0.15f, 2.4f}, {3.2f, 0, 2.4f}, {0, 0,
        3.15f}, {0.8f, 0, 3.15f}, {0.8f, -0.45f, 3.15f}, {0.45f, -0.8f,
        3.15f}, {0, -0.8f, 3.15f}, {0, 0, 2.85f}, {1.4f, 0, 2.4f}, {1.4f,
        -0.784f, 2.4f}, {0.784f, -1.4f, 2.4f}, {0, -1.4f, 2.4f}, {0.4f, 0,
        2.55f}, {0.4f, -0.224f, 2.55f}, {0.224f, -0.4f, 2.55f}, {0, -0.4f,
        2.55f}, {1.3f, 0, 2.55f}, {1.3f, -0.728f, 2.55f}, {0.728f, -1.3f,
        2.55f}, {0, -1.3f, 2.55f}, {1.3f, 0, 2.4f}, {1.3f, -0.728f, 2.4f},
        {0.728f, -1.3f, 2.4f}, {0, -1.3f, 2.4f}, {0, 0, 0}, {1.425f,
        -0.798f, 0}, {1.5f, 0, 0.075f}, {1.425f, 0, 0}, {0.798f, -1.425f,
        0}, {0, -1.5f, 0.075f}, {0, -1.425f, 0}, {1.5f, -0.84f, 0.075f},
        {0.84f, -1.5f, 0.075f}
    };

      public static float[] tex =
    {
      0, 0,
      1, 0,
      0, 1,
      1, 1
    };

      private static void DrawTeapot(int grid, float scale, MeshMode2 type)
      {
        float[] p = new float[48], q = new float[48], r = new float[48], s = new float[48];
        int i, j, k, l;

        GL.PushAttrib(AttribMask.EnableBit | AttribMask.EvalBit);
        GL.Enable(EnableCap.AutoNormal);
        GL.Enable(EnableCap.Normalize);
        GL.Enable(EnableCap.Map2Vertex3);
        GL.Enable(EnableCap.Map2TextureCoord2);

        // time for the math portion: remember augmented matrices?  here's where you use them!
        // prep the matrix for the data to be loaded
        GL.PushMatrix();
        // rotate the view
        GL.Rotate(270.0f, 1.0f, 0.0f, 0.0f);
        // set the size of the data
        GL.Scale(0.5f * scale, 0.5f * scale, 0.5f * scale);
        // move the data via X/Y/Z coordinates
        GL.Translate(0.0f, 0.0f, -1.5f);
        for (i = 0; i < 10; i++)
        {
          for (j = 0; j < 4; j++)
          {
            for (k = 0; k < 4; k++)
            {
              for (l = 0; l < 3; l++)
              {
                p[j * 12 + k * 3 + l] = cpdata[patchdata[i, j * 4 + k], l];
                q[j * 12 + k * 3 + l] = cpdata[patchdata[i, j * 4 + (3 - k)], l];
                if (l == 1)
                  q[j * 12 + k * 3 + l] *= -1.0f;
                if (i < 6)
                {
                  r[j * 12 + k * 3 + l] = cpdata[patchdata[i, j * 4 + (3 - k)], l];
                  if (l == 0)
                    r[j * 12 + k * 3 + l] *= -1.0f;
                  s[j * 12 + k * 3 + l] = cpdata[patchdata[i, j * 4 + k], l];
                  if (l == 0)
                    s[j * 12 + k * 3 + l] *= -1.0f;
                  if (l == 1)
                    s[j * 12 + k * 3 + l] *= -1.0f;
                }
              }
            }
          }

          // high level math for the texture coordinates
          GL.Map2(MapTarget.Map2TextureCoord2, 0f, 1f, 2, 2, 0f, 1f, 4, 2, tex);
          // high level math for the vertices
          GL.Map2(MapTarget.Map2Vertex3, 0f, 1f, 3, 4, 0f, 1f, 12, 4, p);
          // high level math for a 2 dimensional map
          GL.MapGrid2(grid, 0.0, 1.0, grid, 0.0, 1.0);
          // high level math to do the evaluation of the grids
          GL.EvalMesh2(type, 0, grid, 0, grid);
          // high level math for the vertices
          GL.Map2(MapTarget.Map2Vertex3, 0, 1, 3, 4, 0, 1, 12, 4, q);
          // high level math to do the evaluation of the grids
          GL.EvalMesh2(type, 0, grid, 0, grid);
          if (i < 6)
          {
            // high level math for the vertices
            GL.Map2(MapTarget.Map2Vertex3, 0, 1, 3, 4, 0, 1, 12, 4, r);
            // high level math to do the evaluation of the grids
            GL.EvalMesh2(type, 0, grid, 0, grid);
            // high level math for the vertices
            GL.Map2(MapTarget.Map2Vertex3, 0, 1, 3, 4, 0, 1, 12, 4, s);
            // high level math to do the evaluation of the grids
            GL.EvalMesh2(type, 0, grid, 0, grid);
          }
        }

        // release the manipulated data from the matrix
        GL.PopMatrix();
        // release the manipulated data from the matrix attributes
        GL.PopAttrib();
      }

      public static void DrawSolidTeapot(float scale)
      {
        DrawTeapot(14, scale, MeshMode2.Fill);
      }

      public static void DrawWireTeapot(float scale)
      {
        DrawTeapot(10, scale, MeshMode2.Line);
      }

      public static void DrawPointTeapot(float scale)
      {
        DrawTeapot(10, scale, MeshMode2.Point);
      }

    }

    private static int teapotList;

    [STAThread]
    public static void Main()
    {
      using (var game = new GameWindow())
      {
        game.Load += (sender, e) =>
        {
          // setup settings, load textures, sounds
          game.VSync = VSyncMode.On;

          // easier to create float arrays in advance
          float[] ambient = { 0.0f, 0.0f, 0.0f, 1.0f };
          float[] diffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
          float[] specular = { 1.0f, 1.0f, 1.0f, 1.0f };
          float[] position = { 0.0f, 3.0f, 3.0f, 0.0f };
          float[] lmodel_ambient = { 0.2f, 0.2f, 0.2f, 1.0f };
          float[] local_view = { 0.0f };

          // setup your light source(s)
          GL.Light(LightName.Light0, LightParameter.Ambient, ambient);
          GL.Light(LightName.Light0, LightParameter.Diffuse, diffuse);
          GL.Light(LightName.Light0, LightParameter.Position, position);
          GL.LightModel(LightModelParameter.LightModelAmbient, lmodel_ambient);
          GL.LightModel(LightModelParameter.LightModelLocalViewer, local_view);

          GL.FrontFace(FrontFaceDirection.Cw);
          GL.Enable(EnableCap.Lighting);
          GL.Enable(EnableCap.Light0);
          GL.Enable(EnableCap.AutoNormal);
          GL.Enable(EnableCap.Normalize);
          GL.Enable(EnableCap.DepthTest);

          GL.NewList(GL.GenLists(1), ListMode.Compile);

          // teapot time
          Teapot.DrawSolidTeapot(1.0f);
          GL.EndList();
        };

        game.Resize += (sender, e) =>
        {
          // setup the viewer for your image(s)
          GL.Viewport(0, 0, game.Width, game.Height);
        };

        game.UpdateFrame += (sender, e) =>
        {
          // add game logic, input handling
          if (game.Keyboard[Key.Escape])
          {
            game.Exit();
          }
        };

        game.RenderFrame += (sender, e) =>
        {
          // step 1: clear the buffer
          GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

          // step 2: render the teapot as you see fit
          // press S for a solid teapot
          if (game.Keyboard[Key.S])
            Teapot.DrawSolidTeapot(0.5f);
          // press W for a wire frame teapot
          else if (game.Keyboard[Key.W])
            Teapot.DrawWireTeapot(0.5f);
          // press P for a point frame teapot
          else if (game.Keyboard[Key.P])
            Teapot.DrawPointTeapot(0.5f);

          // step 3: force the execution of your GL code
          GL.Flush();

          // step 4: swap the buffers to display your code
          game.SwapBuffers();
        };

        // Run the game at 60 updates per second
        game.Run(60.0);
      }
    }
  }
}

There's decent documentation within the above source code, the output below:

Solid:

Wire:

Pixel:
















Now for some real fun: code some buttons so when you hit certain keys you change the viewing angle so you can rotate the teapot!  Heck, figure out how to change the colors of the teapot on the fly so it looks like a magical teapot.  Enjoy!

Source(s):

No comments:

Post a Comment