Showing posts with label OpenTK. Show all posts
Showing posts with label OpenTK. Show all posts

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 »

Texture in OpenTK

We drew a triangle in the previous post. Now, I want to add texture to the surface of the shape. So, I will try to add an image file as a texture to it. 

Firstly, we have to understand the texturing concept of OpenGL. The texture image should be handled by a coordinate system:
Texturing in Modern OpenGL

OpenGL makes transfers each pixel of the texture to each pixel of our shape. So our texture image is in below:
texturing in modern opengl

If you read the previous posts about usage OpenTK. I created a little system to manage development easily. So, I won't show directly how to use texture in simple code. I want to create Texture class which contains ID of texture. I created a file called Texture.cs:
    public class Texture
    {
        int textureId;

        public Texture(string imagePath)
        {
            SFML.Graphics.Image image = new SFML.Graphics.Image(imagePath);

            GL.GenTextures(1, out textureId);
            GL.BindTexture(TextureTarget.Texture2D, textureId);

            GL.TexImage2D(
                TextureTarget.Texture2D, 
                0, 
                PixelInternalFormat.Rgba, 
                (int)image.Size.X, (int)image.Size.Y, 
                0,
                OpenTK.Graphics.OpenGL.PixelFormat.Rgba,
                PixelType.UnsignedByte,
                image.Pixels
            );
            
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);   
        }

        public void BindTexture()
        {
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, textureId);
        }
    }
Let's examine above the code. Firstly, I need to integer id field called textureId which is keep pointer for the texture from GPU. I added constructor, and this constructor needs parameter called imagePath. I will pass the image path for the texture with this constructor. I used Image class of SFML for loading image. After that the texture is created with GenTextures(1, out textureId) method, and then we have to bound this texture with TextureTarget. We will load the image data via the TexImage2D method. After that we have to apply some settings via TexParameter. In here, I have some doubt that do we have to use TexParameter, because if I don't then the Texture cannot be shown on the shape(if I find the real answer, I will update here). There is a another method called BindTexture for activating the texture. We have to call this method before drawing the shape. Also, if you think you can confuse this function name with api method name which is BindTexture, you can named with different name.

The property name Texture is defined in GameObject class:
    public class GameObject
    {
        ObjectType objectType;

        public int VAO { get; set; }
        public int IndexCount { get; set; }
        public Texture? Texture { get; set; }
        ...
Also, the draw method changed like as follows:
        public void Draw()
        {
            GL.UseProgram(Shader!.ShaderProgram);

            if(Texture != null)
            {
                Texture.BindTexture();
            }

            GL.UniformMatrix4(0, true, ref Transformation);

            GL.BindVertexArray(VAO);
            GL.DrawElements(PrimitiveType.Triangles, IndexCount, DrawElementsType.UnsignedInt, 0);
            GL.BindVertexArray(0);
        }
It's time to make changes to our mesh class. Because OpenGL has to know texture coordinates from the VBO. The GenerateQuad method updated as follows:
                private void GenerateQuad()
        {   
            ...

            float[] vertices = 
            {
                // x, y, z, s, t
                -len,  len, 0f, 0f, 1f,
                 len,  len, 0f, 1f, 1f,  
                 len, -len, 0f, 1f, 0f,
                -len, -len, 0f, 0f, 0f
            };

            ...

            GenerateVAO(len, vertices, indices);
        }
We have to describe the data to OpenGL properly. So, I added new attribute pointer in the GenerateVAO method:
        private void GenerateVAO(float len, float[] vertices, int[] indices)
        {
            GL.GenVertexArrays(1, out vao);
            GL.BindVertexArray(vao);
            
            ...
            
            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 assume you have already knew how to use attribute pointer from the previous posts. We need to change the vertex shader and the fragment shader like below:
#version 330 core

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

uniform mat4 uTransform;

out vec2 passTexCoord;

void main()
{
    gl_Position =  vec4(aPos, 1.0) * uTransform;
    passTexCoord = aTexCoord;
}
We created the empty texture with uniform and the pixel from the image pixels which is specified by passTexCoord is copied to the textureSampler via texture method:
#version 330 core

in vec2 passTexCoord;

out vec4 FragColor;

uniform sampler2D textureSampler;

void main()
{
    FragColor = texture(textureSampler, passTexCoord);
}
Finally, we can define texture for a game object. I created texture for the game object like in the code below:
        private void InitScene()
        {
            objectManager.AddObject(ObjectType.Quad2D);
            objectManager.gameObjects[0].Texture = new Texture("resources/images/image.png");
            ...
Let's see the result:
Texture in OpenTK
Devamını Oku »

2D Transformation

Transformation is the important part of the graphics programming. It is responsible for translation, rotation and scale attributes of shapes. In this post, I'm going to write about two-dimensional transformation. After the some next posts, We will see about 3D transformation.

In OpenGL, transformation operations are handled by matrices. If we want to translate some object in the scene, we need to use necessary matrice for it. We will use Homogeneous Coordinates at this point.

Translation matrix:

Rotation matrix:
Scale matrix:
We will get Transform matrix from these matrices. 
    public class GameObject
    {
        ObjectType objectType;

        public int VAO { get; set; }
        public int IndexCount { get; set; }
        public Shader? Shader {get; set;}
        public Transform Transform {get; set;}

        public Matrix4 Transformation;

        public GameObject(ObjectType objectType)
        {
            Transform = new Transform();
            this.objectType = objectType;
        }

        ...

        public void Draw()
        {
            GL.UseProgram(Shader!.ShaderProgram);

            GL.UniformMatrix4(0, true, ref Transformation);

            GL.BindVertexArray(VAO);
            GL.DrawElements(PrimitiveType.Triangles, IndexCount, DrawElementsType.UnsignedInt, 0);
            GL.BindVertexArray(0);
        }
    }
I updated the vertex shader like in the below:
#version 330 core

in vec3 aPos;

uniform mat4 uTransform;

void main()
{
    gl_Position =  vec4(aPos, 1.0) * uTransform;
}
The transformation matrix will be calculated as follows:
namespace OpenTKTutorial
{
    public class Scene
    {
        RenderManager renderManager;
        VAOManager vaoManager;
        ObjectManager objectManager;
        ShaderManager shaderManager;

        public Scene()
        {
            vaoManager = new VAOManager();
            shaderManager = new ShaderManager();
            objectManager = new ObjectManager(vaoManager, shaderManager);
            renderManager = new RenderManager(objectManager);

            InitScene();
        }

        private void InitScene()
        {

            objectManager.AddObject(ObjectType.Quad2D);
            objectManager.gameObjects[0].Transform.Position = new OpenTK.Mathematics.Vector3(0, 0, 0);
            objectManager.gameObjects[0].Transform.Rotation = new OpenTK.Mathematics.Vector3(45, 0, 0);
            objectManager.gameObjects[0].Transform.Scale = new OpenTK.Mathematics.Vector3(1, 1, 0);
           
            Matrix4 Rotation = OpenTK.Mathematics.Matrix4.CreateRotationX(MathHelper.DegreesToRadians(objectManager.gameObjects[0].Transform.Rotation.X));
            Matrix4 Scale = OpenTK.Mathematics.Matrix4.CreateScale(objectManager.gameObjects[0].Transform.Scale);
            // rule : Translate x Rotation x Scale
            Matrix4 Transform = Rotation * Scale;

            objectManager.gameObjects[0].Transformation = Transform;
        }
        
        ...

Devamını Oku »

Organizing Project (OpenGL OOP)

The project needed a little bit of organization. I decided to add some manager classes and I tried to wrap OpenGL operations. In this way, the project can be maintainable and readable. I tried to create an architecture with the best fit in my mind, but then it will probably need more improvement. I don't claim that this organization is a very good implementation certainly.

The project directory will look like it at the end of the post:
  • managers/
    • ObjectManager.cs
    • RenderManager.cs
    • SceneManager.cs
    • ShaderManager.cs
    • VAOManager.cs
  • objects/
    • components/
      • Transform.cs
    • GameObject.cs
    • Mesh.cs
    • Scene.cs
  • shaders/
    • triangle/
      • vertexshader.glsl
      • fragmentshader.glsl
    • Shader.cs
  • App.cs
  • Program.cs
  • Window.cs
I'm going to explain briefly which file will be responsible for what. 

SceneManager.cs: This manager responsible for scene objecs. These scene objects will be updated and rendered in the SceneManager class.
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public class SceneManager
    {
        public Dictionary<string, Scene> Scenes;
        public string ActiveScene {get; set;}

        public SceneManager()
        {   
            Scenes = new Dictionary<string, Scene>();
        }
        public void AddScene(String sceneName, Scene scene)
        {
            Scenes[sceneName] = scene;
            ActiveScene = sceneName;
        }

        public void Update()
        {
            Scenes[ActiveScene].Update();
        }

        public void Draw()
        {
            Scenes[ActiveScene].Draw();
        }
    }
}
Scene.cs: This object is representation of the scene obviously. Scene object will be contain four references called ObjectManager, RenderManager,  SceneManager, ShaderManager. We added our game objects from here for now.
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public class Scene
    {
        RenderManager renderManager;
        VAOManager vaoManager;
        ObjectManager objectManager;
        ShaderManager shaderManager;

        public Scene()
        {
            vaoManager = new VAOManager();
            shaderManager = new ShaderManager();
            objectManager = new ObjectManager(vaoManager, shaderManager);
            renderManager = new RenderManager(objectManager);

            InitScene();
        }

        private void InitScene()
        {
            for (int i = 0; i < 1000; i++)
            {
                objectManager.AddObject(ObjectType.Quad2D);
                objectManager.gameObjects[i].Transform.Position = new OpenTK.Mathematics.Vector3(new Random().Next(-1000,1000) / 1000f, new Random().Next(-1000,1000) / 1000f, 0);
            }
            // objectManager.AddObject(ObjectType.Quad2D);
        }

        public void Update()
        {
            objectManager.Update();
        }

        public void Draw()
        {
            renderManager.Draw();
        }
    }
}
VAOManager.cs: This manager responsible for Vertex Array Object IDs and their index count. We will create VAO from this class. I used Dictionary structure to manage easily. If VAO is already created for one of the ObjectTypes, we don't need to create another VAO for the same ObjectType. ObjectType defined as enum in the GameObject class.
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public class VAOManager
    {
        Dictionary<ObjectType, int> vaos;
        Dictionary<ObjectType, int> indexCounts;

        public Dictionary<ObjectType, int> VAOs { get { return vaos; } }
        public Dictionary<ObjectType, int> IndexCounts { get { return indexCounts; } }
        
        public VAOManager()
        {
            vaos = new Dictionary<ObjectType, int>();
            indexCounts = new Dictionary<ObjectType, int>();
        }

        public void CreateVAO(ObjectType objType)
        {
            if(!VAOs.ContainsKey(objType))
            {
                Mesh mesh = new Mesh(objType);
                vaos[objType] = mesh.VAO;
                indexCounts[objType] = mesh.IndexCount;
            }
        }
    }    
}
Mesh.cs: This classes represent of the shape as primitive. It will contains VBO, VAO and EBO ids. We will call this class to create VAO in the VAOManager class. We will create specific shapes like cube, circle, quad, etc. through this class.
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public class Mesh
    {
        int vao, vbo, ebo;
        int indexCount;

        public int VAO { get { return vao; }  }
        public int VBO { get { return vbo; } }
        public int EBO { get { return ebo; } }
        public int IndexCount {get {return indexCount; } }

        public Mesh(ObjectType objType)
        {
            if(objType is ObjectType.Quad2D) GenerateQuad();
        }

        private void GenerateQuad()
        {   
            float len = 0.01f;

            float[] vertices = 
            {
                -len,  len, 0f,
                 len,  len, 0f,
                 len, -len, 0f,
                -len, -len, 0f
            };

            int[] indices = 
            {
                0, 1, 3,
                1, 2, 3
            };

            indexCount = 6;

            GenerateVAO(len, vertices, indices);
        }

        private void GenerateVAO(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, 0, 0);
            
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.BindVertexArray(0);
        }
    }
}
ShaderManager.cs: This class responsible for shaders. I'm going to store shaders of ObjectType like Quad2d, Cube via this class. Also, we will create shaders from this class like what we did in VAOManager:
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public class ShaderManager
    {
        Dictionary<ObjectType, Shader> shaders;

        public Dictionary<ObjectType, Shader> Shaders { get { return shaders; } }

        public ShaderManager()
        {   
            shaders = new Dictionary<ObjectType, Shader>();
        }

        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;
                }       
            }
        }
    }
}
ObjectManager.cs: This class responsible for GameObjects. It have references of VAOManager and ShaderManager objects. We will add new GameObject via this class and also we are going to update these existed GameObjects in the update method:
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public class ObjectManager
    {   
        private VAOManager VAOManager;
        private ShaderManager ShaderManager;
        public List<GameObject> gameObjects;

        public ObjectManager(VAOManager VAOManager, ShaderManager ShaderManager)
        {
            gameObjects = new List<GameObject>();
            this.VAOManager = VAOManager;
            this.ShaderManager = ShaderManager;
        }

        public void AddObject(ObjectType objectType)
        {
            this.VAOManager.CreateVAO(objectType);
            this.ShaderManager.CreateShader(objectType);
            GameObject gameObject = new GameObject(objectType);
            gameObject.VAO = this.VAOManager.VAOs[objectType];
            gameObject.IndexCount = this.VAOManager.IndexCounts[objectType];
            gameObject.Shader = this.ShaderManager.Shaders[objectType];
            this.gameObjects.Add(gameObject);
        }

        public void Update()
        {
            foreach (var obj in gameObjects)
            {   
                obj.Update();
            }
        }
    }
}
RenderManager.cs: This class responsible for just rendering. Probably, I'm not using it properly. But for now, keep it that way, I'll most likely update it in the next posts.
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public class RenderManager
    {
        private ObjectManager? objectManager;

        public RenderManager(ObjectManager objectManager)
        {
            this.objectManager = objectManager;
        }

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

        public void Draw()
        {
            Clear();

            for (int i = 0; i < objectManager!.gameObjects.Count; i++)
            {
                objectManager.gameObjects[i].Draw();
            }
        }
    }
}
GameObject.cs: It is a GameoObject class. I also defined ObjectType as enum in this file. GameObject contains VAO id, IndexCount, Shader reference, and Transform as a component. I also added a draw method to draw it. We call this method from RenderManager class:
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public enum ObjectType
    {
        Cube,
        Plane,
        Triangle2D,
        Quad2D
    }

    public class GameObject
    {
        ObjectType objectType;

        public int VAO { get; set; }
        public int IndexCount { get; set; }
        public Shader? Shader {get; set;}
        public Transform Transform {get; set;}

        public GameObject(ObjectType objectType)
        {
            Transform = new Transform();
            this.objectType = objectType;
        }

        public void Update()
        {
            
        }

        public void Draw()
        {
            GL.UseProgram(Shader!.ShaderProgram);

            GL.Uniform3(0, Transform.Position);

            GL.BindVertexArray(VAO);
            GL.DrawElements(PrimitiveType.Triangles, IndexCount, DrawElementsType.UnsignedInt, 0);
            GL.BindVertexArray(0);
        }
    }
}
Transform.cs: Transform is a component for game objects. It will store Position, Rotation, and Scale values of a game object:
using OpenTK.Graphics.OpenGL;
using SFML.Window;

namespace OpenTKTutorial
{
    public class Transform
    {
        public OpenTK.Mathematics.Vector3 Position {get; set;}
        public OpenTK.Mathematics.Vector3 Rotation {get; set;}
        public OpenTK.Mathematics.Vector3 Scale {get; set;}

        public Transform()
        {
            Position = new OpenTK.Mathematics.Vector3(0,0,0);
            Rotation = new OpenTK.Mathematics.Vector3(0,0,0);
            Scale = new OpenTK.Mathematics.Vector3(0,0,0);
        }
    }
}
After that, we just need to use SceneManager object in App class. We've freed the App class pretty much from complexity. If 1000 game objects are drawn successfully on the screen as in the image, there is no problem:
test of the organized project


Devamını Oku »

Uniforms

Uniform is a global variables to using communicate from CPU to GPU in shaders. That's what I understand from Uniform. For example, if we want to manipulate the shader at CPU side, we can use uniforms for it.

Uniforms is defined in shader sources. They can be accessed by uniform methods of OpenGL in CPU. Let's make an example:

#version 330 core

in vec3 aPos;

uniform vec3 uMove;

void main()
{
    gl_Position = vec4(aPos + uMove, 1.0);
}
I added a uniform variable called uMove to the vertex shader. Let's reach this uniform via OpenGL method as follows:
private void Draw()
        {
            Clear();

            // draw triangle
            GL.UseProgram(shader.ShaderProgram);
            
            int uniformLocation_Move = GL.GetUniformLocation(shader.ShaderProgram, "uMove");
            GL.Uniform3(uniformLocation_Move, positionRectangle);

            GL.BindVertexArray(vao);
            // GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
            GL.DrawElements(PrimitiveType.Triangles, 6, DrawElementsType.UnsignedInt, 0);
            GL.BindVertexArray(0);
        }
I opened Window class and added new property called ActiveKey and the keyboard key value will be assigned when the user pressed the key via EventHandler that I added like in the below:
    public class Window : RenderWindow
    {
        ...

        public Keyboard.Key ActiveKey {get; set;}

        ...

        private void InitWindowSettings()
        {
            this.SetVerticalSyncEnabled(true);
            view = new View(new FloatRect(0, 0, this.Size.X, this.Size.Y));
            this.SetView(view);
            this.Closed += (o, e) => { this.Close(); };
            this.Resized += (o, e) => { Resize((int)e.Width, (int)e.Height); };
            this.KeyPressed += (o, e) => { ActiveKey = e.Code; };
            this.SetActive(true);
            GL.Viewport(0, 0, (int)this.Size.X, (int)this.Size.Y);
        }
Turn back to the App class and change the update method like this:
        private void Update()
        {
            if(window.ActiveKey == Keyboard.Key.A) positionRectangle -= new OpenTK.Mathematics.Vector3(0.01f, 0f, 0f);
            if(window.ActiveKey == Keyboard.Key.D) positionRectangle += new OpenTK.Mathematics.Vector3(0.01f, 0f, 0f);
            if(window.ActiveKey == Keyboard.Key.W) positionRectangle += new OpenTK.Mathematics.Vector3(0f, 0.01f, 0f);
            if(window.ActiveKey == Keyboard.Key.S) positionRectangle -= new OpenTK.Mathematics.Vector3(0f, 0.01f, 0f);
            window.ActiveKey = Keyboard.Key.Unknown;
        }
After that, we can move the rectangle with keyboard on the screen. This is an ugly code. Normally, we have to organize the project as well. But it's just for learning stage.
Devamını Oku »

Index Buffer

Everything we draw in opengl is actually composed of triangles. If we want to draw a rectangle then we need to two triangles to create it. We have to know something that is important called Index Buffer at this point. 
a rectange which is created with two triangles
Normally, we can draw this shape with 6 vertices. But, if you notice 2 vertices are same for these triangles. Therefore, we need to store two extra vertices. Maybe, that's not a problem just for one rectangle. But it is a problem when drawing thousands vertices. As a solution for this issue, there is an option called Index Buffer. 
First we collect the vertices to be used in an float array that like we did before. For example, I'm going to use these vertices for this post:
  1. (-0.5, 0.5, 0)
  2. (0.5, 0.5, 0)
  3. (0.5, -0.5, 0)
  4. (-0.5, -0.5, 0)
If you notice each one of these vertices has own index value like (1,2,3,..). So, these vertices will be represented by unsigned integer typed values.
  1. (1, 2, 3) -> draw first triangle
  2. (2, 3, 4) -> draw second triangle
Let's how to do it in source code:
    public class App
    {
        Window? window;
        int vbo, vao, ebo;
I added new field called ebo which stands for element buffer object. Also the vertices and indices were added like in the below:
            float[] vertices = 
            {
                -0.5f, 0.5f, 0f,	// 0
                 0.5f, 0.5f, 0f,	// 1
                 0.5f, -0.5f, 0f,	// 2
                -0.5f, -0.5f, 0f	// 3
            };

            uint[] indices = 
            {
                0, 1, 3,
                1, 2, 3
            };
The ebo is created as follows and bind it and copied data from indices array to it:
            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");
            GL.EnableVertexAttribArray(attrPosition_Position);
            GL.VertexAttribPointer(attrPosition_Position, 3, VertexAttribPointerType.Float, false, 0, 0);
                    
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.BindVertexArray(0);
After that, we need to change draw method as DrawElements instead of DrawArrays method:
        private void Draw()
        {
            Clear();

            // draw triangle
            GL.UseProgram(shader.ShaderProgram);

            GL.BindVertexArray(vao);
            // GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
            GL.DrawElements(PrimitiveType.Triangles, 6, DrawElementsType.UnsignedInt, 0);
            GL.BindVertexArray(0);
        }
We got this result:
Rectangle in OpenTK
Devamını Oku »

Usage of Vertex Color

A triangle was drew in the previous post. We used just positions as attributes. But, we can give different attributes these vertices. I'm going to give three different colors (red, green, blue) to three vertices. 

Firstly, I changed a little bit vertex shader source code like in the below:
#version 330 core

in vec3 aPos;
in vec3 aColor;

out vec3 outColor;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    outColor = aColor;
}
The fragment shader source code was changed according to the vertex shader:
#version 330 core

in vec3 outColor;

out vec4 FragColor;

void main()
{
    FragColor = vec4(outColor, 1.0f);
}
When we gave different color for each vertex, OpenGL interpolated these colors as default. It gives gradient effect from the color to another color visually. 

Let's change our float array called vertices as follows:
            float[] vertices = 
            {
                // x, y, z, r, g, b
                -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,  
                 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 
                 0.0f,  0.5f, 0.0f, 0.0f, 0.0f, 1.0f
            };
We will add new attribute pointer for color. The important thing is to know how to use stride and offset parameters of VertexAttribPointer.
            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");
            GL.EnableVertexAttribArray(attrPosition_Position);
            GL.VertexAttribPointer(attrPosition_Position, 3, VertexAttribPointerType.Float, false, sizeof(float) * 6, 0);
            // color attribute
            int attrPosition_Color = GL.GetAttribLocation(shader.ShaderProgram, "aColor");
            GL.EnableVertexAttribArray(attrPosition_Color);
            GL.VertexAttribPointer(attrPosition_Color, 3, VertexAttribPointerType.Float, false, sizeof(float) * 6, sizeof(float) * 3);
            
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.BindVertexArray(0);
I tried to explain how we can detect stride and offset for attributes as follows:
Stride and Offset

 Let's see the output:
interpolation triangle
Devamını Oku »

Drawing Triangle using OpenTK

In the previous posts we basically created the project template and also mainly talked about shader, vbo and vao. We are going to draw triangle using the information we have learned.
Drawing Triangle
Let's start drawing operation. Firstly, I'm going to define fields called vbo, vao and shader in App class:
    public class App
    {
        Window? window;
        int vbo, vao;
        Shader shader;
Let's create shaders folder in the project directory. After that, I will create new sub folder named triangle in this folder. This triangle folder will contain two files called vertexShader.glsl and fragmentShader.glsl. Let's start type source code of vertex shader:
#version 330 core
in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos, 1.0);
}
aPos variable represent a attribute. It will take vertex position of each vertex. For now, we just take position. But, in the future we will pass another attributes like color, texture, normals, etc. gl_Position is default variable that we have to use when defining position of the vertex. After the vertex shader works for each vertex, and move to fragment shader. Meanwhile, different processes occur. You should know how to work the rendering pipeline briefly. in meaning is input for vertex shader. If we want to pass some data from vertex shader to fragment shader. We can use out for it. Let's start type source code of fragment shader:
#version 330 core

out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
In this shader, we calculate color of pixel and, then output this pixel. FragColor is a default variable that we have to use for it. out meaning is output of fragment shader. We assign same color for each pixel.  vec4(red, green, blue, alpha)

After that, let's return the App class and create a new method called DrawTriangle. In this triangle firstly I create a shader object:
        public App()
        {
            ...
            DrawTriangle();
        }

        private void DrawTriangle()
        {
            shader = new Shader(
                "shaders/triangle/vertexShader.glsl",
                "shaders/triangle/fragmentShader.glsl"
            );
            ...
I created a float array named vertices that contains three 3d positions of triangle:
        private void DrawTriangle()
        {
            shader = new Shader(
                "shaders/triangle/vertexShader.glsl",
                "shaders/triangle/fragmentShader.glsl"
            );

            // three 3D positions
            float[] vertices = 
            {
                // x, y, z
                -0.5f, -0.5f, 0.0f, 
                 0.5f, -0.5f, 0.0f, 
                 0.0f,  0.5f, 0.0f
            };
            ...
Now, we need to pass this data to GPU via VBO. Firstly we will create VAO and bind it then we are going to create VBO and bind it also. After that, we will copy this data to vbo via BufferData method, and finally we will define attributes which tell OpenGL how to use this data correctly:
        private void DrawTriangle()
        {
            ...

            // three 3D positions
            float[] vertices = 
            {
                ...
            };
            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
            int attrPosition_Position = GL.GetAttribLocation(shader.ShaderProgram, "aPos");
            GL.EnableVertexAttribArray(attrPosition_Position);
            GL.VertexAttribPointer(attrPosition_Position, 3, VertexAttribPointerType.Float, false, 0, 0);
            
            // unbind vbo
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            // unbind vao
            GL.BindVertexArray(0);
The last thing we need to do is drawing this triangle:
        private void Draw()
        {
            Clear();

            // draw triangle
            GL.UseProgram(shader.ShaderProgram);
            GL.BindVertexArray(vao);
            GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
            GL.BindVertexArray(0);
        }
We need to activate shader program at first. After that, we will bind vao that is what we want to draw. Finally we use DrawArrays method to draw shape according to the activated vao. Our first index will be zero as start index and we will draw three vertex as triangle. We will unbind vao after draw method. After that, let's run the project:
Drawing triangle using OpenTK

Devamını Oku »

What are VBO and VAO?

VBO - It stands for Vertex Buffer Object. For example, we want to draw triangles. We create float array which contains position data of the three vertices. OpenGL can not use this data directly from CPU. We have store this data in GPU to use it. We will use VBO for uploading vertex data to GPU. We can think VBO as array in GPU like float array which we created in CPU side. For detailed information from Vertex Specification - OpenGL Wiki (khronos.org)

We can create vbo for vertex position data, and also we can create another vbo for vertex color data. But also, we can create just one vbo for both data.

VAO - It stands for Vertex Array Object. OpenGL could not know how to use the data from vbo. We have to explain this data what is it to OpenGL. So, we will do this describing operation with attribute pointers. However, we will have to do this every time when we use it. As a solution we can cache this attributes via VAO at once. So we don't need describe the data anymore in the source code. You can take a look this article also: Tutorial2: VAOs, VBOs, Vertex and Fragment Shaders (C / SDL) - OpenGL Wiki (khronos.org)

VBO and VAO
Creating VAO and VBO Step by Step:
  1. Create vertices float array to draw something.
  2. Define vbo and vao interger typed variables.
  3. Create VAO using GenVertexArrays(1, vao). (We will just use one vao)
  4. Bind this vao using BindVertexArray(vao). After this command, the vao was activated in OpenGL global state.
  5. Create VBO using GenBuffers(1, vbo). Because we have just one vbo to use it currently.
  6. Bind this vbo to ArrayBuffer using BindBuffer(BufferTarget.ArrayBuffer, vbo). We activated vbo in short. After that we can load data(vertices) to the vbo. 
  7. Copy the data to vbo using BufferData(BufferTarget.ArrayBuffer, sizeof(float) * vertices.Length, vertices, BufferUsageHint.StaticDraw).
  8. Now we are going configure attributes. These attributes have index from 0 to 15. 0. index is about position. We will just set one attribute for now:
    1. EnableVertexAttribArray(0)
    2. VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 0, 0)
  9. Unbind vbo using BindBuffer(BufferTarget.ArrayBuffer, 0);
  10. Unbind vao using BindVertexArray(0);
Devamını Oku »