Showing posts with label OpenGL. Show all posts
Showing posts with label OpenGL. Show all posts

2D Top-Left Coordinate System in Modern OpenGL

Normally, The positioning of things in OpenGL seems not understandable at least for me. When I coding a game I used a top-left origin coordinate system, however, it's not general standard in game development.  

OpenGL uses a clipping area as a coordinate system like below:

But if we resize according to viewport:

    def render(self):
        glViewPort(0, 0, self.width, self.height)
        ...
We convert to clip space to screen space briefly and specified our canvas dimension to the OpenGL.

OpenGL have to know our screen dimension to conversion operation in vertex shader. So I'm going to use a uniform function to manipulate vertex shader:
#version 330 core

layout (location = 0) in vec2 position;
layout (location = 1) in vec3 color;

uniform vec2 screen_dim;

out vec3 passColor;

void main(){
    vec2 pos_RatioTo1 = position / screen_dim;
    vec2 clip_space = (pos_RatioTo1 * 2.0) - 1;

    gl_Position = vec4(clip_space, 0.0, 1.0);
    passColor = color;
}
I typed two methods called addUnform and setUniform2f to make easier to usage in ShaderProgram class.
def addUniform(self, uniformName):
    self.uniforms[uniformName] = glGetUniformLocation(self.programID, uniformName)

def setUniform2f(self, uniformName, value)
    glUniform2f(self.uniforms[uniformName], value)
So I changed app source by updates above:
def generate(self):
    self.shader = ShaderProgram("./shaders/vs.glsl", "./shaders/fs.glsl")
    self.shader.addUniform("screen_dim")
    ...
    
...

def render(self):
    glViewport(0, 0, self.width, self.height)

    ...

    self.shader.use()
    self.mesh.draw()
    self.shader.setUniform2f("screen_dim", Vector2(self.width, self.height))
So I reorganized positions of vertices like that:
self.buffer['pos'] = [(200, 50), (500, 500), (800,  50)]
Let's examine the result:
opengl coordinate system

But It's not a top-left coordinate system. It's bottom-left coordinate system. So I have to change glPosition variable value in vertex shader source code like this:
gl_Position = vec4(clip_space * vec2(1, -1), 0.0, 1.0);
This multiply operation just reverses of y-axis direction as opposite entirely. There is no affect on the x-axis by knowledge from school math and the result:
top-left coordinate system in OpenGL

Devamını Oku »

VAO and VBO in Modern OpenGL

I'm going to mention the graphics pipeline of OpenGL in this post. This pipeline consists of certain parts:
  1. Vertex Shader
  2. Tesellations Shader
  3. Geometry Shader
  4. Primitive Assembly
  5. Rasterization
  6. Fragment Shader
  7. Per-sample Operation
We will deal with both Vertex Shader and Fragment Shader mostly. Vertex Shader handles each vertice. Fragment shader handles each pixel color and these shaders have their own special variable.
  • Vertex shader's variable is gl_Position 
  • Fragments shader's variable is fragColor
Vertex shader's called for each vertice. Fragment shader's called for each pixel.

Let's say we have vertices to draw something. These vertices are stored on the CPU. But when we are going to draw something, the related vertices are going to pass to GPU as a buffer. This buffer contains the related vertices about what we want to draw and this buffer is created on the CPU.

Let's create our buffer to draw something. This buffer will contain vertices about two attributes. These attributes are position and color:
self.shader = ShaderProgram("./shaders/vs.glsl", "./shaders/fs.glsl")

data = np.zeros(3, dtype = [ 
                        ("pos", np.float32, 2),
                        ("col", np.float32, 3)
                    ])

data['pos'] = [(-0.5, -0.5), (0.0, +0.5), (+0.5, +0.5)]
data['col'] = [(1,0,0), (0,1,0), (1,1,0)]
Let's create a VAO. Vertex Array Object is array of the VBOs. Vertex Buffer Objects contain the vertex data: 
self.triangleVAO = glGenVertexArrays(1)
glBindVertexArray(self.triangleVAO)

# attrib 0
# loaded vbo'position' to triangleVAO  
positionBuffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer)
glBufferData(GL_ARRAY_BUFFER, data['pos'].nbytes, data['pos'], GL_STATIC_DRAW)

positionLocation = glGetAttribLocation(self.shader.programID, 'position')
glEnableVertexAttribArray(positionLocation)
glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), c_void_p(0))

# attrib 1 
# loaded vbo'color' to triangleVAO
colorBuffer = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer)
glBufferData(GL_ARRAY_BUFFER, data['col'].nbytes, data['col'], GL_STATIC_DRAW)

colorLocation = glGetAttribLocation(self.shader.programID, 'color')
glEnableVertexAttribArray(colorLocation)
glVertexAttribPointer(colorLocation, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat), c_void_p(0))

glBindVertexArray(0)
If you notice, GL_ARRAY_BUFFER is actually our bridge to move data from one place to another. We bind the vbo what we load data to GL_ARRAY_BUFFER. And we load the data which in CPU to dataBuffer which is a VBO. These VBOs are introduced by glVertexAttribPointer which is a built-in function in OpenGL.

I run the code and the result should be like this:
gradinet triangle in opengl

According to my view, the VAO is actually an object which can be used as a game object for me. The code above seems complex and if I want to create another VAOs, the code looks terrible obviously. So I'm creating a class that represents VAO:
    def generate(self):
        self.shader = ShaderProgram("./shaders/vs.glsl", "./shaders/fs.glsl")
        self.mesh = Mesh()
        
	...

    def render(self):
        glClearColor(1.0, 1.0, 1.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT)

        self.shader.use()
        self.mesh.draw()
I just type two methods in Mesh class which are __init__ and draw function. There is no change to the above code. I just pasted these functions into it.
Devamını Oku »

Drawing a Triangle - Modern OpenGL with Python

The first thing we will do is a creating program object. A program object is an empty object that we will attach shaders to it.

...
FPS = 60

# we created program object 
program = glCreateProgram() 

while True:
	...
So, we could pass Vertex Shader and Fragment Shader to the program object. These shaders are actually stages of a rendering pipeline. We will type them with GLSL like the C programming language. These shaders determine what we will draw on the screen step by step. We will use two shader types; vertex shader and fragment shader. The vertex shader is the first step on the pipeline that defines vertices of shape that will be drawn on the screen. For instance, if we want to draw a triangle then we need three vertices. The fragment shader is the last state on the rendering pipeline that gives color to every pixel which is in the region between vertices. It will out result on the screen. So I typed these shader's codes as follows:
...

VERTEX_SHADER_SOURCE = '''
    #version 330 core
    layout (location = 0) in vec3 aPos;
    
    void main()
    {
        gl_Position = vec4(aPos, 1.0)
    }
'''

FRAGMENT_SHADER_SOURCE = '''
    #version 330 core

    out vec4 fragColor;
    void main()
    {
        fragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f)
    }
'''

# we created program object 
program = glCreateProgram() 

...
I will talk about the shader codes in detail later. We are trying to draw a triangle and we gave red color this triangle in FRAGMENT_SHADER_SOURCE. Let's use them shader objects which we will create:
...
# we created program object 
program = glCreateProgram()

# we created vertex shader
vertex_shader = glCreateShader(GL_VERTEX_SHADER)
# we passed vertex shader's source to vertex_shader object
glShaderSource(vertex_shader, VERTEX_SHADER_SOURCE)
# and we compile it
glCompileShader(vertex_shader)

...
We will do the same thing for fragment_shader. So if it is done, we will attach these shaders to the program object.
...

# attach these shaders to program
glAttachShader(program, vertex_shader)
glAttachShader(program, fragment_shader)

while True:
	...
Finally, we will link this program object as follows:
...

# link the program
glLinkProgram(program)

while True:
	...
So we can start drawing progress. OpenGL's coordinate system is different from others. For example, pygame display's origin point is in top-left, But if we put a point on (0,0) origin in OpenGL, this point shows itself in the middle of the screen. The other thing is we should know, x-axis and y-axis can values that are between 1 and -1.

Let's create vertices data in a list that will define the position of the triangle's vertices:
...

#  (x, y, z)
vertices = [
    -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0,
    0.0, 0.5, 0.0,
]
vertices = (GLfloat * len(vertices))(*vertices)
...
We have to pass this vertex data to OpenGL. Therefore, we have to use VBO (vertex buffer object). The VBO contains data like this:
...

# create vbo object
vbo = None
vbo = glGenBuffers(1, vbo)

# enable buffer(VBO)
glBindBuffer(GL_ARRAY_BUFFER, vbo)

# send the data  
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW)

...
Now, OpenGL will take this data. However, OpenGL couldn't know what's going on with this data exactly. We have to declare this data to OpenGL structurally. Because this data may contain position, color, etc. like kind of data. Thus, VAO(vertex array object) appears to bring a solution to this problem. To sum up, we use VAO for explaining how to use data to OpenGL.

For explaining data to OpenGL, We use three tokens for using each attribute in data. For instance, we might want to get texture coordinates as an attribute from data.
  • How many elements in the attribute?
  • What is the type of each element in the attribute?
  • Where is the beginning of the attribute?
Probably, it's not a really good explanation. Let's review this code:
# create vao object
vao = None
vao = glGenVertexArrays(1, vao)

# enable VAO and then finally binding to VBO object what we created before.
glBindVertexArray(vao)

# we activated to the slot of position in VAO (vertex array object)
glEnableVertexAttribArray(0)

# explaining to the VAO what data will be used for slot 0 (position slot) 
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), c_void_p(0))
Let's look at the below image:
vertex data in opengl
Now we know how to use data in VAO. First thing, we should enable VAO then we could use data in ARRAY_BUFFER. There is just one attribute called position or slot 0. So we just enabled slot 0. So, we declare the position data in data to VAO via vertexAttribPointer. Let's look at the below image to understanding:
vertexattribpointer usage
Finally, we can draw the triangle on the screen.
    ...

    glClear(GL_COLOR_BUFFER_BIT)
    
    glUseProgram(program)
    glBindVertexArray(vao)
    glDrawArrays(GL_TRIANGLES, 0, 3)
    
    pygame.display.flip()

    ...
If we want to VAO then we have to enabled firstly. We already enabled this VAO but if there are more VAO. It doesn't work properly. Therefore we should enable the VAO before we draw.
glDrawArrays arguments:
  • GL_TRIANGLES is a primitive type we use when we wanted to drive a triangle.
  • The 0 value is the index of the enabled VBO's array.
  • The 3 value is the meaning of three points which will be rendered
And it's the result:
a triangle with opengl
This is full of the code:

import pygame
from OpenGL.GL import *
from ctypes import sizeof, c_void_p

pygame.init()
display = pygame.display.set_mode((800, 600), pygame.DOUBLEBUF|pygame.OPENGL)
clock = pygame.time.Clock()
FPS = 60


VERTEX_SHADER_SOURCE = '''
    #version 330 core
    layout (location = 0) in vec3 aPos;
    
    void main()
    {
        gl_Position = vec4(aPos, 1.0);
    }
'''

FRAGMENT_SHADER_SOURCE = '''
    #version 330 core

    out vec4 fragColor;
    void main()
    {
        fragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
    }
'''

#  (x, y, z)
vertices = [
    -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0,
    0.0, 0.5, 0.0,
]
vertices = (GLfloat * len(vertices))(*vertices)

# we created program object 
program = glCreateProgram()

# we created vertex shader
vertex_shader = glCreateShader(GL_VERTEX_SHADER)
# we passed vertex shader's source to vertex_shader object
glShaderSource(vertex_shader, VERTEX_SHADER_SOURCE)
# and we compile it
glCompileShader(vertex_shader)


# we created fragment shader
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
# we passed fragment shader's source to fragment_shader object
glShaderSource(fragment_shader, FRAGMENT_SHADER_SOURCE)
# and we compile it
glCompileShader(fragment_shader)

# attach these shaders to program
glAttachShader(program, vertex_shader)
glAttachShader(program, fragment_shader)

# link the program
glLinkProgram(program)

# create vbo object
vbo = None
vbo = glGenBuffers(1, vbo)

# enable buffer(VBO)
glBindBuffer(GL_ARRAY_BUFFER, vbo)

# send the data  
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW)

# create vao object
vao = None
vao = glGenVertexArrays(1, vao)

# enable VAO and then finally binding to VBO object what we created before.
glBindVertexArray(vao)

# we activated to the slot of position in VAO (vertex array object)
glEnableVertexAttribArray(0)

# explaining to the VAO what data will be used for slot 0 (position slot) 
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), c_void_p(0))

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    glClearColor(1.0, 0.6, 0.0, 1.0)
    glClear(GL_COLOR_BUFFER_BIT)

    glUseProgram(program)
    glBindVertexArray(vao)
    glDrawArrays(GL_TRIANGLES, 0, 3)
    
    pygame.display.flip()
    clock.tick(FPS)
Devamını Oku »

Introduction to Modern OpenGL with Python

There are already a lot of sources about Modern OpenGL. But I'm gonna keep going on it as basically. Probably, this post doesn't seem like really scientific stuff. Why I prefer Python for this. Because it's just simple and We have to learn a lot of things for a short period. A programming language is just a tool, well it's for just learning something.

I'm gonna use Pygame which Python's module. Pygame will be an interface that we will communicate with OpenGL. 

We need to OpenGL. Let's install it:

pip install PyOpenGL
So I typed code that below:
import pygame
from OpenGL.GL import *

pygame.init()
display = pygame.display.set_mode((800, 600), pygame.DOUBLEBUF|pygame.OPENGL)
clock = pygame.time.Clock()
FPS = 60

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()
    
    glClearColor(1.0, 0.6, 0.0, 1.0)
    glClear(GL_COLOR_BUFFER_BIT)

    pygame.display.flip()
    clock.tick(FPS)
And that's result what we got:

That's good. There are a few different additions in the basic example of pygame. I'm not going deep into OpenGL in this post. But I will strive to be simple and understandable in the next posts that include this series.
Devamını Oku »