Post Lists

2018년 8월 6일 월요일

Advanced OpenGL - Advanced Data (7)

https://learnopengl.com/Advanced-OpenGL/Advanced-Data

Advanced Data
우리는 꽤 어떤 시간동안 데이터를 저장하기 위해서 OpenGL에서 버퍼를 광범위하게 사용하고 있다. 버퍼를 조작하는 좀 더 흥미로운 방법들과, 텍스쳐를 통해 쉐이더에 많은 양의 데이터를 전달하는 다른 흥미로운 방법들이 있다. 이 튜토리얼에서, 우리는 몇 가지 좀 더 흥미로운 버퍼 함수들을 다루고, 어떻게 우리가 많은 양의 데이터를 저장하기 위해서 텍스쳐 오브젝트를 사용할 수 있는지를 이야기 할 것이다. (튜토리얼의 텍스쳐 부분은 아직 쓰여지지 않았다.)

OpenGL에서 한 버퍼는 메모리의 어떤 부분을 관리하는 한 오브젝트이기만 하다. 우리는 그것을 특정한 buffer target에 바인드시켜 한 버퍼에 의미를 준다. 한 버퍼는 우리가 GL_ARRAY_BUFFER에 바인드 시킬 때, 오직 vertex array buffer이지만, 우리는 쉽게 그것을 GL_ELEMENT_ARRAY_BUFFER에 바인드 시킬 수 있다. OpenGL은 내부적으로 target마다 버퍼를 저장하고, 타겟을 기반으로, 그 버퍼를 다르게 처리한다.

지금까지, 우리는 메모리 조각을 할당하고, 데이터를 이 메모리에 추가하는 glBufferData를 호출하여 buffer objects에 의해 관리되는 메모리를 채워왔다. 만약 우리가 그것의 데이터 인자로서 NULL를 넘긴다면, 그 함수는 오직 메모리만을 할당하고 그것을 채우지 않는다. 이것은 우리가 처음에 특정한 양의 메모리를 보유하고 나중에 이 버퍼를 채우려고 한다면 유용하다.

전체 버퍼를 한 함수 호출로 채우는 것 대신에, 우리는  또한 glBufferSubData를 호출하여 버퍼의 특정 영역을 채울 수 있다. 이 함수는 buffer target, offset, 그리고 data의 사이즈, 실제 data를 그것의 인자로서 기대한다. 이 함수에서 새로운 것은, 우리가 이제 어디에서 부터 우리가 그 버퍼를 채우고 싶은지의 offset를 줄 수 있다는 것이다. 이것은 우리가 버퍼의 메모리의 어떤 부분들을 insert/update할 수 있도록 허용한다. 그 버퍼가 충분히 할당된 메모리를 갖도록 해야만 하는 것에 주의해라. 그래서 glBufferData에 대한 호출은 그 버퍼에 대해 glBufferSubData를 호출하기전에 필수적이다.

  // Range: [24, 24+sizeof(data)]
  glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data);

그러나 데이터를 한 버퍼에 넣는 또 다른 방법은 버퍼의 메모리에 대한 포인터를 요청하여, 직접적으로 스스로 데이터를 버퍼에 복사시키는 것이다. glMapBuffer를 호출하여, OpenGL은 우리를 위해 작동하는 현재 바인드된 버퍼의 메모리에 대한 포인터를 반환한다:


float data[] = 
{
   0.5f, 1.0f, -0.35f,
   ....
};
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// get pointer
void* ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// now copy data into memory
memcpy(ptr, data, sizeof(data);
// make sure to tell OpenGL we're done with the pointer
glUnmapBuffer(GL_ARRAY_BUFFER)

glUnmapBuffer를 통해 OpenGL에게 우리가 포인터 연산작업을 마무리했다고 말하여, OpenGL은 너가 끝난다는 것을 안다. unmapping하여, 그 포인터는 무효하게 되고, 그 함수는 만약 OpenGL이 너의 데이터를 성공적으로 버퍼에 매핑시킬 수 있다면 GL_TRUE를 반환한다.

glMapBuffer를 사용하는 것은 데이터를 직접적으로 버퍼에 매핑 시키기 위해서 유용하다. 그것을 임의의 메모리에 처음에 저장하지 않고. 직접적으로 파일로부터 데이터를 읽어서 그 버퍼의 메모리로 복사하는 것을 생각해 보아라.

Batching vertex attributes
glVertexAttribPointer를 사용하여, 우리는 vertex array buffer의 내용의 attribute layout을 명시할 수 있었다. vertex array buffer 내에서, 우리는 그 attributes들을 interleave한다; 즉, 우리는 각 정점에 대해 position, normal and/or texture coordinates를 서로서로의 다음에 배치 했다. 우리는 버퍼에 대해 조금 알게 됐으니, 우리는 다른 접근법을 취할 수 있다.

우리가 또한 할 수 있는 것은 그것들을 interleave하는 대신에 모든 vector data를 attribute type마다 큰 chunks로 묶는 것이다. interleaved layout 123123123123 대신에, 우리는 batched approach인 111122223333을 취한다.

파일로부터 vertex data를 불러올 때, 너는 일반적으로 positions의 한 배열, normals의 한 배열 and/or 텍스쳐 좌표의 한 배열을 가져온다. 이러한 배열들을 interleaved data의 한 큰 배열로 합치는 것은 노력이 필요할지도 모른다. 그 batching approach를 취하는 것은 우리가 glBufferSubData를 사용하여 쉽게 구현할 수 있는 더 쉬운 해결책이다:


float positions[] = {...};
float normals[] = {...};
float tex[] = {...};

// fill buffer
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);

이 방식으로, 우리는 그것들을 직접적으로 가공하지 않고 직접적으로 전체의 attribute array들을 버퍼로 옮길 수 있다. 우리는 또한 그것들을 하나의 큰 배열에 묶을 수 있고, glBufferData를 사용하여 바로 버퍼를 채울 수 있지만, glBufferSubData는 그 자체로 이러한 것과 같은 일에 완벽하게 도움이 된다.

우리는 또한 vertex attribute pointers를 이러한 변화를 반영하기 위해 업데이트 해야만 한다:


glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));

stride parameter가 vertex attribute의 사이즈와 같다는 것에 주목해라. 왜냐하면 다음의 vertex attribute vector가 그것의 3 (또는 2) 컴포넌트 들 후에 발견되기 때문이다.

이것은 우리에게 vertex attributes를 설정하고 명시하는 다른 접근법을 준다. 둘 중 하나의 접근법을 사용하는 것은 OpenGL에 어떠한 이득이 없지만, 그것은 대개 vertex attributes들을 설정하는 좀 더 깔끔한 방식이다. 너가 사용하기 원하는 접근법은 순수하게 너의 선호와 프로그램의 유형을 기반으로 한다.

Copying buffers
버퍼가 데이터로 채워진다면, 너는 그 데이터를 다른 버퍼들과 귱하기를 원할 수도 있다. 또는 그 버퍼의 내용을 다른 버퍼로 복사하길 원할 수도 있다. glCopyBufferSubData 함수는 우리가 한 버퍼에서 다른 버퍼로 상대적으로 쉽게 데이터를 복사할 수 있도록 해준다. 그 함수의 prototype은 다음과 같다:

  void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset,
                                     GLintptr writeoffset, GLsizeiptr size);

readtarget과 writetarget parameters는 우리가 어디에서 어디로 복사하길 원하는 버퍼 타켓들을 주기를 기대한다. 우리는 예를들어, VERTEX_ARRAY_BUFFER 버퍼에서 VERTEX_ELEMENT_ARRAY_BUFFER로 복사하길 원할 수 있다. 그러한 버퍼들의 타겟을 read and write targets으로 각각 명시하여서. 그러한 버퍼 타겟들에 현재 바인드된 버퍼들은 그러고나서 영향을 받을 것이다.

그러나 우리가 데이터를 읽고 둘 다 vertex arry buffers인 두 개의 다른 버퍼들에 쓰기를 원한다면 어떨 까? 우리는 두 개의 버퍼들을 동시에 같은 버퍼 타겟에 바인드 할 수 없다. 이 이유 때문에, OpenGL은 우리에게 GL_COPY_READ_BUFFER와 GL_COPY_WRITE_BUFFER라고 불리는 두 개 더 버퍼 타겟을 준다. 우리는 그러고나서 우리의 선택에 따라 버퍼들을 이러한 새로운 버퍼 타겟에 바인드하고, 그러한 타겟들을 readtarget과 writetarget 인자로서 설정한다.

glCopyBufferSubData는 그러고나서 주어진 readoffset으로부터 주어진 size의 데이터를 읽어서, 그것을 writeoffset에서 writetarget에 write한다. 두 vertex array buffers의 내용을 복사하는 한 예는 아래에서 보여진다:


float vertexData[] = {...};
glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));

우리는 또한 writetarget buffer를 새로운 buffer target types에 바인드하여 이것을 할 수 있다:


float vertexData[] = {...};
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));

버퍼를 어떻게 조작하는지에 대해 몇 가지 추가 지식으로, 우리는 벌써 좀 더 흥미로운 방식으로 그것들을 사용할 수 있다. OpenGL에서 너가 좀 더 깊이 들어갈 수록, 이러한 새로운 버퍼 방식들을 좀 더 실용적이게 된다. uniform buffer objects를 다루는 다음 튜토리얼에서 우리는 glBufferSubData를 잘 이용할 것이다.

댓글 없음:

댓글 쓰기