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)

No comments:

Post a Comment