Viewport in Monogame

Let's making some practices about use of viewports with Monogame. In my definition, Viewport provides relative rendering area for programmers. I think, it can be useful for making user interface. I tried to create some ui library, but it failed. Let's learn how to use Monogame at first.

In Monogame, we have already a default viewport. It represents whole screen. 

(x = 0, y =  0, width = screen_width, height=screen_size)

Let's create our first viewport. I created a new project as Monogame project, and I create viewport called newViewport in Initialize method in the Game class. But also we need to store our default viewport, because we need it:


newViewport = new Viewport();
newViewport.X = 50;
newViewport.Y = 50;
newViewport.Width = 100;
newViewport.Height = 75;

defaultViewport = GraphicsDevice.Viewport;
If we want to draw on this viewport we have to activate it at first: Because the current viewport is the default viewport. We need to activate it in render method. Then we can draw some objects on newViewport:
protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);



    GraphicsDevice.Viewport = newViewport;
    GraphicsDevice.Clear(Color.Red); // it will not work as expected 

    // render somethings on this viewport 


    // then back to the default viewport
    GraphicsDevice.Viewport = defaultViewport;


    base.Draw(gameTime);
}
After that, all screen filled by red. Because Clear method clear the buffers. This causes for all screen. But if you want to background color in viewport area, we can use RenderTarget2D for that. Let's create RenderTarget2D too in Initialize method:
newViewportTarget = new RenderTarget2D(
        GraphicsDevice,
        newViewport.Width,
        newViewport.Height
    );
So let's go in the render side. It can confuse your mind, or may not. However I confused. So instead of going deep, I'm going to be a little result-oriented. As a result, asking "why" questions is not allowed. Where we were? Rendertarget2D!. We will use RenderTarget2D for each viewport. RenderTarget is a new back buffer for us. So we can use Clear method finally. Firstly we will set rendertarget, we will clear the rendertarget and then render somethings on it and then change the rendertarget or set as null to return default backbuffer, and same steps will be start for them too:
protected override void Draw(GameTime gameTime)
{

    // red area
    GraphicsDevice.SetRenderTarget(newViewportTarget);
    GraphicsDevice.Clear(Color.Red);

    // background area
    GraphicsDevice.SetRenderTarget(null);
    GraphicsDevice.Clear(Color.CornflowerBlue);

    

    
    // draw rendertargets with related viewports

    GraphicsDevice.Viewport = newViewport;

    _spriteBatch.Begin();
    _spriteBatch.Draw(newViewportTarget, new Rectangle(0, 0, newViewport.Width, newViewport.Height), Color.White);
    _spriteBatch.End();
    
    // then back to the default viewport
    GraphicsDevice.Viewport = defaultViewport;

    

    base.Draw(gameTime);
}
Let's look at the result:
This is fair. In my opinion, this features are not enough to create complex, advances to create ui. But it's enough to make useful ui.
Devamını Oku »

Making a Loading Screen for a game

I will handle how to make a loading screen for a game using C# and SFML.Net. I think it covers other frameworks as well. I mean, you can make it with other C# graphics library or other some Java frameworks most probably. 

What's loading screen? The loading screen shows some information of the process that are necessary data for the game. These data can be world map, and generating a world map can take a noticeable amount of time. For example, Terraria, etc. Therefore, we need to use loading screen for that moment.

Normally, when I started the game, there is a blockage because of the generating something for the game. At this moment, we can not do anything with the window. If I click the window, the program might be crushed. Also, it does not give a good impression. 

I will keep my example very simple. Let's get to work.

Firstly, I'm going to use a thread for generating operation. So the map is generated in the another thread. Also, I need a text object. This object will be drawn to show the percentage of the generating map on the screen. I need to get information from the thread I created to use in the main thread. I created a class called SharedData for it:

    class SharedData
    {
        public int Progress {get; set;} = 0;
    }
    
    class Game
    {
        ...
        
        Thread thread;
        Text Percentage { get; set; }        
        public static SharedData shared = new SharedData();
        
        public Game(...)
        {
            ...
            
            Percentage = new Text("0%", font, 100);
            Percentage.Position = new Vector2f(-25, -25);
            Percentage.FillColor = Color.Black;
            
            thread = new Thread(GenerateMap);
            thread.Start();
        }
I defined the SharedData object as static because we need to access it from another thread easily. The shared variable is actually a global variable. I created the text object and named it as Percentage. After that, I created a thread called thread, and add GenerateMap method to the this thread. I started this thread with Start method. Let's use this shared variable in the Generate method:
        public void GenerateMap()
        {
            for(int i = 0; i < 4116; i++)
            {
                
                // generate data here
                
            	Game.shared.Progress = i; 
            }
       	}
I just track how many times the loop has run like in above. We need to use this data in the main thread for the loading screen. If the thread is alive then we can show the loading screen with percentage. That's so simple:
        ...

        public void update(float dt)
        {
            if(thread.IsAlive)
            {
                float calc = ((float)shared.Progress / 4116f) * 100;
                Percentage.DisplayedString = ((int)calc).ToString() + "%";
            }
            else
            {
                // update generated data
            }
        }

        public void draw(RenderTarget target)
        {
            if(thread.IsAlive)
            {
                target.Draw(Percentage);
            }
            else
            {
                // draw generated data
            }
        }
Let's see how it works:
making the loading screen for game
Devamını Oku »

Specular Lighting

We learned about how to apply diffuse lighting and ambient via shaders in the previous post. In this post, we are going to learn how to apply specular which is the last stage of Phong reflection model. The surface that the light hits can be made brighter via specular reflection.

Let's visualize it:

Specular Lighting
In short, the intensity of specular illumination on the surface changes according to the position of the camera. If you remember from the last post, we calculates diffuse intensity via dot product, and we are going use it again for specular. Also, we are going to use reflect method in the fragment shader of the cube. This method gives us reflection vector according to the normal vector. After we can configure the power of specular lighting with the pow method. The pow method here is used to set the size of the reflection. If we give small values then this reflection size will be bigger than according to big values. You can try each number and you will get it what I mean. Also I changed something about diffuse lighting and ambiend lighting in the fragment shader.

Firstly, we need to the position of the camera for specular lighting. I created a uniform for cameraPosition. Then I transform it according to the world position, and pass it to the fragment shader:
#version 330 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec3 aNormal;

uniform mat4 uTransform;
uniform mat4 projection;
uniform mat4 view;

uniform vec3 lightPosition;
uniform vec3 cameraPosition;

out vec2 passTexCoord;
out vec3 passNormal;
out vec3 lightVector;
out vec3 cameraVector;

void main()
{
    gl_Position = vec4(aPos, 1.0) * uTransform * view * projection;
    passTexCoord = aTexCoord;

    passNormal = (vec4(aNormal, 0.0) * uTransform).xyz;
    lightVector = lightPosition - (vec4(aPos, 1.0) * uTransform).xyz;
    
    cameraVector = cameraPosition - (vec4(aPos, 1.0) * uTransform).xyz;
}   
This is the fragment shader. Briefly, it is explained in the comment lines.
#version 330 core

in vec2 passTexCoord;
in vec3 passNormal;
in vec3 lightVector;
in vec3 cameraVector;

out vec4 FragColor;

uniform sampler2D textureSampler;
uniform vec4 lightColor;

void main()
{
    // ambient lighting
    float ambient = 0.1;
    vec4 ambientColor = lightColor * ambient;
    
    // diffuse lighting
    vec3 normalizedNormal = normalize(passNormal);
    vec3 normalizedLightVector = normalize(lightVector);
    float calcDot = dot(normalizedNormal, normalizedLightVector);
    float brightness = max(calcDot, 0.0);
    float diffuse = brightness;
    vec4 diffuseColor = lightColor * diffuse;

    // specular lighting
    // this is instensity setting for reflection
    float reflectivity = 0.9;
    // reflection vector is opposite of the light vector so we need take it as negative
    // so we can find reflection vector according to the normal
    vec3 reflectionVector = reflect(-normalizedLightVector, normalizedNormal);
    vec3 normalizedReflectionVector = normalize(reflectionVector);
    vec3 normalizedCameraVector = normalize(cameraVector);
    float spec = dot(normalizedCameraVector, normalizedReflectionVector);
    float spec2 = max(spec, 0);
    // define size of reflection
    float specular = pow(spec2, 8);  
    vec4 specularColor = lightColor * specular * reflectivity;

    FragColor = texture(textureSampler, passTexCoord) * (ambientColor + diffuseColor + specularColor);
}
I updated the update method of the Camera class. Because we need to the camera position:
        public void Update()
        {
            ...
            
            foreach (var shader in this.shaderManager.Shaders)
            {
                GL.UseProgram(shader.Value.ShaderProgram);
                
                ...
                
                int uniformLocation_cameraPosition = GL.GetUniformLocation(shader.Value.ShaderProgram, "cameraPosition");
                GL.Uniform3(uniformLocation_cameraPosition, Position);
            }
        }
Let's check the result:
specular lighting
Let's make the power 64:
Specular lighting
The difference can be seen.
Devamını Oku »

Lighting OpenGL (OpenTK/C#)

One of the most important part of graphics programming is lighting. The lighting feature that makes really pretty and gives more realistic effect to 3D environment. This post won't contain theoretical information about lighting. I'm going to try adding phong reflection model. You can get more information about phong reflection from this page Phong reflection model - Wikipedia.

We need to know what are normals, vectors, normalize, dot product, etc. Because we are going to use for lighting.

Normals: these are unit vectors and also they must be perpendicular to the surface.
Normalize: It makes vector a unit vector, basically. We will use this method before dot product operation.
Dot product: The brightness is calculated with this formula.

for example the use of the dot product:

a: vec3(2, -1, 4)
b: vec3(3, 2, -1)

a.b = ?

a.b = 2.3 + (-1.2) + (4.-1)
= 6 - 2 - 4
= 0

Let's get into the programming part. Let's add new ObjectType called Light:
    public enum ObjectType
    {
        Cube,
        Plane,
        Triangle2D,
        Quad2D,
        Light
    }
Also, a new method called AddObject to ObjectManager as follows, (we did method overloading, this is just for easy usage, but don't remember to declare objectType field as public, also it can be changed as property):
        public void AddObject(GameObject obj)
        {
            this.VAOManager.CreateVAO(obj.objectType);
            this.ShaderManager.CreateShader(obj.objectType);
            // GameObject gameObject = new GameObject(obj.objectType);
            obj.VAO = this.VAOManager.VAOs[obj.objectType];
            obj.EBO = this.VAOManager.EBOs[obj.objectType];
            obj.IndexCount = this.VAOManager.IndexCounts[obj.objectType];
            obj.Shader = this.ShaderManager.Shaders[obj.objectType];
            this.gameObjects.Add(obj);
        }
I will create a new mesh method for light. This method create a 3d cube for light object:
        private void GenerateLight()
        {
            float len = 0.3f;

            var (r,g,b,a) = (1.0f, 1.0f, 1.0f, 1.0f);

            float[] vertices = 
            {
                -len, -len, -len, r, g, b, a,  // 0
                 len, -len, -len, r, g, b, a,  // 1 
                 len, -len,  len, r, g, b, a,  // 2 
                -len, -len,  len, r, g, b, a,  // 3 

                -len,  len, -len, r, g, b, a,  // 4
                 len,  len, -len, r, g, b, a,  // 5
                 len,  len,  len, r, g, b, a,  // 6
                -len,  len,  len, r, g, b, a  // 7
            };

            int[] indices = 
            {
                7, 6, 2,  7, 2, 3,      // front face
                4, 5, 1,  4, 1, 0,      // back face
                4, 5, 6,  4, 6, 7,      // top face
                0, 1, 2,  0, 2, 3,      // bottom face
                6, 5, 1,  6, 1, 2,      // right face
                7, 4, 0,  7, 0, 3       // left face
            };
    
            indexCount = 36;

            GenerateVAOforLight(len, vertices, indices);
        }
The GenerateVAOforLight as follows:
        private void GenerateVAOforLight(float len, float[] vertices, int[] indices)
        {
            GL.GenVertexArrays(1, out vao);
            GL.BindVertexArray(vao);
            // create vbo to store the data in opengl and copy data to vbo
            GL.GenBuffers(1, out vbo);
            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
            GL.BufferData(BufferTarget.ArrayBuffer, sizeof(float) * vertices.Length, vertices, BufferUsageHint.StaticDraw);
            // create ebo
            GL.GenBuffers(1, out ebo);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
            GL.BufferData(BufferTarget.ElementArrayBuffer, sizeof(int) * indices.Length, indices, BufferUsageHint.StaticDraw);
            // tell opengl how to use the data via attributes
            // position attribute
            // int attrPosition_Position = GL.GetAttribLocation(shader.ShaderProgram, "aPos");
            int attrPosition_Position = 0;
            GL.EnableVertexAttribArray(attrPosition_Position);
            GL.VertexAttribPointer(attrPosition_Position, 3, VertexAttribPointerType.Float, false, sizeof(float) * 7, 0);
            int attrPosition_Color = 1;
            GL.EnableVertexAttribArray(attrPosition_Color);
            GL.VertexAttribPointer(attrPosition_Color, 4, VertexAttribPointerType.Float, false, sizeof(float) * 7, sizeof(float) * 3);
            
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.BindVertexArray(0);
            Console.WriteLine("light vao: " + vao  + " vbo: " + vbo + " ebo: " + ebo);
        }
    }
In the Mesh class constructor:
        public Mesh(ObjectType objType)
        {
            if(objType is ObjectType.Quad2D) GenerateQuad();
            if(objType is ObjectType.Cube) GenerateCube();
            if(objType is ObjectType.Light) GenerateLight();
        }
Also, we need to create a shader for light. I created a folder named light in shaders folder. I added the vertexShader and fragmentShader file like below:
#version 330 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec4 aColor;

uniform mat4 uTransform;
uniform mat4 projection;
uniform mat4 view;

out vec4 passColor;

void main()
{
    gl_Position = vec4(aPos, 1.0) * uTransform * view * projection;
    passColor = aColor;
}
the fragment shader:
#version 330 core

in vec4 passColor;

out vec4 FragColor;

uniform vec4 lightColor;

void main()
{
    FragColor = passColor * lightColor;
}
We will create the shader in ShaderManager class(We need to refactor this method but that's good for now. But if you want to refactor the code then go ahead.):
        public void CreateShader(ObjectType objectType)
        {
            if(!Shaders.ContainsKey(objectType))
            {
                if(objectType == ObjectType.Quad2D) 
                {
                    Shader shader = new Shader("shaders/triangle/vertexShader.glsl","shaders/triangle/fragmentShader.glsl");
                    shaders[objectType] = shader;
                }
                if(objectType == ObjectType.Cube)
                {
                    Shader shader = new Shader("shaders/cube/vertexShader.glsl","shaders/cube/fragmentShader.glsl");
                    shaders[objectType] = shader;
                }
                if(objectType == ObjectType.Light)
                {
                    Shader shader = new Shader("shaders/light/vertexShader.glsl","shaders/light/fragmentShader.glsl");
                    shaders[objectType] = shader;
                }    
            }
        }
I'm going to create a Light class in objects folder. This class is going to inherit from the GameObject class. But we need to declare some methods of the GameObject class as virtual to override. The Light class is responsible for shaders of all objects that need to be illuminated. 
namespace OpenTKTutorial
{
    public class Light: GameObject
    {
        ShaderManager shaderManager;
        public Vector4 LightColor {get; set;}
        public Vector3 LightPosition {get; set;}
        public Light(ShaderManager shaderManager, ObjectType objectType = ObjectType.Light) : base(objectType)
        {
            this.shaderManager = shaderManager;
            LightColor = new Vector4(1f, 0, 0, 1f);
        }

        public override void Update()
        {
            Matrix4 Translation = OpenTK.Mathematics.Matrix4.CreateTranslation(this.Transform.Position);
            Matrix4 Rotation = OpenTK.Mathematics.Matrix4.CreateRotationZ(MathHelper.DegreesToRadians(this.Transform.Rotation.Z));
            Matrix4 Scale = OpenTK.Mathematics.Matrix4.CreateScale(this.Transform.Scale);
            // rule : Translate x Rotation x Scale
            Matrix4 Transform = Scale * Rotation * Translation;
            Transformation = Transform;

            foreach (var shader in shaderManager.Shaders)
            {
                if(shader.Value != this.Shader)
                {
                    GL.UseProgram(shader.Value.ShaderProgram);
                    int uniformLocation_lightColor = GL.GetUniformLocation(shader.Value.ShaderProgram, "lightColor");
                    GL.Uniform4(uniformLocation_lightColor, LightColor);
                    int uniformLocation_lightPosition = GL.GetUniformLocation(shader.Value.ShaderProgram, "lightPosition");
                    GL.Uniform3(uniformLocation_lightPosition, this.Transform.Position);
                }
            }
        }

        public override void Draw()
        {
            GL.UseProgram(Shader!.ShaderProgram);
            int uniformLocation_Transformation = GL.GetUniformLocation(Shader.ShaderProgram, "uTransform");
            GL.UniformMatrix4(uniformLocation_Transformation, true, ref Transformation);
            GL.BindVertexArray(VAO);
            GL.DrawElements(PrimitiveType.Triangles, IndexCount, DrawElementsType.UnsignedInt, 0);
            GL.BindVertexArray(0);
        }
    }
}
Let's visualize lighting as follows:
Lighting
We need to normals from mesh. Normally, we don't need to calculate normals because when we will use Blender for models, this model will be contain normals already. Right now, we have no option like that. We need to define normals of the cube:
Normals of cube
        private void GenerateCube()
        {
            float len = 0.3f;

            float[] vertices = 
            {
                -len, -len, -len,   // 0
                 len, -len, -len,   // 1 
                 len, -len,  len,   // 2 
                -len, -len,  len,   // 3 

                -len,  len, -len,   // 4
                 len,  len, -len,   // 5
                 len,  len,  len,   // 6
                -len,  len,  len,   // 7
            };

            // 36 * 8 = 288
            float[] cubeVertices = new float[288];

            int[] indices = 
            {
                7, 6, 2,  7, 2, 3,      // front face
                4, 5, 1,  4, 1, 0,      // back face
                4, 5, 6,  4, 6, 7,      // top face
                0, 1, 2,  0, 2, 3,      // bottom face
                6, 5, 1,  6, 1, 2,      // right face
                7, 4, 0,  7, 0, 3       // left face
            };

            float[] texCoords = { 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f }; 
            
            float[] normals = 
            {
                 0f,  0f,  1f,      // front
                 0f,  0f, -1f,      // back
                 0f,  1f,  0f,      // top
                 0f, -1f,  0f,      // bottom
                 1f,  0f,  0f,      // right
                -1f,  0f,  0f       // right
            };

            int nLine = 0;
            for (int i = 0; i < indices.Length; i++)
            {
                if(i!=0 && i%6==0)
                {
                    nLine++;
                }
                int index = indices[i];
                cubeVertices[i * 8 + 0] = vertices[(index * 3) + 0];
                cubeVertices[i * 8 + 1] = vertices[(index * 3) + 1];
                cubeVertices[i * 8 + 2] = vertices[(index * 3) + 2];
                cubeVertices[i * 8 + 3] = texCoords[(i % 6) * 2];
                cubeVertices[i * 8 + 4] = texCoords[(i % 6) * 2 + 1];
                cubeVertices[i * 8 + 5] = normals[(nLine % 6) * 3];
                cubeVertices[i * 8 + 6] = normals[(nLine % 6) * 3 + 1];
                cubeVertices[i * 8 + 7] = normals[(nLine % 6) * 3 + 2];
            }


            GenerateVAOforCube(len, cubeVertices, indices);
        }
I added new attribute pointer for normals in GenerateVAOforCube:
        private void GenerateVAOforCube(float len, float[] vertices, int[] indices)
        {
            ...
            
            int attrPosition_Position = 0;
            GL.EnableVertexAttribArray(attrPosition_Position);
            GL.VertexAttribPointer(attrPosition_Position, 3, VertexAttribPointerType.Float, false, sizeof(float) * 8, 0);
            
            int attrPosition_TextureCoords = 1;
            GL.EnableVertexAttribArray(attrPosition_TextureCoords);
            GL.VertexAttribPointer(attrPosition_TextureCoords, 2, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 3);
            
            int attrPosition_Normal = 2;
            GL.EnableVertexAttribArray(attrPosition_Normal);
            GL.VertexAttribPointer(attrPosition_Normal, 3, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 5);

            ...
        }
Let's update the vertex shader of cube:
#version 330 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec3 aNormal;

uniform mat4 uTransform;
uniform mat4 projection;
uniform mat4 view;

uniform vec3 lightPosition;

out vec2 passTexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0) * uTransform * view * projection;
    passTexCoord = aTexCoord;
}
Now, we can start to calculating of lighting effect on shader. Let's move on the vertex shader:
#version 330 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec3 aNormal;

uniform mat4 uTransform;
uniform mat4 projection;
uniform mat4 view;

uniform vec3 lightPosition;

out vec2 passTexCoord;
out vec3 passNormal;
out vec3 lightVector;

void main()
{
    gl_Position = vec4(aPos, 1.0) * uTransform * view * projection;
    passTexCoord = aTexCoord;

    passNormal = (vec4(aNormal, 0.0) * uTransform).xyz;
    lightVector = lightPosition - (vec4(aPos, 1.0) * uTransform).xyz;
}
After calculation of normal and lightVector, we will pass these vectors to the fragment shader:
#version 330 core

in vec2 passTexCoord;
in vec3 passNormal;
in vec3 lightVector;

out vec4 FragColor;

uniform sampler2D textureSampler;
uniform vec4 lightColor;

void main()
{
    vec3 normalizedNormal = normalize(passNormal);
    vec3 normalizedLightVector = normalize(lightVector);
    float calcDot = dot(normalizedNormal, normalizedLightVector);
    float brightness = max(calcDot, 0.0);
    vec4 diffuse = brightness * lightColor;
    
    FragColor = texture(textureSampler, passTexCoord) * diffuse;
}
The vectors are normalized and used dot production after normalizing. The brightness value is obtained with the smallest value being at least 0. The brightness and the color of the light are multiplied and diffuse is obtained. Finally we multiplied the diffuse with texture pixel, and we get the illuminated pixel value. One of the most important issues to be considered is the multiplication order. If the multiplication order is not as above, you will not get the desired result:
lighting
That's cool but it's too dark. Especially there faces that don't see light. Let's define an ambient variable in the fragment shader:
void main()
{
    float ambient = 0.1;

    vec3 normalizedNormal = normalize(passNormal);
    vec3 normalizedLightVector = normalize(lightVector);
    float calcDot = dot(normalizedNormal, normalizedLightVector);
    float brightness = max(calcDot, 0.0);
    vec4 diffuse = brightness * lightColor;

    FragColor = texture(textureSampler, passTexCoord) * (diffuse + ambient);
}
The result with ambient value:
opengl lighting
I think that's enough for this post. But we're not done with lighting yet. 
Devamını Oku »

Loading Texture to Cube

Let's cover the cube we created in the previous article with texture. But, I will change the generating cube method. Because it is not possible to apply texture coordinates using an index buffer. (It may be possible)

If you don't know how to use texture, you can check it out Texture in OpenTK | let's develop games (letsdevelopgames.com)
    private void GenerateCube()
    {
        float len = 1.0f;

        float[] vertices = 
        {
            -len, -len, -len,   // 0
             len, -len, -len,   // 1 
             len, -len,  len,   // 2 
            -len, -len,  len,   // 3 

            -len,  len, -len,   // 4
             len,  len, -len,   // 5
             len,  len,  len,   // 6
            -len,  len,  len,   // 7
        };

        float[] cubeVertices = new float[180];


        int[] indices = 
        {
            7, 6, 2,  7, 2, 3,      // front face
            4, 5, 1,  4, 1, 0,      // back face
            4, 5, 6,  4, 6, 7,      // top face
            0, 1, 2,  0, 2, 3,      // bottom face
            6, 5, 1,  6, 1, 2,      // right face
            7, 4, 0,  7, 0, 3       // left face
        };

        float[] texCoords = { 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f }; 

        for (int i = 0; i < indices.Length; i++)
        {
            int index = indices[i];
            cubeVertices[i * 5 + 0] = vertices[(index * 3) + 0];
            cubeVertices[i * 5 + 1] = vertices[(index * 3) + 1];
            cubeVertices[i * 5 + 2] = vertices[(index * 3) + 2];
            cubeVertices[i * 5 + 3] = texCoords[(i % 6) * 2];
            cubeVertices[i * 5 + 4] = texCoords[(i % 6) * 2 + 1];
        }

        indexCount = 36;

        GenerateVAOforCube(len, cubeVertices, indices);
    }
I removed the ebo in the GenerateVAOforCube:
    private void GenerateVAOforCube(float len, float[] vertices, int[] indices)
    {
        GL.GenVertexArrays(1, out vao);
        GL.BindVertexArray(vao);
        // create vbo to store the data in opengl and copy data to vbo
        GL.GenBuffers(1, out vbo);
        GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
        GL.BufferData(BufferTarget.ArrayBuffer, sizeof(float) * vertices.Length, vertices, BufferUsageHint.StaticDraw);
        // tell opengl how to use the data via attributes
        // position attribute
        // int attrPosition_Position = GL.GetAttribLocation(shader.ShaderProgram, "aPos");
        int attrPosition_Position = 0;
        GL.EnableVertexAttribArray(attrPosition_Position);
        GL.VertexAttribPointer(attrPosition_Position, 3, VertexAttribPointerType.Float, false, sizeof(float) * 5, 0);
        int attrPosition_TextureCoords = 1;
        GL.EnableVertexAttribArray(attrPosition_TextureCoords);
        GL.VertexAttribPointer(attrPosition_TextureCoords, 2, VertexAttribPointerType.Float, false, sizeof(float) * 5, sizeof(float) * 3);
        
        GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
        GL.BindVertexArray(0);
    }
I'm going to use this image as texture:
cube texture
I changed the draw method of GameObject class:
        public void Draw()
        {
            GL.UseProgram(Shader!.ShaderProgram);

            ...

            GL.BindVertexArray(VAO);
            if(objectType == ObjectType.Cube)
                GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
            else
                GL.DrawElements(PrimitiveType.Triangles, IndexCount, DrawElementsType.UnsignedInt, 0);
            GL.BindVertexArray(0);
        }
I added one cube to the scene:
            objectManager.AddObject(ObjectType.Cube);
            objectManager.gameObjects[objectManager.gameObjects.Count-1].Texture = new Texture("resources/images/cubetexture.png");
This is the output:
Textured Cube
Let's add more cube:
Cubes
Devamını Oku »

Creating Cube Object

Finally, we will be able to make a three-dimensional drawing with this post. I'm going to draw cube. 

Firstly, we have to think the coordinates of the cube vertices in the three-dimensional space. The origin of the cube must be (x:0,y:0,z:0). According to this information, we have to calculate the position of the 8 vertices:

Drawing Cube OpenGL

We have to find the necessary indices for each faces:

Faces of the cube
Front face:       7-6-2 , 7-2-3
Back face:        4-5-1 , 4-1-0
Top face:          4-5-6 , 4-6-7
Bottom face:   0-1-2 , 0-2-3
Right face:       6-5-1 , 6-1-2
Left face:         7-4-0 , 7-0-3

I'm going to add a new method called GenerateCube for Cube in Mesh class:
    public Mesh(ObjectType objType)
    {
        if(objType is ObjectType.Quad2D) GenerateQuad();
        if(objType is ObjectType.Cube) GenerateCube();
    }

	... 

    private void GenerateCube()
    {
        float len = 1.0f;

        // colors 
        var (c0r, c0b, c0g, c0a) = (0.1f, 0.5f, 0.1f, 1.0f);
        var (c1r, c1b, c1g, c1a) = (0.1f, 0.5f, 0.6f, 1.0f); 
        var (c2r, c2b, c2g, c2a) = (0.3f, 0.8f, 0.2f, 1.0f); 
        var (c3r, c3b, c3g, c3a) = (0.1f, 0.3f, 0.1f, 1.0f); 
        var (c4r, c4b, c4g, c4a) = (0.4f, 0.5f, 0.1f, 1.0f); 
        var (c5r, c5b, c5g, c5a) = (0.5f, 0.2f, 0.3f, 1.0f);
        var (c6r, c6b, c6g, c6a) = (0.1f, 0.2f, 0.4f, 1.0f); 
        var (c7r, c7b, c7g, c7a) = (0.7f, 0.5f, 0.5f, 1.0f); 

        float[] vertices = 
        {
            -len, -len, -len, c0r, c0b, c0g, c0a,  // 0
             len, -len, -len, c1r, c1b, c1g, c1a,  // 1
             len, -len,  len, c2r, c2b, c2g, c2a,  // 2
            -len, -len,  len, c3r, c3b, c3g, c3a,  // 3

            -len,  len, -len, c4r, c4b, c4g, c4a,   // 4
             len,  len, -len, c5r, c5b, c5g, c5a,   // 5
             len,  len,  len, c6r, c6b, c6g, c6a,   // 6
            -len,  len,  len, c7r, c7b, c7g, c7a    // 7
        };

        int[] indices = 
        {
            7, 6, 2,  7, 2, 3,      // front face
            4, 5, 1,  4, 1, 0,      // back face
            4, 5, 6,  4, 6, 7,      // top face
            0, 1, 2,  0, 2, 3,      // bottom face
            6, 5, 1,  6, 1, 2,      // right face
            7, 4, 0,  7, 0, 3       // left face
        };

        indexCount = 36;

        GenerateVAOforCube(len, vertices, indices);
    }


    ...


    private void GenerateVAOforCube(float len, float[] vertices, int[] indices)
    {
        GL.GenVertexArrays(1, out vao);
        GL.BindVertexArray(vao);
        // create vbo to store the data in opengl and copy data to vbo
        GL.GenBuffers(1, out vbo);
        GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
        GL.BufferData(BufferTarget.ArrayBuffer, sizeof(float) * vertices.Length, vertices, BufferUsageHint.StaticDraw);
        // create ebo
        GL.GenBuffers(1, out ebo);
        GL.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
        GL.BufferData(BufferTarget.ElementArrayBuffer, sizeof(int) * indices.Length, indices, BufferUsageHint.StaticDraw);
        // tell opengl how to use the data via attributes
        // position attribute
        // int attrPosition_Position = GL.GetAttribLocation(shader.ShaderProgram, "aPos");
        int attrPosition_Position = 0;
        GL.EnableVertexAttribArray(attrPosition_Position);
        GL.VertexAttribPointer(attrPosition_Position, 3, VertexAttribPointerType.Float, false, sizeof(float) * 7, 0);
        int attrPosition_Color = 1;
        GL.EnableVertexAttribArray(attrPosition_Color);
        GL.VertexAttribPointer(attrPosition_Color, 4, VertexAttribPointerType.Float, false, sizeof(float) * 7, sizeof(float) * 3);
        
        GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
        GL.BindVertexArray(0);
    }
}
I created new folder named cube in shaders. The vertex shader:
#version 330 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec4 aColor;

uniform mat4 uTransform;
uniform mat4 projection;
uniform mat4 view;

out vec4 passColor;

void main()
{
    gl_Position =  vec4(aPos, 1.0) * uTransform * view * projection;
    passColor = aColor;
}
The fragment shader:
#version 330 core

in vec4 passColor;

out vec4 FragColor;

// uniform sampler2D textureSampler;

void main()
{
    FragColor = passColor;
}
I updated CreateShader method in the ShaderManager class:
        ...

        public void CreateShader(ObjectType objectType)
        {
            if(!Shaders.ContainsKey(objectType))
            {
                if(objectType == ObjectType.Quad2D) 
                {
                    Shader shader = new Shader("shaders/triangle/vertexShader.glsl","shaders/triangle/fragmentShader.glsl");
                    shaders[objectType] = shader;
                }
                if(objectType == ObjectType.Cube)
                {
                    Shader shader = new Shader("shaders/cube/vertexShader.glsl","shaders/cube/fragmentShader.glsl");
                    shaders[objectType] = shader;
                }     
            }
        }
    }
Finally, we can add a new cube to program:
        ...
        private void InitScene()
        {
            ...
            
            objectManager.AddObject(ObjectType.Cube);
            
            camera = new Camera(shaderManager, 45f, 1024, 768, 0.1f, 100f);
            // camera.Position = new Vector3(15f,0f,1f);
        }
Let's look at the result:
Cube
That's works but there is a problem. At this moment, we need to activate depth buffer, and also we have clear it in every frame. I updated RenderManager class as follows:
...

        public RenderManager(ObjectManager objectManager)
        {
            this.objectManager = objectManager;
            GL.Enable(EnableCap.DepthTest);
        }

        private void Clear()
        {
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
        }

        ...
    }
}
Probably, it will not work because we are using SFML for window. Therefore we need to set some settings of SFML Window:
        public Window(VideoMode mode, string title, ContextSettings settings) : base(mode, title, SFML.Window.Styles.Default, settings)
        {
            InitWindowSettings();
        }
Configure the window in App class like below:
        public App()
        {
            
            SFML.Window.ContextSettings settings = new ContextSettings();
            settings.DepthBits = 24;

            window = new Window(new VideoMode(1024, 768), "OpenTK Test", settings);
            ...
That should be fine:
Cube Depth Test


Devamını Oku »

Camera

I'm going to add a camera system to the project. Because I would to make some 3D things in the next posts. I won't explain the math behind it, because I'm not the chosen one. We will only learn the methods we need to use.
Camera System
I tried to draw an image showing how we get a projection view.

Let's start with Camera class. I will create a file called Camera.cs in objects folder. Because a camera is an object for me:
public class Camera
{
    Matrix4 view;
    Matrix4 projection;
    ShaderManager shaderManager;
    public Vector3 Position { get; set; }

    public Camera(ShaderManager shaderManager, float angle, int width, int height, float zNear=0.1f, float zFar=100f)
    {
        this.shaderManager = shaderManager;

        // Creates a perspective projection matrix.
        projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(angle), (float) width / (float) height, zNear, zFar);


        Position = new Vector3(0f,0f,3f);
        // camera position -> (0,0,3)
        // camera look at -> (0,0,0) origin
        // up -> axis-y
        view = Matrix4.LookAt(Position, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f));

        // we can set projection matrice from shader at once.
        foreach (var shader in this.shaderManager.Shaders)
        {
            Console.WriteLine(shader);
            GL.UseProgram(shader.Value.ShaderProgram);
            int uniformLocation_projection = GL.GetUniformLocation(shader.Value.ShaderProgram, "projection");
            GL.UniformMatrix4(uniformLocation_projection, true, ref projection);
        }
    }

    public void Update()
    {
        view = Matrix4.LookAt(Position, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f));
        
        foreach (var shader in this.shaderManager.Shaders)
        {
            GL.UseProgram(shader.Value.ShaderProgram);
            int uniformLocation_view = GL.GetUniformLocation(shader.Value.ShaderProgram, "view");
            GL.UniformMatrix4(uniformLocation_view, true, ref view);
        }
    }
}
I added the update method because when the camera has to move, the view matrix will be updated. Let's update the vertex shader:
#version 330 core

layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoord;

uniform mat4 uTransform;
uniform mat4 projection;
uniform mat4 view;

out vec2 passTexCoord;

void main()
{
    gl_Position =  vec4(aPos, 1.0) * uTransform * view * projection;
    passTexCoord = aTexCoord;
}
Normally, the multiplication of matrices should not be in this order in opengl applications, however it should be multiplied in the opposite way in OpenTK as far as I understand. I updated the draw method of GameObject class as follows:
        public void Draw()
        {
            GL.UseProgram(Shader!.ShaderProgram);

            ...
            
            int uniformLocation_Transformation = GL.GetUniformLocation(Shader.ShaderProgram, "uTransform");
            GL.UniformMatrix4(uniformLocation_Transformation, true, ref Transformation);

            ...
        }
Now, I can use the camera in the scene. I created an object called camera in the Scene.cs:
        private void InitScene()
        {
            ...

            camera = new Camera(shaderManager, 45f, 1024, 768, 0.1f, 100f);
        }
        
        public void Update()
        {
            objectManager.Update();
            camera.Update();
        }
Let's test the camera how it looks:
Camera Test OpenGL

The camera position is changed like below:
            camera = new Camera(shaderManager, 45f, 1024, 768, 0.1f, 100f);
            camera.Position = new Vector3(0,0,15f);
        }
The output:
camera zoom out OpenGL


Devamını Oku »