PyOpenGL과 GLFW를 사용하여 삼각형을 그려보는 Modern OpenGL 예제입니다.



다음 깃허브의 코드를 바탕으로 강좌를 진행하고 있습니다.

https://github.com/totex/PyOpenGL_tutorials



Python으로 배우는 Modern OpenGL - 1. 개발환경 만들기

https://webnautes.tistory.com/1271





1. GLFW를 초기화합니다.


   if not glfw.init():
       return




2. 크기 800 x 600의 윈도우를 생성합니다. 생성시 문제가 발생하면  GLFW를 사용해 생성한 윈도우와 context 관련 자원을 해제합니다.


    window = glfw.create_window(800, 600, "My OpenGL window", None, None)

   if not window:
       glfw.terminate()
       return




3. 새로 생성한 윈도우의 context를 현재 쓰레드에서 사용하도록 합니다.


  glfw.make_context_current(window)




4.  삼각형을 그리기 위한 버텍스(=꼭지점)과 버텍스를 위한 색상을 정의합니다.  버텍스 배치 순서는 반시계 방향입니다.


   #            positions    colors
   triangle = [ 0.0,  0.5, 0.0, 1.0, 0.0, 0.0, # red

                0.5, -0.5, 0.0, 0.0, 1.0, 0.0, # green
               -0.5, -0.5, 0.0, 0.0, 0.0, 1.0] # blue



다음과 같은 좌표계에 삼각형을 그립니다. 원점이 중앙에 있습니다.



위에서 정의한 대로 버텍스와 버텍스를 위한 색을 배치하면 다음처럼 됩니다.





5. 버텍스 위치와 컬러 데이터를 float32 타입의 NumPy 배열로 변환합니다. NumPy 배열의 크기는 4 바이트 x 6  = 72 바이트입니다.


   triangle = numpy.array(triangle, dtype = numpy.float32)

   print(triangle.itemsize * triangle.size)




6. 버텍스 버퍼 객체에 저장되어 있는 버텍스 데이터를 GPU상에서 처리하게 될 Vertex Shader 소스 코드를 작성합니다.

vertex 속성 중 position만 처리하고 color나 texture coordinate 같은 속성은 다음 단계로 넘겨주는 역활을 합니다.


#version 전처리기 지시어는 사용되는 shader 언어의 버전을 명시합니다. OpenGL 3.3 이후 부터는 OpenGL 버전과 shader 언어의 버전이 일치합니다.


in 키워드를 사용하여 Vertex Shader의 입력(=vertex 속성)을 지정해 줍니다.

position, color는 각각 3차원 벡터(vec3)로 vertex 속성 중 (x,y,z), (r, g, b)값을 Vertex Shader의 입력으로 받는 변수입니다.


VBO에 저장한 vertex 데이터와 vertex 속성 변수간의 연결에 대해서는 나중에 다룹니다.


vec3 타입의 position 변수의 값을  vec4 타입의 gl_Position 전역변수에 대입하고 있는데 4번째 컴포넌트인 w를 위해 1.0을 지정해주고 있습니다. homogenous 좌표로 저장하기 위해서인데 자세한건 나중에 다룹니다.


각 vertex의 color 속성의 경우 out 키워드로 지정된 출력 변수 newColor로 대입되어 Fragment Shader로 전달됩니다.


   vertex_shader_source = """
   #version 330
   in vec3 position;
   in vec3 color;

   out vec3 newColor;
   void main()
   {
       gl_Position = vec4(position, 1.0f);
       newColor = color;
   }
   """




7. Vertex Shader에서 전달한 vertex의 color 속성인 newColor 변수를 받아서 출력 변수 outColor에 전달합니다.


Vertex Shader에서 3개의 vertex에 대한 처리를 했지만 Fragment Shader에서는 더 많은 수의 fragment를 대상으로 처리를 해야합니다.


렌더링 파이프라인의 바로 전 단계인 래스터화(rasterization) 단계에서 vertex에 의해서 구성되는 primitive  내부 영역이 fragment로 변환되었기 때문입니다. 이 때 color 속성도 fragment별로 보간된 값을 갖게 됩니다.


OpenGL에서 color는 red, green, blue, alpha 컴포넌트로 구성되기 때문에 vec4를 출력 변수의 타입으로 사용합니다. color의 컴포넌트는 0.0~1.0 사이의 값을 갖습니다.


   fragment_shader_source = """
   #version 330
   in vec3 newColor;

   out vec4 outColor;
   void main()
   {
       outColor = vec4(newColor, 1.0f);
   }
   """




8.  Vertex Shader 소스와 Fragment Shader 소스를 컴파일하여 새로 생성한 프로그램 객체에 포함시킵니다.

나중에 Shader 객체를 렌러딩 처리에서 사용하도록 glUseProgram() 함수가 호출됩니다.


   vertex_shader = shaders.compileShader(vertex_shader_source, GL_VERTEX_SHADER)
   fragment_shader = shaders.compileShader(fragment_shader_source, GL_FRAGMENT_SHADER)
   shader = shaders.compileProgram(vertex_shader, fragment_shader)




9. glGenBuffers 함수를 호출하여 새로운 버퍼 객체를 생성하고 glBindBuffer 함수를 호출하여 저장할 데이터 타입으로 버텍스 버퍼 객체(VBO)를 지정해줍니다.

버텍스 버퍼 객체는 버텍스 데이터를 GPU 메모리에 복사해놓기 위해서 사용됩니다.

렌더링시 CPU를 거치지 않고 GPU에서 바로 데이터 접근해서 처리하는게 가능해지므로 렌더링 속도가 빨라집니다.


glBufferData 함수를 호출하여 실제로 버퍼 객체를 위한 비디오 메모리 공간을 할당하고 버텍스 데이터를  버퍼 객체에 복사합니다.

나머지 아규먼트 의미는 다음과 같습니다.

  • 변수 triangle로부터 전체 데이터 4바이트 x 18 = 72바이트를 복사합니다.

  • 버텍스 속성을 저장할 것이므로 GL_ARRAY_BUFFER를 사용합니다.

  • 데이터 변경이 없을 것이므로 GL_STATIC_DRAW를 사용합니다.


   VBO = glGenBuffers(1)
   glBindBuffer(GL_ARRAY_BUFFER, VBO)
   glBufferData(GL_ARRAY_BUFFER, 72, triangle, GL_STATIC_DRAW)




10.  Vertex Shader 코드에 선언된 position 속성 변수에서 버텍스 버퍼 객체에 저장된 버텍스 데이터를 접근하는 방법을 지정해줘야 합니다.


position 속성의 크기는 3입니다.

position 속성 간격은 4바이트 * 6 = 24바이트이며 position 속성이 시작되는 위치는 맨 첫위치부터 이므로 0입니다.


   position = glGetAttribLocation(shader, "position")
   glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
   glEnableVertexAttribArray(position)




10.  Fragment Shader 코드에 선언된 color 속성 변수에서 버텍스 버퍼 객체에 저장된 버텍스 데이터를 접근하는 방법을 지정해줘야 합니다.


color  속성의 크기는 3입니다.

color 속성 간격은 4바이트 * 6 = 24바이트이며 color 속성이 시작되는 위치는 position 속성 다음이므로 12 바이트입니다.


   color = glGetAttribLocation(shader, "color")
   # 컬러 속성이 시작되는 위치는 포지션 속성 다음 위치 즉, 4 * 3 = 12바이트 후입니다.
   glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
   glEnableVertexAttribArray(color)




11. glUseProgram() 함수를 호출하여  프로그램 객체에 포함된 Shader를 렌더링 처리시 사용할 것임을 알립니다.


   glUseProgram(shader)




12. 컬러 버퍼 지울 때 색을 검은색을 사용합니다.


   glClearColor(0, 0, 0, 0)




13. 윈도우를 닫기 전까지 루프를 반복합니다.  이벤트 처리를 위해 루프 첫줄을 실행할때 마다 poll_events()를 호출합니다.


   while not glfw.window_should_close(window):
       glfw.poll_events()




14. 호출하면 실제로 컬러 버퍼가 지워집니다. 렌더링 결과 앞에서 glClearColor함수로 지정한 색으로 윈도우가 채워지는 것처럼 보입니다.


       glClear(GL_COLOR_BUFFER_BIT)




15.  glDrawArrays()함수를 호출하면 실제 렌더링이 시작되어 화면에 삼각형을 그리게 됩니다.

버텍스 데이터 객체의 첫번째 인덱스 위치부터 3개의 버텍스를 가져와 화면에 삼각형을 그립니다.


       glDrawArrays(GL_TRIANGLES, 0, 3)




16. 루프 내에서 화면에 원하는 것을 그리고,  swap_buffers()함수를 호출하면 화면이 업데이트 됩니다.


GLFW에서는 기본적으로 더블 버퍼링을 사용합니다. 즉, 각 윈도우는 2 개의 렌더링 버퍼를 갖습니다. - 프론트 버퍼와 백 버퍼

화면 전환을 자연스럽게 하기위해 백 버퍼에다가 다음에 화면에 보여줄 영상(렌더링 결과물)을 준비해놓습니다.

백 버퍼에서 준비가 완료되면 현재 프론트 버퍼와 역활을 바꿔서 백 버퍼의 내용을 화면에 출력해줍니다.


       glfw.swap_buffers(window)




17.  GLFW를 사용해 생성한 모든 윈도우와 context를 위한 자원을 해제합니다.


   glfw.terminate()




다음은 이번 포스팅의 전체 소스 코드입니다.


import glfw
from OpenGL.GL import *
import OpenGL.GL.shaders as shaders
import numpy


def main():

   if not glfw.init():
       return

   window = glfw.create_window(800, 600, "My OpenGL window", None, None)

   if not window:
       glfw.terminate()
       return

   glfw.make_context_current(window)


   #            positions    colors
   triangle = [-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]


   triangle = numpy.array(triangle, dtype = numpy.float32)

   print(triangle.itemsize * triangle.size)

   vertex_shader_source = """
   #version 330
   in vec3 position;
   in vec3 color;

   out vec3 newColor;
   void main()
   {
       gl_Position = vec4(position, 1.0f);
       newColor = color;
   }
   """

   fragment_shader_source = """
   #version 330
   in vec3 newColor;

   out vec4 outColor;
   void main()
   {
       outColor = vec4(newColor, 1.0f);
   }
   """

   vertex_shader = shaders.compileShader(vertex_shader_source, GL_VERTEX_SHADER)
   fragment_shader = shaders.compileShader(fragment_shader_source, GL_FRAGMENT_SHADER)
   shader = shaders.compileProgram(vertex_shader, fragment_shader)


   VBO = glGenBuffers(1)
   glBindBuffer(GL_ARRAY_BUFFER, VBO)
   glBufferData(GL_ARRAY_BUFFER, 72, triangle, GL_STATIC_DRAW)


   position = glGetAttribLocation(shader, "position")
   glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
   glEnableVertexAttribArray(position)

   color = glGetAttribLocation(shader, "color")
   glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
   glEnableVertexAttribArray(color)

   glUseProgram(shader)

   glClearColor(0, 0, 0, 0)

   while not glfw.window_should_close(window):
       glfw.poll_events()

       glClear(GL_COLOR_BUFFER_BIT)

       glDrawArrays(GL_TRIANGLES, 0, 3)

       glfw.swap_buffers(window)

   glfw.terminate()


if __name__ == "__main__":
   main()




최초 작성  2018. 12. 3

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기

+ Recent posts