본문 바로가기

Python/OpenGL

[OpenGl] (GetIntoGameDev) 02. Coloured Triangle

(pointer)(pointer)

 

아래는 'GetIntoGameDev' Youtube 강의와 GitHub를 통해 공부한 포스팅입니다.

 

 

GitHub - amengede/getIntoGameDev

Contribute to amengede/getIntoGameDev development by creating an account on GitHub.

github.com

 

 

(GetIntoGameDev) OpenGl 강의 02. Coloured Triangle
색칠된 삼각형 만들기
OpenGl에서는 최소 Vertex Shader와 Fragment Shader는 직접 작성 하여 사용하여야 한다. (GPU에 Vertex Shader와 Fragment Shader가 내장되어 있지 않다.)

일반적으로 OpenGL 에서는 Shader를 txt 파일로 만들어 로드 한다. string 으로 정의 하여 로드 할 수도 있지만 잘 사용하지 않는 방법이다.

OpenGl에서는 GLSL(GL Shader Language) 를 통해 Shader를 구현하고 내부적으로 컴파일하여 현재 프로그램에 사용한다. GLSL은 C언어 기반으로 C언어 와 매우 유사하다.

 

  • Vertex Shader 구현
Vertex Shader는 랜더링 하고자 하는 물체의 모든 정점에서 한번씩 호출되고 각 정점에 대한 정보를 Graphics Pipline을 통과할 수 있는 형태로 변환하여 출력한다. positions(위치), color(색), normal vectors(법선벡터), texture(질감), coordinates(좌표) 등의 정보를 가진 Vertex 데이터가 Vertex Shader를 통과하여 Graphics Pipline에서 보간되고 2d shape를 채운다. 이때 Vertex 데이터를 채우는 positions(위치), color(색) 등의 데이터 들을 Vertex Attributes(점정 속성) 이라고 한다.

아래 코드는 위치와 색만을 반환하는 Vertex Shader 코드의 구현이다.

 

@version 330 core

  먼저 버전을 선언한다. (OpenGl 3.3 부터는 GLSL 버전과 OpenGL의 버전이 맞아야 한다.) 위 코드는 3.3 버전의 core profile 기능을 사용할 것이라고 명시해주는 구간이다.

 

layout (location=0) in vec3 vertexPos; // 위치
layout (location=1) in vec3 vertexColour; // 색

  in 을 통해 Shader가 입력받을 Vertex(정점)의 위치(position)와 색(color)을 저장할 변수를 지정한다.  선언할때 location을 명시 해주어야 하는데 그 이유는 추후 OpenGl에서 Vertex Shader를 사용 할때 각 정점 속성의 위치(pointer)를 찾을 수 있도록 하기 위해서 이다. location 을 명시해 주지 않으면 OpenGl 은 어느 위치(pointer)에 어떤 속성이 있는지 알 수 없다.

 

out vec3 fragmentColour;

  out 을 통해 Vertex 색 속성을 출력할 변수를 선언한다. 해당 변수는 이번 실습에서는 Fragment Shader의 입력 변수로 들어가게 된다. 이건 추측 이지만 이번 실습에서는 색에 관련해서 보간할 것이 없어 Vertex Shader가 색 정보를 곧바로 Fragment Shader에 전달 해주는 것 같다. Graphics Pipeline 에서는 Vertex Shader를 통해 data를 전달 받은 후 각 속성 정보를 조합하여 색 보간을 진행하는 단계가 있는데 그 단계 실습을 하게 되면 곧 바로 넘기지 않게 될것 같다.

 

void main()
{
    gl_Position = vec4(vertexPos, 1.0);
    fragmentColour = vertexColour;
}

  입력받은 위치와 색 변수를 반환한다. fragmentColour는 우리가 출력변수로 명시하였던 변수 이고 gl_Position은 system 이 미리 선언해 놓은 변수 이다. gl_Position 변수 명을 오입력하면 추후 OpenGl을 돌릴때 위치 데이터를 입력받지 못해 오류가 발생한다. gl_Position은 4차원 벡터 형태의 data 이므로 형식을 맞추어 준다.

  아래는 최종 Vertex Shader 코드 이다.

#version 330 core

layout (location=0) in vec3 vertexPos;
layout (location=1) in vec3 vertexColour;

out vec3 fragmentColour;

void main()
{
    gl_Position = vec4(vertexPos, 1.0);
    fragmentColour = vertexColour;
}

 

* Shader 에서 Vector 데이터 타입사용
Shader 에서는 총 4차원 까지 Vector 변수를 선언할 수 있고 벡터 데이터를 접근/이용 하는데 유연한 요소를 가진다.
>> 선언]
vec1, vec2, ... vec4 등등 vec뒤에 숫자를 붙여 차원을 명시하여 백터 변수를 생성한다.
ex) vec4 position; // -> 4차원 float 벡터 형태의 position 변수

>> 접근1] 변수명.위치
변수명.x, 
변수명.y, 
변수명.z, 
변수명.w

>> 접근2] 변수명.위치들
변수명.xyzw
변수명.yz

>> 이용] swizzling
vec2 pos2;
vec4 pos4 = pos2.xyxx
( 위와 같이 데이터 타입이 달라도 차원수 만 맞춰 준다면 유연하게 이용 할 수 있다.)

  • Fragment Shader 구현
Frament Shader 는 pixel의 최종 픽셀 컬러를 생성한다. 이전 강에서 색은 r,g,b,alpha 로 이루어진 4차원 vector로 표현하고였고 Frament Shader 또한 4차원 vector 형태로 색을 출력하겠다. 파일명은 fragment.txt 로 한다.

Frament Shader에서 최종 픽셀 컬러를 지정하는 것이 실패 하면 OpenGL에서는 검정색 혹은 흰색을 반환한다.

 

#version 330 core

in vec3 fragmentColour;

out vec4 colour;

void main()
{
    colour = vec4(fragmentColour, 1.0);
}

  입력 받은 rgb 의 3차원 벡터로 이루어진 fragmentColour 변수를 alpha 값까지 포함된 4차원 벡터로 반환한다.


  • 삼각형을 그리는 class 객체 구현
class Triangle:
    def __init__(self, shader):
        # 현재 프로그램에 shader 프로그램 설치.
        glUseProgram(shader)

  먼저 Triangle class 객체를 만들고 생성자를 통해 초기화 한다. 현재 프로그램에 shader 프로그램을 붙인다.

 

        self.vertices = (
            -0.5, -0.5, 0.0, 1.0, 0.0, 0.0,
            0.5, -0.5, 0.0, 0.0, 1.0, 0.0,
            0.0, 0.5, 0.0, 0.0, 0.0, 1.0
        )
        
        self.vertex_count = 3
        self.vertices = np.array(self.vertices, dtype=np.float32)

  Vertex Data를 저장할 vertices 변수와 Vertex 갯수(corner의 갯수)를 저장할 vertex_count 변수를 초기화 한다. vertices 변수에서 6개의 float형 수는 각각 position 을 나타내는 x,y,z와 color를 나타내는 r,g,b 이다. 기본적으로 Vertex Data는 -1와 1사이의 정규화된 장치 좌표(device coordinate) 안에 존재 한다.

  position data는 다음과 같은 의미를 가진다.

x : 0은 중앙. 음수는 중앙보다 왼쪽에 양수는 오른쪽에 위치
y : 0은 중앙. 음수는 중앙보다 아래쪽에 양수는 위쪽에 위치
z(depth) : 0은 우리가 level of screen, 화면의 수준에 있다는 것을 의미한다.(2차원)

  color data는 0~1의 수를 가지는데 다음과 같은 의미를 가진다.

r : 숫자가 커질 수록 red 와 가까운 색을 의미
g : 숫자가 커질 수록 green 과 가까운 색을 의미
b : 숫자가 커질 수록 blue 와 가까운 색을 의미

  OpenGL에서 이용가능하도록 vertices 튜플을 numpy 배열로 변환 해준다. data type 은 32비트의 float형으로 선언한다. 64비트로 data type을 지정하면 오류가 발생 할 수도 있다.

 

        # VAO 생성
        self.vao = glGenVertexArrays(1)
        # VAO 바인딩
        glBindVertexArray(self.vao)
        
        # VBO 생성
        self.vbo = glGenBuffers(1)
        # VBO 바인딩
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        # 최근 바인딩된 VBO에 Vertex Data 복사
        glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)
        
        # Vertax Shader의 location 0번 속성을 활성화
        glEnableVertexAttribArray(0)
        
        # 현재 바인딩된 VBO에서 Vertax Shader의 location 0번 속성을 연결하고,
        # 해당하는 Vertex Data의 사용 규칙을 명시
        # Vertex Data가 정규화되어 있지 않다면 GL_True를 통해 정규화 가능
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
        
        # Vertax Shader의 location 1번 속성을 활성화
        glEnableVertexAttribArray(1)
        
        # 현재 바인딩된 VBO에서 Vertax Shader의 location 0번 속성을 연결하고,
        # 해당하는 Vertex Data의 사용 규칙을 명시
        # Vertex Data가 정규화되어 있지 않다면 GL_True를 통해 정규화 가능
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))

  VAO 와 VBO 를 생성/초기화 하여 연결한다.

 

    def draw(self, shader):
    	# 현재 프로그램에 shader 프로그램 설치.
        glUseProgram(shader)
        
        # 생성자를 통해 설정된 VAO를 glDrawArrays 함수에서 사용할 수 있도록 바인딩
        glBindVertexArray(self.vao)
        
        # self.vao에 reference 된 VBO의 Vertex Data를 통해 삼각형 프리미티브 랜더링
        glDrawArrays(GL_TRIANGLES, 0, self.vertex_count)

  draw 함수를 통해 생성자에서 설정해두었던대로 삼각형을 그린다.

 

    # Data와 GPU를 claean up
    def destroy(self):
        glDeleteVertexArrays(1, (self.vao,))
        glDeleteBuffers(1, (self.vbo,))

  작업이 다 끝난 후 Data와 GPU를 비울 destory 함수를 구현한다.


  • Window 창에 삼각형 그리기
  이전 강의에서 구현했던 Window창을 띄우는 코드를 그대로 가져와 Window창에 정의 했던 Triangle Class로 삼각형을 그린다.

 

    # txt 로 구현했던 GLSL Code를 Compile
    def createShader(self, vertexFilepath, fragmentFilepath):
        # 코드를 string으로 받아와 저장
        with open(vertexFilepath, 'r') as f:
            vertex_src = f.readlines()

        with open(fragmentFilepath, 'r') as f:
            fragment_src = f.readlines()
		
        # Shader 컴파일
        # 코드 상단에 OpenGL.GL.shaders 에서 제공하는 
        # compileProgram,compileShader을 import 해주어야 한다.
        
        shader = compileProgram(compileShader(vertex_src, GL_VERTEX_SHADER),
                                compileShader(fragment_src, GL_FRAGMENT_SHADER))

        return shader

  GLSL 로 작성했던 Shader를 사용 하기 위해  Compile 해주는 함수를 구현 한다.

 

    def __init__(self):
        #initialise pygame
        pg.init()
        pg.display.set_mode((640,480), pg.OPENGL|pg.DOUBLEBUF)
        self.clock = pg.time.Clock()
        #initialise opengl
        glClearColor(0.1, 0.2, 0.2, 1)
		
        # shader 생성
        self.shader = self.createShader("./shaders/vertex.txt", "./shaders/fragment.txt")
        # triangle 객체 생성
        self.triangle = Triangle(self.shader)
        
        self.mainLoop()

  생성자에 Shader와 Trinangle 객체 를 생성한다.

 

    def mainLoop(self):
        running = True
        while (running):
            #check events
            for event in pg.event.get():
                if (event.type == pg.QUIT):
                    running = False
            #refresh screen
            glClear(GL_COLOR_BUFFER_BIT)
            
            # 삼각형 그리기
            self.triangle.draw(self.shader)
            
            pg.display.flip()

            #timing
            self.clock.tick()
            framerate = int(self.clock.get_fps())
            pg.display.set_caption(f"Running at {framerate} fps.")
        self.quit()

  mainLoop 에 삼각형을 그리는 코드를 추가한다.


 

 

실행 결과

Window 창에 색 있는 삼각형 그리기

 

 

마무리

  사용된 라이브러리에 대한 상세 설명과 기타는 아래 포스팅에 정리하였다.

'Python > OpenGL' 카테고리의 다른 글

[OpenGl] (GetIntoGameDev) 01. Window Creation  (0) 2021.09.26
[OpenGl] OpenGl intro  (0) 2021.09.25