Tuesday, February 14, 2017

OpenGL 4 with OpenTK in C# Part 12: Basic Movable Camera


In this post we will create some basic cameras in OpenGL with the help of OpenTK.

This is part 12 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 part 11, including homing bullets from this post.

Camera

At this point, it is quite easy to implement a movable camera, it is actually just another level of matrix multiplication that we do when we setup the ModelView matrix for each object. So lets start there by modifying the Render method of the AGameObject class to take a camera as input.
public virtual void Render(ICamera camera)
{
    _model.Bind();
    var t2 = Matrix4.CreateTranslation(_position.X, _position.Y, _position.Z);
    var r1 = Matrix4.CreateRotationX(_rotation.X);
    var r2 = Matrix4.CreateRotationY(_rotation.Y);
    var r3 = Matrix4.CreateRotationZ(_rotation.Z);
    var s = Matrix4.CreateScale(_scale);
    _modelView = r1*r2*r3*s*t2*camera.LookAtMatrix;
    GL.UniformMatrix4(21, false, ref _modelView);
    _model.Render();
}

In this case our cameras should be able to provide a LookAtMatrix and be able to update themselves. So the interface looks like this:
public interface ICamera
{
    Matrix4 LookAtMatrix{ get; }
    void Update(double time, double delta);
}
The update method is called from the OnFrameUpdate override in the GameWindow and the look at matrix is used for each object rendered in the OnRenderFrame override.

So, lets look at some cameras

Default: Static Camera


This camera is basically what we have out of the box and been using so far. It is located at origin (0, 0, 0) and is pointed towards the negative Z axis. I.e. into the screen (remember right handed coordinate system).
Lets create a camera that implements this camera so that we can change back to it whenever we want to.
public class StaticCamera : ICamera
{
    public Matrix4 LookAtMatrix { get; }
    public StaticCamera()
    {
        Vector3 position;
        position.X = 0;
        position.Y = 0;
        position.Z = 0;
        LookAtMatrix = Matrix4.LookAt(position, -Vector3.UnitZ, Vector3.UnitY);
    }
    public StaticCamera(Vector3 position, Vector3 target)
    {
        LookAtMatrix = Matrix4.LookAt(position, target, Vector3.UnitY);
    }
    public void Update(double time, double delta)
    {}
}
Also added a constructor that makes this camera a little bit more useful, it can initialize to any position and look at any static target. Note that we are using the OpenTK Matrix4 method LookAt to create out camera look at matrix.

First Person Camera

Next camera is the First Person Camera. We send in a AGameObject that the camera should follow and it should give us a feed following the path of the object.
public class FirstPersonCamera : ICamera
{
    public Matrix4 LookAtMatrix { get; private set; }
    private readonly AGameObject _target;
    private readonly Vector3 _offset;

    public FirstPersonCamera(AGameObject target)
        : this(target, Vector3.Zero)
    {}
    public FirstPersonCamera(AGameObject target, Vector3 offset)
    {
        _target = target;
        _offset = offset;
    }

    public void Update(double time, double delta)
    {
        LookAtMatrix = Matrix4.LookAt(
            new Vector3(_target.Position) + _offset,  
            new Vector3(_target.Position + _target.Direction) + _offset, 
            Vector3.UnitY);
    }
}

Here as well we have an overloaded constructor that takes an offset. Still looking in the direction that the object is moving, but from an offset to the position variable, maybe from the cockpit of an airplane instead of the origin of the model.

Third Person Camera

Our third person camera looks at the object that we are tracking from an offset baside it.
public class ThirdPersonCamera : ICamera
{
    public Matrix4 LookAtMatrix { get; private set; }
    private readonly AGameObject _target;
    private readonly Vector3 _offset;

    public ThirdPersonCamera(AGameObject target)
        : this(target, Vector3.Zero)
    {}
    public ThirdPersonCamera(AGameObject target, Vector3 offset)
    {
        _target = target;
        _offset = offset;
    }

    public void Update(double time, double delta)
    {
        LookAtMatrix = Matrix4.LookAt(
            new Vector3(_target.Position) + (_offset * new Vector3(_target.Direction)),  
            new Vector3(_target.Position), 
            Vector3.UnitY);
    }
}

Demo in the video of the three basic movable cameras:


For the complete source code for the tutorial at the end of this part, go to: https://github.com/eowind/dreamstatecoding

So there, thank you for reading. Hope this helps someone out there : )

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

No comments:

Post a Comment