Wireless Debugging on Android Devices

I have published my application named 'Coğrafya İnteraktif Harita', which is one of the mobile applications I developed over the last two weeks. 

I am writing this blog post to document the fast debugging method I use during development. I mean, this is not exactly a secret, but it might serve as useful information for those who are unaware of it or haven't realized it yet.

Under normal circumstances, debugging through the Android simulator provided by Android Studio or standalone emulators can be painful depending on your environment. In contrast, debugging directly on a real Android phone is highly fast and comfortable. Of course, this situation might also vary depending on the size of the application. Anyway, I remember that in the past, I had to connect my phone to the computer to debug; because the connection was constantly dropping—whether due to the USB cable itself or the computer's erratic behavior—the debugging process turned into a complete ordeal for me and really put me off. After a long time, when I returned to mobile development for a small application, I joyfully experienced that there was actually no need for this cable situation at all. The solution is 'Wireless Debugging'. Back then, my old Android device probably did not feature such a function, so I couldn't benefit from this blessing. Anyway, without further ado, let's see how Wireless Debugging is done using ADB.

Note: Both your computer and your Android device must be connected to the same Wi-Fi network

First, let's open the command prompt on your Windows computer:

C:\Users\gurbu>adb devices
* daemon not running; starting now at tcp:5037
* daemon started successfully
List of devices attached
First, I performed a routine check to see if any device was connected, and it appears that none are. Now, let's open the Android phone; you will likely access the required settings section on your phone in a similar manner.

Wireless Debugging on Android Devices

Open the Wireless Debugging.
Wireless Debugging on Android Devices
Make sure 'USB debugging' and 'Wireless debugging' are selected. Additionally, press and hold 'Wireless debugging' to open the following page:
Wireless Debugging on Android Devices

The sections that concern us here are the 'IP address & Port' section and the 'Pair device with pairing code' section. First, click on 'Pair device with pairing code':

Wireless Debugging on Android Devices
Now, I want to pair the devices according to the information provided above, as shown in the command prompt below:

C:\Users\gurbu>adb pair 192.168.1.102:44749
Enter pairing code: 611794
Successfully paired to 192.168.1.102:44749 [guid=adb-XXXXXXXXXXX-XXX]
I have completed the pairing process; once paired, your computer should already appear listed under the 'paired devices' list.

Wireless Debugging on Android Devices

Now we can connect to this Android device from our computer wirelessly. This time, we enter the IP and port information written under the 'IP address & Port' section. Note: This is not the IP address and port that we used during pairing; take a close look at my screenshots.
C:\Users\gurbu>adb connect 192.168.1.102:32979
connected to 192.168.1.102:32979
It appears that we have successfully connected; let's check the devices list once again:
C:\Users\gurbu>adb devices
List of devices attached
192.168.1.102:32979     device
adb-xxxxxxxxxxxxx._adb-tls-connect._tcp       device
Our device has now been successfully connected to the computer wirelessly for debugging.

We can also see that the device is visible and ready to be used on Visual Studio.
Wireless Debugging on Android Devices Visual Studio
Devamını Oku »

Map Design of the 2D RPG Game

Now we've come to the part I hate the most. Probably the most important aspect of an RPG is the game map. I haven't quite decided exactly what it will be like yet. I hope I don't make a bad start. I know I need to set up a chunk system anyway. However, I don't want to make my game map tile-based. I don't really like the grid look. That's why I asked Gemini for advice. It suggested things like decalling and splatting, which are exactly what I have in mind, but the terminology isn't very familiar to me. Now it's time to get to work. I'm going to define a WorldManager and manage the chunks from there.

If you don't know how the chunk system works, I previously made a simple application related to this topic. You can check out that post here Devlog #2 Isometric Projection

I found a library called FastNoiseLite, and it's quite useful. I think it will allow me to achieve a non-repeating appearance for terrain.

Terrain Generation for 2D Game

It's good.I could work on this until dawn to get a good result. My goal is to ensure the randomness of that landform.

2D terrain landform

I guess I would have to work on the grass texture later. But now it's like enough. I want to see the map when the character moved. That's why I need a camera also.

It worked, but there is some performance issue about chunk rendering. It's to slow and not efficient.

My problem was solved after lowering the splat resolution. I also applied the map to be drawn according to the central chunk where the player is located. However, I'm still not happy with the grass area, so it needs an update...
Terrain Grass
And that's it. Well that's enough for now. Another post, I'm going to focus the character design, and a rigging tool.


Devamını Oku »

Starting The Basic RTS Game Project Structure

First, let's start by slowly building the project structure. I mentioned that I'll be using Monogame. I previously set up a Monogame template in VS 2022, so I can create a project directly. You can take look from this link(Setting up Visual Studio on Windows | MonoGame) I created LdgRpg project as monogame desktop cross-platform project.

I executed and I can see cornflowerblue color filled display. First, I want to add my character. Therefore, I will create an entities folder and add a class called Character. In this class, I will define general properties of this character, such as its position, speed, and size.

Of course, I'll have a general character pool. Therefore, I'll create a manager class to manage this character collection collectively. I also want to assign GUIDs to my characters; this might be useful later.

Normally, Monogame offers a class like Game1, but I want to build my own separate structure. Of course, I will use this structure within the Game1 class. I'm creating my own game manager. This manager will be the hub for managing all other managers.
protected override void LoadContent()
{
    _spriteBatch = new SpriteBatch(GraphicsDevice);
    gameManager = new GameManager();
    // TODO: use this.Content to load your game content here
}
Now we need to add some ridiculous character texture. I'll create a texture manager structure for this. Because managing textures one by one is too much trouble. And let's draw it:
And there it is my little ugly man right there. That's enough for now.
Devamını Oku »

Let's develop an RPG game

After a long time, I'm decided to back this useless blog and revived again. Well, too much changes happened in the tech. You know AI-era(or hype?) thing, idk. Honestly, after these changes, I feel like content creation has not really mattered anymore. So, at least the kind of content I produce doesn't really matter anymore. People can now create games with simple concepts using just a single prompt. Or they can say "hey teach me that how can i make this game step by step?" to an AI. That's why that's blog going to be more personal about my amateur game development journey. My posts won't contain as much source code as before. This will be a blog where I mostly write about my own process and my observations during this process. Briefly, it won't be like a tutorial. 

 Let's get to the main point. I've decided to make a simple rgp game. Honestly, I don't have much of an idea how i am going to proceed right now, but it will be become clear as I progress, I guess. But I think I'm going to use Monogame framework. I don't want to mess around with a game engine. Of course, I'll use AI to help with this process, but I don't plan on doing anything too AI-generated in the game itself. I think I'll only be able to generate a few basic codes. On the game art side, I'm planning to do pixel art drawing. But that doesn't mean I'll only use pixel art assets. I'll also create and use designs in Blender. I'll use Aseprite for pixel art. I think it would be a good idea to use the power of a blender, as well. After developing the game, I want to release it on a platform, but I am not entirely sure whether it will be mobile or desktop. Right now, I'm leaning towards mobile. 

This post will also serve as the homepage for this development process, so I plan to list the other posts here. The posts are the following:

Devamını Oku »

Service Provider in .NET MAUI

namespace Temptor.Helpers
{
    public static class ServiceProvider
    {
        public static TService GetService<TService>()
        {
            return Current.GetService<TService>();
        }

        public static IServiceProvider Current
        {
            get 
            {
                #if WINDOWS10_0_17763_0_OR_GREATER
                    return MauiWinUIApplication.Current.Services;
                #else
                    return null;
                #endif
            }
        }
    }
}
No need to use constructor injection for any service if necessary:
namespace Temptor.Models
{
    public class Property
    {
        public PropertyService ropertyService;

        ...

        public Property() 
        {
            this.PropertyService = Helpers.ServiceProvider.GetService<PropertyService>();

            ...
        }
    }
}
Devamını Oku »

Shader Codes

 Circle Drawing:

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    float2 center = float2(0.5, 0.5);

    float distance = length(coords - center);

    float radius = 0.4;

    float4 color = (distance < radius) ? float4(0, 0, 0, 1) : float4(1, 1, 1, 1);

    return color;
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

 sin and cos

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{

    float amplitude = 0.9;
    float frequency =55.0;
    
    // vertical
    float sinValue = amplitude * sin(coords.x * frequency);

    // horizontal
    float cosValue = amplitude * cos(coords.y * frequency);

    float4 color = float4(sinValue, 0, cosValue, 1);

    return color;
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

 Basic Animation:

Pass the time parameter from update method with these code lines that float time = (float)gameTime.TotalGameTime.TotalMilliseconds;, and then effect.Parameters["time"].SetValue(time);.

float time;

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    float amplitude = 0.9;
    float frequency =55.0;
    
    // vertical
    float sinValue = amplitude * sin((coords.x + time * 0.5) * frequency);

    float4 color = float4(sinValue, 0, 0, 1);

    return color;
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    return float4(coords.x, 0.0, 0.0, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    return float4(0.0, coords.y, 0.0, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    return float4(uv.x, uv.y, 0.0, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    return float4(0.0, distance, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance -= 0.7;

    distance = abs(distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance -= 0.7;

    distance = abs(distance);

    //distance = step(0.1, distance)
    distance = smoothstep(0.0, 0.2, distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance = cos(distance * 10)/10;

    distance = abs(distance);

    //distance = step(0.1, distance)
    distance = smoothstep(0.0, 0.2, distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float time;

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);


    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance = cos(distance * 10 + (time * 0.005))/10;

    distance = abs(distance);

    //distance = step(0.1, distance)
    distance = smoothstep(0.0, 0.2, distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float time;

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);
    
    uv *= 4;
    uv = frac(uv);
    uv -= 0.5;

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance = cos(distance * 10 + (time * 0.005)) / 10;

    distance = abs(distance);

    //distance = step(0.1, distance)
    distance = smoothstep(0.0, 0.2, distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float time;

static const float PI = 3.14159;

float2x2 rotate3d(float angle) {
    return float2x2(cos(angle), -sin(angle), sin(angle), cos(angle));
}

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    float2 p = coords * 2 - float2(1,1);

    float temp = (time * 0.005) * PI;

    p = mul(rotate3d(temp), p);

    float t = 0.075 / abs(0.7 - length(p));

    float vl = 0.2 * (sin(time) + 3.0);
    float3 temp2 = float3(t, t, t) * float3(vl, p.y * 0.8, 3.0);

    return float4(temp2, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

Devamını Oku »

A UI library for MonoGame - Devlog #1

I've been working on my own ui library for a while. Progress is a bit slow because I don't have much time. I can say it's more of a project I pursue for hobby purposes. 

There are some reasons why I am doing such a project:

  • I can work on projects in Monogame, so I might need some UI support.
  • I couldn't find a UI library in Monogame that truly meets my requirements. (There are some issues for ImGUI in Monogame)
And other things that I haven't thought of yet.

So far in the project, I'm progressing with two elements: Component and Panel. The components will be placed inside the panels. Yes, simply like that. I don't want it to be more complex for now. I have created elements such as panel, button, and scrollbar. It's functioning adequately in its basic form. In fact, it seems possible to achieve a responsive outcome.

An example image:


The project will not be open source for now. Additionally, my GitHub page has become quite cluttered.

In the next stage, I plan to add basic elements such as horizontal scrollbar, label, text box, grid etc.. I also need to create a suitable structure for handling and capturing events.

Let's create a todo list:
  • Label
  • TextBox
  • Handling Events
  • Horizontal Scrollbar
  • z-order (for collision ex.)
  • Grid (??)
  • ImageButton component (~)

Devamını Oku »

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 »

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 »