Showing posts with label Shaders. Show all posts
Showing posts with label Shaders. Show all posts

Sunday, February 12, 2017

OpenGL 4 with OpenTK in C# Part 11: Mipmap


This post we will go through how to setup Mipmaps in OpenGL 4.5 with the help of OpenTK.

This is part 11 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
Basic bullet movement patterns in 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
OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse

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 10.

Mipmap what?

Mipmapping is an optimization tool that lets OpenGL pick a texture that is closer to the needed size depending on the size of the geometry being rendered. This lets OpenGL sample from an image that has the data needed closer together, i.e. the stride between texels is not very far. Also, this helps preventing scintillation or grid illusions when the object is far away. All this to a small penalty of memory overhead.
An example of a manually created mipmap texture below:
Wood texture with mipmap levels included
Wood texture with mipmap levels included

Manual mipmap loading

So, lets see how to load a manually created mipmap texture with OpenTK.
Lets first create a new class, basically a copy from the existing TexturedRenderObject, called MipmapManualRenderObject.
    public class MipMapManualRenderObject : ARenderable
    {
        private int _minMipmapLevel = 0;
        private int _maxMipmapLevel;
        private int _texture;
        public MipMapManualRenderObject(TexturedVertex[] vertices, int program, string filename, int maxMipmapLevel)
            : base(program, vertices.Length)
Note the addition of the maxMipmapLevel parameter in the constructor. This will help us load the texture.
Now lets modify the LoadTexture method a bit to allow for it to load all the levels and return them to the InitializeTextures method
private List<MipLevel> LoadTexture(string filename)
{
    var mipmapLevels = new List<MipLevel>();
    using (var bmp = (Bitmap)Image.FromFile(filename))
    {
        int xOffset = 0;
        int width = bmp.Width;
        int height = (bmp.Height/3)*2;
        var originalHeight = height;
        for (int m = 0; m < _maxMipmapLevel; m++)
        {
            xOffset += m == 0 || m == 1 ? 0 : width*2;
            var yOffset = m == 0 ? 0 : originalHeight;

            MipLevel mipLevel;
            mipLevel.Level = m;
            mipLevel.Width = width;
            mipLevel.Height = height;
            mipLevel.Data = new float[mipLevel.Width * mipLevel.Height * 4];
            int index = 0;
            ExtractMipmapLevel(yOffset, mipLevel, xOffset, bmp, index);
            mipmapLevels.Add(mipLevel);

            if (width == 1 || height == 1)
            {
                _maxMipmapLevel = m;
                break;
            }

            width /= 2;
            height /= 2;
        }
    }
    return mipmapLevels;
}
Basically what we do here is to load the image from disk, iterate over it foreach mipmap level and create a MipLevel object that holds the data for that level.
For every iteration, the width and height varibles are halved, for example: 256, 128, 64, 32, 16, 8, 4, 2, 1 would yield in 0 as the original texture followed by 8 mipmaps.
If the width or height goes to 1 before the other, we will continue until both reach 1. If they reach 1 before we reach the max levels provided as input, we just reset the variable to whatever level we are on at the moment. The MipLevel struct looks like following.
public struct MipLevel
{
    public int Level;
    public int Width;
    public int Height;
    public float[] Data;
}

For each level the following method will extract the data from the bitmap. The assumed structure is as the example images in this post.
private static void ExtractMipmapLevel(int yOffset, MipLevel mipLevel, int xOffset, Bitmap bmp, int index)
{
    var width = xOffset + mipLevel.Width;
    var height = yOffset + mipLevel.Height;
    for (int y = yOffset; y < height; y++)
    {
        for (int x = xOffset; x < width; x++)
        {
            var pixel = bmp.GetPixel(x, y);
            mipLevel.Data[index++] = pixel.R/255f;
            mipLevel.Data[index++] = pixel.G/255f;
            mipLevel.Data[index++] = pixel.B/255f;
            mipLevel.Data[index++] = pixel.A/255f;
        }
    }
}

And the InitializeTexture method would have the following changes:
private void InitTextures(string filename)
{
    var data = LoadTexture(filename);
    GL.CreateTextures(TextureTarget.Texture2D, 1, out _texture);
    GL.BindTexture(TextureTarget.Texture2D, _texture);
    GL.TextureStorage2D(
        _texture,
        _maxMipmapLevel,             // levels of mipmapping
        SizedInternalFormat.Rgba32f, // format of texture
        data.First().Width,
        data.First().Height); 

    for (int m = 0; m < data.Count; m++)
    {
        var mipLevel = data[m];
        GL.TextureSubImage2D(_texture,
            m,                  // this is level m
            0,                  // x offset
            0,                  // y offset
            mipLevel.Width,
            mipLevel.Height,
            PixelFormat.Rgba,
            PixelType.Float,
            mipLevel.Data);
    }
            
    var textureMinFilter = (int)All.LinearMipmapLinear;
    GL.TextureParameterI(_texture, All.TextureMinFilter, ref textureMinFilter);
    var textureMagFilter = (int)All.Linear;
    GL.TextureParameterI(_texture, All.TextureMagFilter, ref textureMagFilter);
    // data not needed from here on, OpenGL has the data
}
First off we tell GL.TextureStorage2D that we will have mipmaps by supplying the _maxMipmapLevel variable. We then proceed to iterate over the levels provided by our load method and initialize them with GL.TextureSubImage2D with the level.
At the end, we tell OpenGL that we want to use mipmapping for the minimizing filter by sending in the LinearMipmapLinear constant to the GL.TextureParameterI method

Let OpenGL generate the mipmap

Ok, so maybe we are lazy and don't want to create this by ourselves. Luckily, there is a shortcut. We can tell OpenGL to generate mipmap levels based on the input texture that you load into level 0 with the following command.
GL.GenerateTextureMipmap(_texture);

So, lets create another class called MipmapGeneratedRenderObject as a copy from the original TextureRenderObject and change it to generate the mipmapping by iteself.
We need only change the InitTextures method in this case
private void InitTextures(string filename)
{
    int width, height;
    var data = LoadTexture(filename, out width, out height);
    GL.CreateTextures(TextureTarget.Texture2D, 1, out _texture);
    GL.TextureStorage2D(
        _texture,
        _maxMipmapLevel,             // levels of mipmapping
        SizedInternalFormat.Rgba32f, // format of texture
        width, 
        height); 

    GL.BindTexture(TextureTarget.Texture2D, _texture);
    GL.TextureSubImage2D(_texture,
        0,                  // this is level 0
        0,                  // x offset
        0,                  // y offset
        width,   
        height, 
        PixelFormat.Rgba,
        PixelType.Float,
        data);
            
    GL.GenerateTextureMipmap(_texture);
    GL.TextureParameterI(_texture, All.TextureBaseLevel, ref _minMipmapLevel);
    GL.TextureParameterI(_texture, All.TextureMaxLevel, ref _maxMipmapLevel);
    var textureMinFilter = (int)TextureMinFilter.LinearMipmapLinear;
    GL.TextureParameterI(_texture, All.TextureMinFilter, ref textureMinFilter);
    var textureMagFilter = (int)TextureMinFilter.Linear;
    GL.TextureParameterI(_texture, All.TextureMagFilter, ref textureMagFilter);
    // data not needed from here on, OpenGL has the data
}
Call the GL.GenerateTextureMipmap and then set the rendering filters and levels needed to get it working. It may not be the best quality and may differ between different graphics cards, but it does the trick. If you want the best quality, you must use the manual creation above.

Change to the Fragment Shader

For this to work we need to change from texelFetch that took exact texture coordinates and mipmap level to texture that takes a coordinate between 0 to 1 and does the interpolation and level changing automatically.
#version 450 core
in vec2 vs_textureCoordinate;
uniform sampler2D textureObject;
out vec4 color;

void main(void)
{
 color = texture(textureObject, vs_textureCoordinate);
}
It is nice to see that my questioning of the solution in the Texture post now found its answer. We need to change the calls to
RenderObjectFactory.CreateTexturedCube(1, 1, 1)
To send in 1 as width and height instead of the pixel sizes before.

In action

So how does this work in action. Lets initialize 3 objects with the same texture. One without mipmap (to the left) one with auto generated mipmap (right lower) and one with manually created mipmap texture (right upper) and see how it looks like when the object moves farther away and then back close.  I added the level numberings as I did not see any difference with this particular texture between the manual and generated one, but it is interesting to see how OpenGL fluidly changes between the levels as needed.
The manually created object has the following texture so that we can see the mipmap level that is used at the moment.
Asteroid texture with mipmap levels numbered
Asteroid texture with mipmap levels numbered
For the complete source code for the game 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 : )

All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment or share a link, not required but appreciated! :)

Friday, February 10, 2017

OpenGL 4 with OpenTK in C# Part 10: Asteroid Invaders

OpenGL 4 with OpenTK in C# Part 10: Asteroid Invaders Screenshot

In this part of the series we will look at putting everything we've learned so far together to create a simple game, a hybrid of the classics Asteroids and Space Invaders.
The end product will have a functional keyboard based steering of a spacecraft, that is able to shoot bullets and 3 different types of asteroids.

This is part 10 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
Basic bullet movement patterns in 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
OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse

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 9..

End goal

A spaceship that is able to
  • move around to dodge asteroids, 
  • shoot at asteroids and 
  • receive different score depending on type of asteroid shot down

Objects: AGameObject

This will be the base for our game objects, meaning that we put all generic code that is the same for all game objects into this class.
public abstract class AGameObject
{
    public ARenderable Model => _model;
    public Vector4 Position => _position;
    public Vector3 Scale => _scale;
    private static int GameObjectCounter;
    protected readonly int _gameObjectNumber;
    protected ARenderable _model;
    protected Vector4 _position;
    protected Vector4 _direction;
    protected Vector4 _rotation;
    protected float _velocity;
    protected Matrix4 _modelView;
    protected Vector3 _scale;

    public AGameObject(ARenderable model, Vector4 position, Vector4 direction, Vector4 rotation, float velocity)
    {
        _model = model;
        _position = position;
        _direction = direction;
        _rotation = rotation;
        _velocity = velocity;
        _scale = new Vector3(1);
        _gameObjectNumber = GameObjectCounter++;
    }

    public void SetScale(Vector3 scale)
    {
        _scale = scale;
    }
    public virtual void Update(double time, double delta)
    {
        _position += _direction*(_velocity*(float) delta);
    }

    public virtual void Render()
    {
        _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;
        GL.UniformMatrix4(21, false, ref _modelView);
        _model.Render();
    }
}
Basically we say that each game object should have

  • a ARenderable object as a model.
  • a Position, i.e. where it is in the game world
  • a Direction, where it is pointing
  • a Velocity, what speed is it moving in the direction
  • a Rotation, how is it rotated in space
  • a Scale, how is the original model scaled to fit its purposes
  • a Number to identify it (mostly used for uniqueness of updates seen later)

Our generic Update method takes both the time (total time since start) and delta time (seconds since last frame). The generic update only calculates a new position for each object based on the direction and velocity variables.
At this point there should be no real surprises in here. The Render method handles the calculation of a new ModelView matrix and sets it in the shader before calling the render on the model (TexturedRenderObject). See that Matrix4.CreateScale is added to the calculation.

Objects: Asteroid

The asteroid class basically just inherits from AGameObject and adds its own override for the Update method.
public override void Update(double time, double delta)
{
    _rotation.X = (float)Math.Sin((time + _gameObjectNumber) * 0.3);
    _rotation.Y = (float)Math.Cos((time + _gameObjectNumber) * 0.5);
    _rotation.Z = (float)Math.Cos((time + _gameObjectNumber) * 0.2);
    var d = new Vector4(_rotation.X, _rotation.Y, 0, 0);
    d.Normalize();
    _direction = d;
    base.Update(time, delta);
}
So, what we do is to calculate a new rotation for the object for this frame, we want to asteroids to tumble around the screen. Here we add in the _gameObjectNumber to allow for a little uniquenes in the rotation so that not all asteroids rotate the same.
Then, from the rotation vector, we create a new direction vector based on X and Y dimensions. We want all objects to stay at the same level on the Z axis. Normalize that one and set the direction before calling the base Update method that handles the actual updating of the position.

Objects: Bullet

For the bullet we also override the Update method. But we only set a spin on it, no change in direction. We want to bullet to go straight.
public override void Update(double time, double delta)
{
    _rotation.X = (float)Math.Sin(time * 15 + _gameObjectNumber);
    _rotation.Y = (float)Math.Cos(time * 15 + _gameObjectNumber);
    _rotation.Z = (float)Math.Cos(time * 15 + _gameObjectNumber);
    base.Update(time, delta);
}

Objects: Spacecraft

Ok, here we will have a little more code, but here as well we only add to the Update method.

private bool _moveLeft;
private bool _moveRight;
public override void Update(double time, double delta)
{
    // if the use wants to move left and we are stopped or already moving left
    if (_moveLeft && !(_direction.X > 0 && _velocity > 0))
    {
        _direction.X = -1;
        _velocity += 0.8f * (float)delta;
        _moveLeft = false;
    }
    // if the use wants to move right and we are stopped or already moving right
    else if (_moveRight && !(_direction.X < 0 && _velocity > 0))
    {
        _direction.X = 1;
        _velocity += 0.8f * (float)delta;
        _moveRight = false;
    }
    // otherwise decrease speed to a stop. if the use changes direction, this will happen first
    else
    {
        _velocity -= 0.9f * (float)delta;
    }
    // maximum velocity
    if (_velocity > 0.8f)
    {
        _velocity = 0.8f;
    }
    // minimum velocity
    if (_velocity < 0)
    {
        _velocity = 0;
        _rotation.Y = 0;
    }
    // to make the spacecraft tilt in the way it is moving, we change the 
    // rotation of the Y axis based on the current velocity
    if (_direction.X < 0 && _velocity > 0)
        _rotation.Y = -_velocity;
    if (_direction.X > 0 && _velocity > 0)
        _rotation.Y = _velocity;
    base.Update(time, delta);
}

public void MoveLeft()
{
    _moveLeft = true;
}

public void MoveRight()
{
    _moveRight = true;
}

Basically we allow the user to move the ship left or right. If the ship is moving in another direction, it will first decelerate to 0 before changing direction. This is done so that the tilt animation looks fluid. We set a minimum and maximum velocity and then calculate the tilt of the ship.

GameWindow

OnLoad

asteroid texture, take from a picture of the moon over at pexels.com
Asteroid texture, take from a picture of the moon over at pexels.com

programmers art of a spacecraft
Programmers art of a spacecraft. Red with Viper stripes:D 
In the on load method we need to setup our new game objects and their textures.
So lets replace the following old code
_renderObjects.Add(new TexturedRenderObject(ObjectFactory.CreateTexturedCube(0.2f), _texturedProgram.Id, @"Components\Textures\dotted2.png"));
_renderObjects.Add(new TexturedRenderObject(ObjectFactory.CreateTexturedCube(0.2f), _texturedProgram.Id, @"Components\Textures\wooden.png"));
_renderObjects.Add(new ColoredRenderObject(ObjectFactory.CreateSolidCube(0.2f, Color4.HotPink), _solidProgram.Id));
_renderObjects.Add(new TexturedRenderObject(ObjectFactory.CreateTexturedCube(0.2f), _texturedProgram.Id, @"Components\Textures\dotted.png"));

With the following
var models = new Dictionary<string, ARenderable>();
models.Add("Wooden", new TexturedRenderObject(RenderObjectFactory.CreateTexturedCube(1, 256, 256), _texturedProgram.Id, @"Components\Textures\wooden.png"));
models.Add("Golden", new TexturedRenderObject(RenderObjectFactory.CreateTexturedCube(1, 256, 256), _texturedProgram.Id, @"Components\Textures\golden.bmp"));
models.Add("Asteroid", new TexturedRenderObject(RenderObjectFactory.CreateTexturedCube(1, 256, 256), _texturedProgram.Id, @"Components\Textures\asteroid.bmp"));
models.Add("Spacecraft", new TexturedRenderObject(RenderObjectFactory.CreateTexturedCube6(1, 1536, 256), _texturedProgram.Id, @"Components\Textures\spacecraft.png"));
models.Add("Gameover", new TexturedRenderObject(RenderObjectFactory.CreateTexturedCube6(1, 1536, 256), _texturedProgram.Id, @"Components\Textures\gameover.png"));
models.Add("Bullet", new ColoredRenderObject(RenderObjectFactory.CreateSolidCube(1, Color4.HotPink), _solidProgram.Id));
_gameObjectFactory = new GameObjectFactory(models);

_player = _gameObjectFactory.CreateSpacecraft();
_gameObjects.Add(_player);
_gameObjects.Add(_gameObjectFactory.CreateAsteroid());
_gameObjects.Add(_gameObjectFactory.CreateGoldenAsteroid());
_gameObjects.Add(_gameObjectFactory.CreateWoodenAsteroid());
Instead of storing our renderObjects directly in the GameWindow, we initialize a dictionary of objects that can be reused and send it to a new GameObjectFactory that will handle the creation of game objects. The GameWindow will have a list of game objects instead of render objects.

OnExit

public override void Exit()
{
 Debug.WriteLine("Exit called");
 _gameObjectFactory.Dispose();
 _solidProgram.Dispose();
 _texturedProgram.Dispose();
 base.Exit();
}
No big change here, we make sure that our native resources, in this case render programs and render objects (that are bound to openGL) are correctly released.
I take up this part in almost every part of this tutorial series but it can't be said enough times. When working with unmanaged resources it it Your job to dispose of them correctly. For example I had managed to remove the program disposers above and wondered why the game took longer and longer to start until I had to restart the computer. It is because you allocate memory, and if you don't clear it it will still be hogged even though your application has shut down. When I finally figured out what was wrong and reintroduced the disposes, everything works fine again.
This is something that people tend to forget about when working in C#, but it is really important.

OnUpdateFrame, Keyboard handling

Luckily we already have a method for keyboard handling. Currently it only checks for the Escape key to close the game but now we will add support for the following keys as well
  • A: Move left
  • D: Move right
  • Spacebar: fire bullet
private void HandleKeyboard(double dt)
{
    var keyState = Keyboard.GetState();

    if (keyState.IsKeyDown(Key.Escape))
    {
        Exit();
    }
    if (keyState.IsKeyDown(Key.A))
    {
        _player.MoveLeft();
    }
    if (keyState.IsKeyDown(Key.D))
    {
        _player.MoveRight();
    }
    if (!_gameOver && keyState.IsKeyDown(Key.Space) && _lastKeyboardState.IsKeyUp(Key.Space))
    {
        _gameObjects.Add(_gameObjectFactory.CreateBullet(_player.Position));
    }
    _lastKeyboardState = keyState;
}
For the movement it is OK for the user to press and hold the key down, but for the shooting part we want her to press and release space for every bullet. Otherwise we would generate a lot of bullets for every keypress as it will last for multiple frames. So we store a Last Keypress variable and check that the key was up the previous frame and is now down before we add a new bullet.

OnUpdateFrame

Here we will need to call Update for all game objects in play at the moment. Luckily we have them stored in a list that can be iterated over.
protected override void OnUpdateFrame(FrameEventArgs e)
{
    _time += e.Time;
    var remove = new HashSet<AGameObject>();
    var view = new Vector4(0, 0, -2.4f, 0);
    int outOfBoundsAsteroids = 0;
    foreach (var item in _gameObjects)
    {
        item.Update(_time, e.Time);
        if ((view - item.Position).Length > 2)
        {
            remove.Add(item);
            outOfBoundsAsteroids++;
        }

    }
    foreach (var r in remove)
        _gameObjects.Remove(r);
    for (int i = 0; i < outOfBoundsAsteroids; i++)
    {
        _gameObjects.Add(_gameObjectFactory.CreateRandomAsteroid());
    }
    HandleKeyboard(e.Time);
}
If an asteroid wanders outside of the game screen, we remove it and generate a new one. Note that the removal is done outside of the foreach loop as you should not change a collection being iterated.

OnRenderFrame

Lastly, to render we just iterate over all game objects in play and call their render method.
Note the resetting of the projectionMatrix if the render program changes between objects.
Also note the object counter and score counters in the title row. I have no idea how to write stuff on the screen yet so I went for the easy out and wrote those in the title for now :)
protected override void OnRenderFrame(FrameEventArgs e)
{
    Title = $"{_title}: FPS:{1f / e.Time:0000.0}, obj:{_gameObjects.Count}, score:{_score}";
    GL.ClearColor(Color.Black); // _backColor);
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

    int lastProgram = -1;
    foreach (var obj in _gameObjects)
    {
        var program = obj.Model.Program;
        if (lastProgram != program)
            GL.UniformMatrix4(20, false, ref _projectionMatrix);
        lastProgram = obj.Model.Program;
        obj.Render();

    }
    SwapBuffers();
}

GameObjectFactory

Ok, lets look at the game object factory and see if we can get some stuff on the screen and test play a session..
public class GameObjectFactory : IDisposable
{
    // set a constant Z distance. We are pretty much doing a 2D game in a 3D space
    private const float Z = -2.7f;
    private readonly Random _random = new Random();
    private readonly Dictionary<string, ARenderable> _models;
    public GameObjectFactory(Dictionary<string, ARenderable> models)
    {
        _models = models;
    }
    
    public Spacecraft CreateSpacecraft()
    {
        var spacecraft = new Spacecraft(_models["Spacecraft"], new Vector4(0, -1f, Z, 0), Vector4.Zero, Vector4.Zero, 0);
        // as the spacecraft is just a texture, we scale the z axis to quite thin
        spacecraft.SetScale(new Vector3(0.2f, 0.2f, 0.01f));
        return spacecraft;
    }
    // create asteroid and scale, also sets the score given to the player when the asteroid is destroyed.
    public Asteroid CreateAsteroid(string model, Vector4 position)
    {
        var obj = new Asteroid(_models[model], position, Vector4.Zero, Vector4.Zero, 0.1f);
        obj.SetScale(new Vector3(0.2f));
        switch (model)
        {
            case "Asteroid":
                obj.Score = 1;
                break;
            case "Wooden":
                obj.Score = 10;
                break;
            case "Golden":
                obj.Score = 50;
                break;
        }
        return obj;
    }
    // low probablity for golden asteroid, bit higher for wooden and usually it is just a rock
    public AGameObject CreateRandomAsteroid()
    {
        var rnd = _random.NextDouble();
        var position = GetRandomPosition();
        if (rnd < 0.01)
            return CreateAsteroid("Golden", position);
        if (rnd < 0.2)
            return CreateAsteroid("Wooden", position);
        return CreateAsteroid("Asteroid", position);
    }
    // for the bullet we set the velocity from start as well as direction of movement to UnitY, i.e. straight up
    // we also send in an initial position based on the spacecraft
    public Bullet CreateBullet(Vector4 position)
    {
        var bullet = new Bullet(_models["Bullet"], position + new Vector4(0, 0.1f, 0, 0), Vector4.UnitY, Vector4.Zero, 0.8f);
        bullet.SetScale(new Vector3(0.05f));
        return bullet;
    }
    private Vector4 GetRandomPosition()
    {
        var position = new Vector4(
            ((float) _random.NextDouble() - 0.5f) * 1.1f,
            ((float) _random.NextDouble() - 0.5f) * 1.1f,
            Z,
            0);
        return position;
    }
    // cleanup models to avoid memory leak
    public void Dispose()
    {
        foreach (var obj in _models)
            obj.Value.Dispose();
    }
}

So a lot of stuff here. Key parts include the locked in Z axis to a constant value as we are basically doing a top down 2D game.
The spaceship model is just a very thin box with texture on the front facing side.
Bullet velocity is set from the start.
And Disposing of OpenGL resources at the end.

Add some action: basic collision detection

By now you should have noticed that not much is happening when bullets hit the asteroids. So lets add some code to see if objects touch each other on the screen. To accomplish this, we will just add a simple bounding sphere calculation. I.e. if two objects bounding spheres are intersecting, then we have a collision.
First in the GameWindow OnUpdateFrame loop we shall add the following to detect if a bullet or an asteroid is hitting something. Bullets will hit asteroids and if they hit the asteroid should be removed and 2 new created randomly. If the asteroid hits something, it is the spacecraft and the game is over:
if (item.GetType() == typeof (Bullet))
{
    var collide = ((Bullet) item).CheckCollision(_gameObjects);
    if (collide != null)
    {
        remove.Add(item);
        if (remove.Add(collide))
        {
            _score += ((Asteroid)collide).Score;
            removedAsteroids++;
        }
    }
}
if (item.GetType() == typeof(Spacecraft))
{
    var collide = ((Spacecraft)item).CheckCollision(_gameObjects);
    if (collide != null)
    {
        foreach (var x in _gameObjects)
            remove.Add(x);
        _gameObjects.Add(_gameObjectFactory.CreateGameOver());
        _gameOver = true;
        removedAsteroids = 0;
        break;
    }
}

Then after we have removed whatever items we will remove we add the following to generate new Asteroids.
for (int i = 0; i < removedAsteroids; i++)
{
    _gameObjects.Add(_gameObjectFactory.CreateRandomAsteroid());
    _gameObjects.Add(_gameObjectFactory.CreateRandomAsteroid());
}

Check collision method for the bullet iterates over all game objects and calculates the distance between itself and all asteroids. If the distance is smaller then the radius of the asteroid, we count it as a hit.
public AGameObject CheckCollision(List<AGameObject> gameObjects)
{
    foreach (var x in gameObjects)
    {
        if(x.GetType() != typeof(Asteroid))
            continue;
        // naive first object in radius
        if ((Position - x.Position).Length < x.Scale.X)
            return x;
    }
    return null;
}

And pretty much the same thing for the spaceship. But here we take both spaceship radius and Asteroid radius into account.
public AGameObject CheckCollision(List<AGameObject> gameObjects)
{
    foreach (var x in gameObjects)
    {
        if (x.GetType() != typeof(Asteroid))
            continue;
        // naive first object in radius
        if ((Position - x.Position).Length < (Scale.X + x.Scale.X))
            return x;
    }
    return null;
}

End results

So, nothing fancy at all in the end. But our first working game mechanics with OpenTK. At least we know that after 10 posts on the topic, it is possible to make a game that is playable :)

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

For some bullet movement patterns to add to the game, wave and homing go here.

All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment or share a link, not required but appreciated! :)

Wednesday, February 8, 2017

OpenGL 4 with OpenTK in C# Part 9: Texturing

In this post we will look at how to add texture to our objects.

This is part 9 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
Basic bullet movement patterns in 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
OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse

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 8..

Textures

So now that we have some objects flying around on the screen it is time to give them textures. After the initialization of buffers and attribute bindings in our RenderObject. I've done some restructuring of the code here so that we have a TexturedRenderObject to work with.
As we are rolling a new object type here, we can ditch the old vertex struct that had position and color and instead have position and a texture coordinate instead. (color will be take from the texture)
public struct TexturedVertex
{
    public const int Size = (4 + 2) * 4; // size of struct in bytes

    private readonly Vector4 _position;
    private readonly Vector2 _textureCoordinate;

    public TexturedVertex(Vector4 position, Vector2 textureCoordinate)
    {
        _position = position;
        _textureCoordinate = textureCoordinate;
    }
}
A new constructor for the TexturedRenderObject binds the position and texture coordinate attributes from the above struct.
private int _texture;
public TexturedRenderObject(TexturedVertex[] vertices, int program, string filename)
    : base(program, vertices.Length)
{
    // create first buffer: vertex
    GL.NamedBufferStorage(
        Buffer,
        TexturedVertex.Size * vertices.Length,        // the size needed by this buffer
        vertices,                           // data to initialize with
        BufferStorageFlags.MapWriteBit);    // at this point we will only write to the buffer
            
    GL.VertexArrayAttribBinding(VertexArray, 0, 0);
    GL.EnableVertexArrayAttrib(VertexArray, 0);
    GL.VertexArrayAttribFormat(
        VertexArray,
        0,                      // attribute index, from the shader location = 0
        4,                      // size of attribute, vec4
        VertexAttribType.Float, // contains floats
        false,                  // does not need to be normalized as it is already, floats ignore this flag anyway
        0);                     // relative offset, first item, in bytes
            
    GL.VertexArrayAttribBinding(VertexArray, 1, 0);
    GL.EnableVertexArrayAttrib(VertexArray, 1);
    GL.VertexArrayAttribFormat(
        VertexArray,
        1,                      // attribute index, from the shader location = 1
        2,                      // size of attribute, vec2
        VertexAttribType.Float, // contains floats
        false,                  // does not need to be normalized as it is already, floats ignore this flag anyway
        16);                     // relative offset after a vec4, in bytes

    // link the vertex array and buffer and provide the stride as size of Vertex
    GL.VertexArrayVertexBuffer(VertexArray, 0, Buffer, IntPtr.Zero, TexturedVertex.Size);

    _texture = InitTextures(filename);
}

The initialization of a texture follows the same pattern as with buffers and attributes. First we call a create method to get a new name that we then can setup storage for, bind and then store stuff in. This is called the direct state access and was introduced in OpenGL 4.5.
private int InitTextures(string filename)
{
    int width, height;
    var data = LoadTexture(filename, out width, out height);
    int texture;
    GL.CreateTextures(TextureTarget.Texture2D, 1, out texture);
    GL.TextureStorage2D(
        texture,
        1,                           // levels of mipmapping
        SizedInternalFormat.Rgba32f, // format of texture
        width, 
        height); 

    GL.BindTexture(TextureTarget.Texture2D, texture);
    GL.TextureSubImage2D(texture,
        0,                  // this is level 0
        0,                  // x offset
        0,                  // y offset
        width,   
        height, 
        PixelFormat.Rgba,
        PixelType.Float,
        data);
    return texture;
    // data not needed from here on, OpenGL has the data
}

The LoadTexture method uses System.Drawing to load an image from disk. GetPixel(int x, int y) gets the color of the pixel at the coordinates and we just pick it up and put it in or array in the correct order (RGBA).,
private float[] LoadTexture(string filename, out int width, out int height)
{
    float[] r; 
    using (var bmp = (Bitmap)Image.FromFile(filename))
    {
        width = bmp.Width;
        height = bmp.Height;
        r = new float[width * height * 4];
        int index = 0;
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                var pixel = bmp.GetPixel(x, y);
                r[index++] = pixel.R / 255f;
                r[index++] = pixel.G / 255f;
                r[index++] = pixel.B / 255f;
                r[index++] = pixel.A / 255f;
            }
        }
    }
    return r;
}

Then, when binding the TexturedRenderObject, we need to bind the Texture together with the Program and VertexArray.
public virtual void Bind()
{
    GL.UseProgram(Program);
    GL.BindVertexArray(VertexArray);
    GL.BindTexture(TextureTarget.Texture2D, _texture);
}

Vertex Shader
The job of our vertex shader at this stage is to just forward the texture coordinate to the fragment shader.
#version 450 core

layout (location = 0) in vec4 position;
layout (location = 1) in vec2 textureCoordinate;

out vec2 vs_textureCoordinate;

layout(location = 20) uniform  mat4 projection;
layout (location = 21) uniform  mat4 modelView;

void main(void)
{
 vs_textureCoordinate = textureCoordinate;
 gl_Position = projection * modelView * position;
}

Texture Coordinates

descriptive illustration of model coordinates versus texture coordinates in opengl

Texture coordinates tell OpenGL how to fit the texture to the triangles that you have.
In the the code where we generate a cube, we need to add the texture coordinates for each vertex. Following the pattern in the picture each negative side (-side) becomes 0 and each positive side (+side) becomes 1. Each vertex has 3 coordinates, but for each side we only care about 2 of them. The third, the one that does not change on that panel is ignored.
Example side:
x
y
z
w
texture.x
texture.y
-side
 -side
 side
 1.0f
0 0
side
 -side
 side
 1.0f
1 0
-side
 side
 side
 1.0f
0 1
-side
 side
 side
 1.0f
0 1
side
 -side
 side
 1.0f
1 0
side
 side
 side
 1.0f
1 1
The z axis does not change so this is positive z, i.e. front facing side. z column is ignored. x matches texture.x and y matches texture.y.
Should be easy, took me a while to figure it out. Most examples seem to expect that you use an application like Blender to do this for you.

In the end we end up with a create method that looks like the following:
public static TexturedVertex[] CreateTexturedCube(float side, float textureWidth, float textureHeight)
{
    float h = textureHeight;
    float w = textureWidth;
    side = side / 2f; // half side - and other half

    TexturedVertex[] vertices =
    {
        new TexturedVertex(new Vector4(-side, -side, -side, 1.0f),   new Vector2(0, 0)),
        new TexturedVertex(new Vector4(-side, -side, side, 1.0f),    new Vector2(0, h)),
        new TexturedVertex(new Vector4(-side, side, -side, 1.0f),    new Vector2(w, 0)),
        new TexturedVertex(new Vector4(-side, side, -side, 1.0f),    new Vector2(w, 0)),
        new TexturedVertex(new Vector4(-side, -side, side, 1.0f),    new Vector2(0, h)),
        new TexturedVertex(new Vector4(-side, side, side, 1.0f),     new Vector2(w, h)),

        new TexturedVertex(new Vector4(side, -side, -side, 1.0f),    new Vector2(0, 0)),
        new TexturedVertex(new Vector4(side, side, -side, 1.0f),     new Vector2(w, 0)),
        new TexturedVertex(new Vector4(side, -side, side, 1.0f),     new Vector2(0, h)),
        new TexturedVertex(new Vector4(side, -side, side, 1.0f),     new Vector2(0, h)),
        new TexturedVertex(new Vector4(side, side, -side, 1.0f),     new Vector2(w, 0)),
        new TexturedVertex(new Vector4(side, side, side, 1.0f),      new Vector2(w, h)),

        new TexturedVertex(new Vector4(-side, -side, -side, 1.0f),   new Vector2(0, 0)),
        new TexturedVertex(new Vector4(side, -side, -side, 1.0f),    new Vector2(w, 0)),
        new TexturedVertex(new Vector4(-side, -side, side, 1.0f),    new Vector2(0, h)),
        new TexturedVertex(new Vector4(-side, -side, side, 1.0f),    new Vector2(0, h)),
        new TexturedVertex(new Vector4(side, -side, -side, 1.0f),    new Vector2(w, 0)),
        new TexturedVertex(new Vector4(side, -side, side, 1.0f),     new Vector2(w, h)),

        new TexturedVertex(new Vector4(-side, side, -side, 1.0f),    new Vector2(0, 0)),
        new TexturedVertex(new Vector4(-side, side, side, 1.0f),     new Vector2(0, h)),
        new TexturedVertex(new Vector4(side, side, -side, 1.0f),     new Vector2(w, 0)),
        new TexturedVertex(new Vector4(side, side, -side, 1.0f),     new Vector2(w, 0)),
        new TexturedVertex(new Vector4(-side, side, side, 1.0f),     new Vector2(0, h)),
        new TexturedVertex(new Vector4(side, side, side, 1.0f),      new Vector2(w, h)),

        new TexturedVertex(new Vector4(-side, -side, -side, 1.0f),   new Vector2(0, 0)),
        new TexturedVertex(new Vector4(-side, side, -side, 1.0f),    new Vector2(0, h)),
        new TexturedVertex(new Vector4(side, -side, -side, 1.0f),    new Vector2(w, 0)),
        new TexturedVertex(new Vector4(side, -side, -side, 1.0f),    new Vector2(w, 0)),
        new TexturedVertex(new Vector4(-side, side, -side, 1.0f),    new Vector2(0, h)),
        new TexturedVertex(new Vector4(side, side, -side, 1.0f),     new Vector2(0, 0)),

        new TexturedVertex(new Vector4(-side, -side, side, 1.0f),    new Vector2(0, 0)),
        new TexturedVertex(new Vector4(side, -side, side, 1.0f),     new Vector2(w, 0)),
        new TexturedVertex(new Vector4(-side, side, side, 1.0f),     new Vector2(0, h)),
        new TexturedVertex(new Vector4(-side, side, side, 1.0f),     new Vector2(0, h)),
        new TexturedVertex(new Vector4(side, -side, side, 1.0f),     new Vector2(w, 0)),
        new TexturedVertex(new Vector4(side, side, side, 1.0f),      new Vector2(w, h)),
    };
    return vertices;
}

Fragment Shader
#version 450 core
in vec2 vs_textureCoordinate;
uniform sampler2D textureObject;
out vec4 color;

void main(void)
{
 color = texelFetch(textureObject, ivec2(vs_textureCoordinate.x, vs_textureCoordinate.y), 0);
}
I'm not totally happy with this one, feels strange to have to scale the coordinate so I guess I should have set it directly in the object builder. At least the texture is on the screen but I guess there is room for improvement. First time for everyone, right? :)
Turns out that I had the vertice order wrong, when you turn on face culling (tell OpenGL to not render backsides of triangles, things got nifty. The code above is updated, unsure about the repository.
A great next step is to build a CreateTexturedCube that takes a 1536x256 texture, 256x256 per side so that one can have different type on each side.

Tie it all together

Lets make some changes to or OnLoad method to initialize these shaders and render objects.

protected override void OnLoad(EventArgs e)
{
    Debug.WriteLine("OnLoad");
    VSync = VSyncMode.Off;
    CreateProjection();
    _solidProgram = new ShaderProgram();
    _solidProgram.AddShader(ShaderType.VertexShader, @"Components\Shaders\1Vert\simplePipeVert.c");
    _solidProgram.AddShader(ShaderType.FragmentShader, @"Components\Shaders\5Frag\simplePipeFrag.c");
    _solidProgram.Link();

    _texturedProgram = new ShaderProgram();
    _texturedProgram.AddShader(ShaderType.VertexShader, @"Components\Shaders\1Vert\simplePipeTexVert.c");
    _texturedProgram.AddShader(ShaderType.FragmentShader, @"Components\Shaders\5Frag\simplePipeTexFrag.c");
    _texturedProgram.Link();

    _renderObjects.Add(new TexturedRenderObject(ObjectFactory.CreateTexturedCube(0.2f, 256, 256), _texturedProgram.Id, @"Components\Textures\dotted2.png"));
    _renderObjects.Add(new TexturedRenderObject(ObjectFactory.CreateTexturedCube(0.2f, 256, 256), _texturedProgram.Id, @"Components\Textures\wooden.png"));
    _renderObjects.Add(new ColoredRenderObject(ObjectFactory.CreateSolidCube(0.2f, Color4.HotPink), _solidProgram.Id));
    _renderObjects.Add(new TexturedRenderObject(ObjectFactory.CreateTexturedCube(0.2f, 256, 256), _texturedProgram.Id, @"Components\Textures\dotted.png"));

    CursorVisible = true;

    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
    GL.PatchParameter(PatchParameterInt.PatchVertices, 3);
    GL.PointSize(3);
    GL.Enable(EnableCap.DepthTest);
    Closed += OnClosed;
    Debug.WriteLine("OnLoad .. done");
}
At this point, we should have textured objects flying across the screen similar to the following:
So, there. feels like progress and actually something usable on the screen. Only took 9 posts to get here.
For the full source at the end of part 9, including all the refactoring, go to: https://github.com/eowind/dreamstatecoding


Thank you for reading, here's a cat video to lighten up your day.


Sunday, February 5, 2017

OpenGL 4 with OpenTK in C# Part 8: Drawing multiple objects

In this post we will look at how to reuse the Vertex Array and Buffers to draw the same model multiple times.

This is part 8 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
Basic bullet movement patterns in 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
OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse

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 6..

Projection Matrix

As a starter, in the previous post the cube looked a little odd. It had no real perspective and when the window was rescaled it didn't behave as it should have. I.e. it got out of shape.
To solve this, we shall add a Projection Matrix to our solution.
In our vertex shader, we will add another uniform matrix in addition to the modelView matrix we already have as follows:
#version 450 core

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

out vec4 vs_color;

layout(location = 20) uniform  mat4 projection;
layout (location = 21) uniform  mat4 modelView;

void main(void)
{
 gl_Position = projection * modelView * position;
 vs_color = color;
}

Be sure to get the order of multiplications correct or else things will look very very strange.
In our GameWindow, lets add the following method to create this projection matrix.
private void CreateProjection()
{
            
    var aspectRatio = (float)Width/Height;
    _projectionMatrix = Matrix4.CreatePerspectiveFieldOfView(
        60*((float) Math.PI/180f), // field of view angle, in radians
        aspectRatio,                // current window aspect ratio
        0.1f,                       // near plane
        4000f);                     // far plane
}
Basically we tell OpenTKs Matrix4 to create this matrix for us. And we want to supply the angle of the field of view together with the window aspect ratio and the near and far clipping planes.
Changing the angle is a cheap mans zoom of sorts, the aspect ratio helps keep things in shape even if we drag the window to strange sizes and the near and far clipping determine how close or far away an object can be and still be drawn on screen.
We want to call the CreateProjection from both the OnLoad and OnResize event handlers.
Then in our OnRenderFrame add a call to
GL.UniformMatrix4(20, false, ref _projectionMatrix);
And we should be set.

Drawing multiple objects

So, now that we have gone through the projection matrix that should probably have been covered in an earlier post, but I did not know it at the time of writing we can finally start with the topic of this post: Drawing multiple objects on screen.

Lets initialize a few different RenderObjects in out OnLoad method. As the only model we have at the moment is a cube, lets create some different colored ones so that we know the difference.
In reality these would probably be different models that look differently all together. But for now lets take what we have. But you can imagine that HotPink is a car, BlueViolet is a truck, Red is a Motorcycle and LimeGreen is a lawnmower ;)
protected override void OnLoad(EventArgs e)
{
    VSync = VSyncMode.Off;
    CreateProjection();
    _renderObjects.Add(new RenderObject(ObjectFactory.CreateSolidCube(0.2f, Color4.HotPink)));
    _renderObjects.Add(new RenderObject(ObjectFactory.CreateSolidCube(0.2f, Color4.BlueViolet)));
    _renderObjects.Add(new RenderObject(ObjectFactory.CreateSolidCube(0.2f, Color4.Red)));
    _renderObjects.Add(new RenderObject(ObjectFactory.CreateSolidCube(0.2f, Color4.LimeGreen)));

    CursorVisible = true;

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

So, now we have 4 different RenderObjects created, each containing Vertex Arrays and Buffers. Next step is to draw them on the screen. But we don't want to just draw one of each, lets draw a bunch.
protected override void OnRenderFrame(FrameEventArgs e)
{
    _time += e.Time;
    Title = $"{_title}: (Vsync: {VSync}) FPS: {1f / e.Time:0}";
    GL.ClearColor(_backColor);
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

    GL.UseProgram(_program);
    GL.UniformMatrix4(20, false, ref _projectionMatrix);
    float c = 0f;
    foreach (var renderObject in _renderObjects)
    {
        renderObject.Bind();
        for (int i = 0; i < 5; i++)
        {
            var k = i + (float)(_time * (0.05f + (0.1 * c)));
            var t2 = Matrix4.CreateTranslation(
                (float)(Math.Sin(k * 5f) * (c + 0.5f)),
                (float)(Math.Cos(k * 5f) * (c + 0.5f)),
                -2.7f);
            var r1 = Matrix4.CreateRotationX(k * 13.0f + i);
            var r2 = Matrix4.CreateRotationY(k * 13.0f + i);
            var r3 = Matrix4.CreateRotationZ(k * 3.0f + i);
            var modelView = r1 * r2 * r3 * t2;
            GL.UniformMatrix4(21, false, ref modelView);
            renderObject.Render();
        }
        c += 0.3f;
    }
    GL.PointSize(10);
    SwapBuffers();
}
The projection matrix will stay the same during the whole frame, so we need only to set it once before the render loop.
Then for each model that we loaded (different colored cube in our case), lets apply some transforms and rotations, each object gets a unique model view matrix that we set.
Note that the same RenderObject is bound once but rendered multiple times, just with different translations.
In the RenderObject, we need to split the old Render method into a Render and Bind method for this to work.
public void Bind()
{
    GL.BindVertexArray(_vertexArray);
}
public void Render()
{
    GL.DrawArrays(PrimitiveType.Triangles, 0, _verticeCount);
}

The end results should be like this:


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

Hope this helps someone out there :)
No cat video today either, it seems to be a every other post thing currently.


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
Basic bullet movement patterns in 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
OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse

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

Wednesday, February 1, 2017

OpenGL 4 with OpenTK in C# Part 5: Buffers and Triangle


In this post we will look at how to access buffers in OpenGL from your application code to provide your shaders with vertex data and to draw our first triangle.

This is part 5 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
Basic bullet movement patterns in 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
OpenGL 4 with OpenTK in C# Part 15: Object picking by mouse

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.

Initialize a buffer

Some of the methods used here require OpenGL 4.5.0 to work. Please update your graphic card drivers if you have any issues and make sure you are running on the correct graphics card and not the integrated one.

First off lets define a struct to hold our vertex position and color.
public struct Vertex
{
    public const int Size = (4 + 4) * 4; // size of struct in bytes

    private readonly Vector4 _position;
    private readonly Color4 _color;

    public Vertex(Vector4 position, Color4 color)
    {
        _position = position;
        _color = color;
    }
}
The above struct matches the Vertex Shader, i.e. that it has a Vec4 for Position and Vec4 for Color. The Vector4 and Color4 are provided by OpenTK.

In the previous posts we have used a pretty static VertexArray just because it is needed to be able to draw anything. So lets create a vertex array and a buffer.
    _vertexArray = GL.GenVertexArray();
    _buffer = GL.GenBuffer();

    GL.BindVertexArray(_vertexArray);
    GL.BindBuffer(BufferTarget.ArrayBuffer, _buffer);
Next step is to tell OpenGL how we will be using this new buffer
    GL.NamedBufferStorage(
        _buffer,
        Vertex.Size*vertices.Length,        // the size needed by this buffer
        vertices,                           // data to initialize with
        BufferStorageFlags.MapWriteBit);    // at this point we will only write to the buffer
Now we need to provide information on where to find the attribute data for the shader, i.e. position and color. Starting with Position:
    GL.VertexArrayAttribBinding(_vertexArray, 0, 0);
    GL.EnableVertexArrayAttrib(_vertexArray, 0);
    GL.VertexArrayAttribFormat(
        _vertexArray,
        0,                      // attribute index, from the shader location = 0
        4,                      // size of attribute, vec4
        VertexAttribType.Float, // contains floats
        false,                  // does not need to be normalized as it is already, floats ignore this flag anyway
        0);                     // relative offset, first item
And then the color
    GL.VertexArrayAttribBinding(_vertexArray, 1, 0);
    GL.EnableVertexArrayAttrib(_vertexArray, 1);
    GL.VertexArrayAttribFormat(
        _vertexArray,
        1,                      // attribute index, from the shader location = 1
        4,                      // size of attribute, vec4
        VertexAttribType.Float, // contains floats
        false,                  // does not need to be normalized as it is already, floats ignore this flag anyway
        16);                     // relative offset after a vec4
Finally we link this together using the following command.
    GL.VertexArrayVertexBuffer(_vertexArray, 0, _buffer, IntPtr.Zero, Vertex.Size);

Rendering this all with:
    GL.BindVertexArray(_vertexArray);
    GL.DrawArrays(PrimitiveType.Triangles, 0, 3);


Refactoring to a RenderObject class

As this is quite a lot of code, I decided to create a class for this called RenderObject. The above code for initiating goes into the constructor and the render code into the Render method
public class RenderObject : IDisposable
{
    private bool _initialized;
    private readonly int _vertexArray;
    private readonly int _buffer;
    private readonly int _verticeCount;
    public RenderObject(Vertex[] vertices)
    {
        _verticeCount = vertices.Length;
 
  // create vertex array and buffer here
  
        _initialized = true;
    }
    public void Render()
    {
        GL.BindVertexArray(_vertexArray);
        GL.DrawArrays(PrimitiveType.Triangles, 0, _verticeCount);
    }
        
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_initialized)
            {
                GL.DeleteVertexArray(_vertexArray);
                GL.DeleteBuffer(_buffer);
                _initialized = false;
            }
        }
    }
}

This will hopefully help us further down the road when we want to add more objects to the scene.
This gives us the following changes in our GameWindow starting the OnLoad method:
private List<RenderObject> _renderObjects = new List<RenderObject>(); 
protected override void OnLoad(EventArgs e)
{
 Vertex[] vertices =
 {
  new Vertex(new Vector4(-0.25f, 0.25f, 0.5f, 1-0f), Color4.HotPink),
  new Vertex(new Vector4( 0.0f, -0.25f, 0.5f, 1-0f), Color4.HotPink),
  new Vertex(new Vector4( 0.25f, 0.25f, 0.5f, 1-0f), Color4.HotPink), 
 };
 _renderObjects.Add(new RenderObject(vertices));

 CursorVisible = true;

 _program = CreateProgram();
 GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
 GL.PatchParameter(PatchParameterInt.PatchVertices, 3);
 Closed += OnClosed;
}
So we replaced the VertexBuffer initialization with initialization of a render object list instead. The example vertice array should result in a pink triangle on the screen similar to the title picture of this post.

The OnExit method is changed to dispose the render objects that have been initialized by our code
public override void Exit()
{
 Debug.WriteLine("Exit called");
 foreach(var obj in _renderObjects)
  obj.Dispose();
 GL.DeleteProgram(_program);
 base.Exit();
}

And finally our OnRenderFrame code loops over the render objects and calls their independent Render methods to get them on the screen.
protected override void OnRenderFrame(FrameEventArgs e)
{
 _time += e.Time;
 Title = $"{_title}: (Vsync: {VSync}) FPS: {1f / e.Time:0}";
 Color4 backColor;
 backColor.A = 1.0f;
 backColor.R = 0.1f;
 backColor.G = 0.1f;
 backColor.B = 0.3f;
 GL.ClearColor(backColor);
 GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

 GL.UseProgram(_program);
 foreach(var renderObject in _renderObjects)
  renderObject.Render();
 SwapBuffers();
}

Shaders used

The shaders used in this post are the following
Vertex Shader
#version 450 core

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

void main(void)
{
 gl_Position = position;
 vs_color = color;
}
Fragment Shader
#version 450 core
in vec4 vs_color;
out vec4 color;

void main(void)
{
 color = vs_color;
}

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

Hope this helps someone out there :)
Thanks for reading. Here's another video of one of our cats playing to lighten up your day.

All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment or share a link on social media, not required but appreciated! :)