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

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

No comments:

Post a Comment