OpenGL은 데이터를 저장하고 접근하기 위해 2개 형태의 데이터 스토리지를 제공한다.
- 버퍼
- 텍스처
버퍼
버퍼는 타입이 정해져 있지 않은 데이터의 연속 공간이다. 버퍼는 이름으로 구별된다. 버퍼를 사용하기 전에 OpenGL을 통해 사용할 이름을 예약해야 하고, 그 이름을 사용해서 메모리를 할당하고 그 메모리에 데이터를 저장한다.
버퍼의 이름을 알았다면, 이 이름을 OpenGL contaxt에 attach가 가능하다. attach하기 위해서는 그 이름을 버퍼 바인딩 포인트에 바인딩시켜야 한다. 바인딩 포인트는 타깃이라고도 부른다.
버퍼를 사용하여 메모리 할당하기
버퍼 객체를 사용하여 메모리를 할당하기 위해 사용하는 함수는 glBufferData()
다. 자세한 함수 설명은 [여기]에서 확인할 수 있다.
다음 코드는 glGenBuffers()
를 호출하여 버퍼에 대한 이름을 예약하는 방법이다. 그리고 glBindBuffer()
를 사용하여 그 버퍼가 해당 콘텍스트에 어떻게 바인딩 되는지, 또 어떻게 호출하여 스토리지를 할당하는지 알 수 있다.
1
2
3
4
5
6
7
8
# 버퍼에 대한 이름을 생성한다.
vbo = glGenBuffers(1)
# GL_ARRAY_BUFFER 바인딩 포인트를 사용하여 콘텍스트에 바인딩한다.
glBindBuffer(GL_ARRAY_BUFFER, vbo)
# 버퍼에 사용하고자 하는 스토리지의 크기를 반영한다.
glBufferData(GL_ARRAY_BUFFER, 1024 * 1024, NULL, GL_STATIC_DRAW)
vbo는 어떤 용도일지는 모르지만 NULL로 초기화된 1메가바이트의 스토리지를 갖도록 초기화된 버퍼 객체의 이름을 담는다.
이 vbo에 데이터를 전달하고 싶다면 glBufferSubData()
를 사용하여 OpenGL로 데이터를 보내 직접 복사하도록 하는 것이다. 자세한 함수 설명은 [여기]에서 확인할 수 있다.
버퍼로부터 버텍스 쉐이더에 입력 전달하기
버택스 배열 객체(vertex array object, VAO)를 하나 만들어 버텍스 배열 상태를 저장하도록 하자.
1
2
3
4
5
6
vao = glGenVertexArray(1)
glBindVertexArray(vao)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices)
glEnableVertexAttribArray(0)
vao를 생성했고 바인딩했다. OpenGL에 데이터가 버퍼 객체의 어디에 있는지 알려주기 위해 glVertexAttribPointer()
함수를 사용하고, 속성을 자동으로 채우도록 glEnableVertexAttribArray()
를 호출한다. 자세한 함수 설명은 [여기]에서 확인할 수 있다.
이제 버텍스 쉐이더를 보자.
1
2
3
4
5
6
7
8
9
vertex_shader_source = """
#version 430 core
layout(location=0) in vec3 position;
void main()
{
gl_Position = vec4(position, 1.0);
}
"""
이렇게 된다면, glEnableVertexAttribArray()
가 실행될 당시 바인딩된 버퍼로부터 읽어온 데이터를 자동적으로 버텍스 쉐이더의 첫 번째 속성에 채운다.
여러 개의 버텍스 쉐이더 입력 사용하기
버텍스 쉐이더에 색상을 추가해보자.
1
2
3
4
5
6
7
8
9
10
11
12
vertex_shader_source = """
#version 430 core
layout(location=0) in vec3 position;
layout(location=1) in vec3 color;
out vec3 newColor;
void main()
{
gl_Position = vec4(position, 1.0);
newColor = color;
}
"""
버텍스 쉐이더 입력을 애플리케이션 데이터로 연결시키는 2개 방식이 있다.
- 독립 속성
- 인터리브 속성
속성이 독립이라는 의미는 다른 버퍼에 위치해 있을 수 있거나 적어도 동일한 버퍼에서 다른 위치에 있을 수 있다는 의미다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -1에서 1사이의 랜덤한 정점 좌표 생성
vertices = np.random.uniform(-1.0, 1.0, (20, 3)).astype(np.float32)
# 0에서 1사이의 랜덤한 색상 생성
colors = np.random.uniform(0.0, 1.0, (20, 3)).astype(np.float32)
# VBO 생성
vbo_position = glGenBuffers(1)
vbo_color = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo_position)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, vbo_color)
glBufferData(GL_ARRAY_BUFFER, colors.nbytes, colors, GL_STATIC_DRAW)
# enable vertex attributes
glBindBuffer(GL_ARRAY_BUFFER, vbo_position)
position_location = glGetAttribLocation(shader, "position")
glVertexAttribPointer(position_location, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(position_location)
glBindBuffer(GL_ARRAY_BUFFER, vbo_color)
color_location = glGetAttribLocation(shader, "color")
glVertexAttribPointer(color_location, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(color_location)
이렇게 vbo_position과 vbo_color 두 개의 버퍼 객체를 생성하고 배열을 입력하였다.
인터리브 속성은 동일한 버퍼의 다른 오프셋 위치에 넣는 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -1에서 1사이의 랜덤한 정점 좌표 생성
vertices = np.random.uniform(-1.0, 1.0, (20, 3)).astype(np.float32)
# 0에서 1사이의 랜덤한 색상 생성
colors = np.random.uniform(0.0, 1.0, (20, 3)).astype(np.float32)
# 두 배열을 하나의 배열로 합침
interleaved_data = np.concatenate((vertices, colors))
# VBO 생성
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, interleaved_data.nbytes, interleaved_data, GL_STATIC_DRAW)
# enable vertex attributes
position_location = glGetAttribLocation(shader, "position")
glVertexAttribPointer(position_location, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
glEnableVertexAttribArray(position_location)
color_location = glGetAttribLocation(shader, "color")
glVertexAttribPointer(color_location, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(3 * 4))
glEnableVertexAttribArray(color_location)
전체 코드는 [깃허브] 를 확인하면 된다.
Reference
• OpenGL Super Bible 개정 6판