Thursday, February 2, 2017

OpenGL 4 with OpenTK in C# Part 6: Rotations and Movement of objects


In this post we will look at how to manipulate an object by rotating and moving it around.

This is part 6 of my series on OpenGL4 with OpenTK.
For other posts in this series:
OpenGL 4 with OpenTK in C# Part 1: Initialize the GameWindow
OpenGL 4 with OpenTK in C# Part 2: Compiling shaders and linking them
OpenGL 4 with OpenTK in C# Part 3: Passing data to shaders
OpenGL 4 with OpenTK in C# Part 4: Refactoring and adding error handling
OpenGL 4 with OpenTK in C# Part 5: Buffers and Triangle
OpenGL 4 with OpenTK in C# Part 6: Rotations and Movement of objects
OpenGL 4 with OpenTK in C# Part 7: Vectors and Matrices
OpenGL 4 with OpenTK in C# Part 8: Drawing multiple objects
OpenGL 4 with OpenTK in C# Part 9: Texturing
OpenGL 4 with OpenTK in C# Part 10: Asteroid Invaders
OpenGL 4 with OpenTK in C# Part 11: Mipmap
OpenGL 4 with OpenTK in C# Part 12: Basic Moveable Camera
OpenGL 4 with OpenTK in C# Part 13: IcoSphere
OpenGL 4 with OpenTK in C# Part 14: Basic Text

As stated in the previous post, I am in no way an expert in OpenGL. I write these posts as a way to learn and if someone else finds these posts useful then all the better :)
If you think that the progress is slow, then know that I am a slow learner :P
This part will build upon the game window and shaders from the previous post.

3D Cube

Ah, the old cube that is used in all tutorials is back.
Lets create a helper class, ObjectFactory, that emits the vertices for a cube (single color)
public class ObjectFactory
{
 public static Vertex[] CreateSolidCube(float side, Color4 color)
 {
  side = side/2f; // half side - and other half
  Vertex[] vertices =
  {
   new Vertex(new Vector4(-side, -side, -side, 1.0f),   color),
   new Vertex(new Vector4(-side, -side, side, 1.0f),    color),
   new Vertex(new Vector4(-side, side, -side, 1.0f),    color),
   new Vertex(new Vector4(-side, side, -side, 1.0f),    color),
   new Vertex(new Vector4(-side, -side, side, 1.0f),    color),
   new Vertex(new Vector4(-side, side, side, 1.0f),     color),

   new Vertex(new Vector4(side, -side, -side, 1.0f),    color),
   new Vertex(new Vector4(side, side, -side, 1.0f),     color),
   new Vertex(new Vector4(side, -side, side, 1.0f),     color),
   new Vertex(new Vector4(side, -side, side, 1.0f),     color),
   new Vertex(new Vector4(side, side, -side, 1.0f),     color),
   new Vertex(new Vector4(side, side, side, 1.0f),      color),

   new Vertex(new Vector4(-side, -side, -side, 1.0f),   color),
   new Vertex(new Vector4(side, -side, -side, 1.0f),    color),
   new Vertex(new Vector4(-side, -side, side, 1.0f),    color),
   new Vertex(new Vector4(-side, -side, side, 1.0f),    color),
   new Vertex(new Vector4(side, -side, -side, 1.0f),    color),
   new Vertex(new Vector4(side, -side, side, 1.0f),     color),

   new Vertex(new Vector4(-side, side, -side, 1.0f),    color),
   new Vertex(new Vector4(-side, side, side, 1.0f),     color),
   new Vertex(new Vector4(side, side, -side, 1.0f),     color),
   new Vertex(new Vector4(side, side, -side, 1.0f),     color),
   new Vertex(new Vector4(-side, side, side, 1.0f),     color),
   new Vertex(new Vector4(side, side, side, 1.0f),      color),

   new Vertex(new Vector4(-side, -side, -side, 1.0f),   color),
   new Vertex(new Vector4(-side, side, -side, 1.0f),    color),
   new Vertex(new Vector4(side, -side, -side, 1.0f),    color),
   new Vertex(new Vector4(side, -side, -side, 1.0f),    color),
   new Vertex(new Vector4(-side, side, -side, 1.0f),    color),
   new Vertex(new Vector4(side, side, -side, 1.0f),     color),

   new Vertex(new Vector4(-side, -side, side, 1.0f),    color),
   new Vertex(new Vector4(side, -side, side, 1.0f),     color),
   new Vertex(new Vector4(-side, side, side, 1.0f),     color),
   new Vertex(new Vector4(-side, side, side, 1.0f),     color),
   new Vertex(new Vector4(side, -side, side, 1.0f),     color),
   new Vertex(new Vector4(side, side, side, 1.0f),      color),
  };
  return vertices;
 }
}
And replace the triangle initialization with a call to it in the OnLoad method in the GameWindow
protected override void OnLoad(EventArgs e)
{
    Vertex[] vertices = ObjectFactory.CreateSolidCube(0.2f, Color4.HotPink);
    _renderObjects.Add(new RenderObject(vertices));

    CursorVisible = true;

    _program = CreateProgram();
    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
    GL.PatchParameter(PatchParameterInt.PatchVertices, 3);
    GL.Enable(EnableCap.DepthTest);
    Closed += OnClosed;
}

This should result in something like the following;
So nothing fancy at all. As you can see the lines are drawn thanks to the following line in the OnLoad, it was PolygonMode.Fill before and now it says PolygonMode.Line.
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
and each side of the cube is drawn as 2 triangles.
So lets get this cube moving

Rotating

I am really bad at math. Thankfully a lot of this is provided by OpenTK.

Matrices

We will use matrices to rotate and move (translate) objects in 3D.
Our first goal is to create a model=>view matrix. The purpose of this matrix is to transform the coordinates of each vertex from model space (i.e. what we create above for the cube) to coordinates in the view space.

Vertex Shader
First lets update our vertex shader so that it can take a matrix input and then transform the vertex position with it.
#version 450 core

layout (location = 0) in vec4 position;
layout(location = 1) in vec4 color;

out vec4 vs_color;

layout (location = 20) uniform  mat4 modelView;

void main(void)
{
 gl_Position = modelView * position;
 vs_color = color;
}
Note the uniform keyword here, what it says is that this is no ordinary attribute that could differ between vertices but constant for this batch.

Having done so, lets add some rotations to the cube so that we can see all sides of it.
In our OnUpdateFrame, lets add a little more then just the keyboard handler
private Matrix4 _modelView;
protected override void OnUpdateFrame(FrameEventArgs e)
{
 _time += e.Time;
 var k = (float)_time*0.05f;
 var r1 = Matrix4.CreateRotationX(k * 13.0f);
 var r2 = Matrix4.CreateRotationY(k * 13.0f);
 var r3 = Matrix4.CreateRotationZ(k * 3.0f);
 _modelView = r1 * r2 * r3;

 HandleKeyboard();
}
And update our OnRenderFrame to provide the new matrix to our shader with the GL.UniformMatrix4 command.
protected override void OnRenderFrame(FrameEventArgs e)
{
    Title = $"{_title}: (Vsync: {VSync}) FPS: {1f / e.Time:0}";
    GL.ClearColor(_backColor);
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

    GL.UseProgram(_program);
    GL.UniformMatrix4(20,              // match the layout location in the shader
                      false,           // transpose
                      ref _modelView); // our matrix
    foreach (var renderObject in _renderObjects)
    {
        renderObject.Render();
    }
    SwapBuffers();
}

Output should be something like this:

Translating (moving)

Moving the object around is the next step, this we will be doing with the help of Translations.
Add the following to the OnUpdateFrame method to make the object circle around the center of the screen at a leisurely pace.
var t1 = Matrix4.CreateTranslation(
    (float) (Math.Sin(k * 5f) * 0.5f),
    (float) (Math.Cos(k * 5f) * 0.5f),
    0f);
_modelView = r1*r2*r3*t1;

Should give the following result

Be aware that the order of the multiplications is really important,
_modelView = t1*r1*r2*r3;
Yields in a totally different experience



For the full source at the end of part 6, go to: https://github.com/eowind/dreamstatecoding

Hope this helps someone out there :)
I must sadly inform that there is no cat video for this part, hopefully they will do something fun in front of the camera until next post.

Until next time: Work to Live, Don’t Live to Work

1 comment:

  1. Turns out I had the vertex order wrong in the CreateSolidCube method.
    If you turned on the face culling (telling opengl to not render backsides of triangles, things looked shady). The code in the article is updated with correct vertex order.

    ReplyDelete