Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
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 26 27
28 29 30
Archives
Today
Total
관리 메뉴

게임공장

[Learn OpenGL 번역] 5-7. 고급 OpenGL - 고급 Data 본문

OpenGL

[Learn OpenGL 번역] 5-7. 고급 OpenGL - 고급 Data

짱승_ 2018. 8. 16. 13:12

고급 Data

고급 OpenGL/고급 Data

  우리는 OpenGL에서 데이터를 저장하기 위해 광범위하게 buffer들을 사용해왔습니다. buffer들을 다루는 좀 더 흥미로운 방법과 texture들을 통해 shader에 많은 양의 data를 전달하는 흥미로운 방법이 존재합니다. 이 강좌에서 좀 더 흥미로운 buffer 함수들과 많은 양의 데이터를 저장하기 위해 텍스처 객체를 사용하는 방법을 다룰 것입니다.


  OpenGL에서의 buffer는 특정 메모리를 관리하는 것 그 이상 이하도 아닙니다. 특정 buffer target에 바인딩하여 의미를 부여해줍니다. GL_ARRAY_BUFFER에 바인딩하면 이 buffer는 vertex array buffer인 것입니다. 하지만 이 동일한 버퍼를 GL_ELEMENT_ARRAY_BUFFER에 바인딩할 수도 있습니다. OpenGL은 내부적으로 target에 대해 buffer를 저장하고 이 buffer들을 따로 처리합니다.


  지금까지 메모리를 할당해주고 이 메모리에 데이터를 삽입해주는 glBufferData 함수를 사용하여 buffer 객체에 의해 관리되는 메모리를 채워왔습니다. 만약에 이 함수의 data 파라미터에 NULL 값을 집어넣는다면 이 함수는 메모리 할당만 해주고 데이터를 채워넣지 않습니다. 이는 우리가 먼저 특정 메모리 크기를 reserve(예약)해놓고 나중에 이 buffer를 채우려고 할 경우에 유용합니다.


  하나의 함수를 호출하여 전체 buffer를 채우는 것 대신 glBufferSubData 함수를 사용하여 buffer의 특정 부분을 채우는 것 또한 가능합니다. 이 함수는 buffer target, offset, 데이터의 크기, 실제 데이터를 파라미터로 받습니다. 이 함수의 새로운 점은 이 buffer를 어디에서부터 채울지를 지정하는 offset를 정해줄 수 있다는 점입니다. 이는 buffer 메모리의 특정 부분에만 삽입/수정할 수 있도록 해줍니다. 이 buffer는 충분한 메모리가 할당되어있어야하므로 glBufferSubData 함수를 호출하기 전에 glBufferData 함수를 꼭 호출해야 합니다.


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

  Buffer에 데이터를 집어넣는 또다른 방법은 pointer에게 buffer의 메모리를 요청하고 데이터를 buffer에 직접 복사하는 방법입니다. glMapBuffer 함수를 사용하면 OpenGL은 현재 바인딩된 buffer의 메모리를 가리키고 있는 포인터를 리턴합니다.


float data[] = {
  0.5f, 1.0f, -0.35f
  ...
};
glBindBuffer(GL_ARRAY_BUFFER, buffer);
// 포인터 얻기
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
// 데이터를 메모리에 복사
memcpy(ptr, data, sizeof(data));
// OpenGL에게 이 포인터와 작업을 마친다고 알려줌
glUnmapBuffer(GL_ARRAY_BUFFER);

  glUnmapBuffer 함수를 통해 포인터 작업이 끝났다고 알려줌으로써 OpenGL은 수행이 완료되었다는 것을 알 수 있습니다. Unmapping하여 이 포인터는 무효화되고 이 함수는 OpenGL이 buffer에 데이터를 성공적으로 매핑할 수 있었다면 GL_TRUE를 리턴합니다.


  glMapBuffer 함수를 사용하는 것은 먼저 임시메모리에 저장하지 않고 직접적으로 데이터를 buffer에 매핑하기에 유용합니다. 파일에서 직접적으로 데이터를 읽고 buffer의 메모리에 복사하는 것을 생각해보세요.

Vertex attributes 묶기

  glVertexAttribPointer 함수를 사용하여 vertex array buffer 내용의 attribute layout을 지정할 수 있었습니다. Vertex array buffer 내부에 attribute들을 끼워넣었습니다. 즉, 위치, 법선, 텍스처 좌표들을 다른 것들 옆에 위치시켰다는 것이죠. 이제 buffer에 대해서 좀 더 알았으니 조금 다른 방법을 취할 수 있습니다.


  우리가 또 할 수 있는 일은 모든 벡터 데이터들을 끼워넣는 것이 아니라 attribute 유형마다 큰 덩어리로 묶을 수 있다는 것입니다. 123123123123 이런 식으로 끼워넣는 것이 아니라 111122223333 이런식으로 묶는 방법이 있습니다.


  파일로부터 vertex 데이터를 불러올 때 일반적으로 위치의 배열, 법선의 배열, 텍스처 좌표의 배열을 얻습니다. 이 배열들을 끼워넣어진 형태로 하나의 큰 배열로 결합하는 것은 꽤 큰 비용이 듭니다. 묶는 방법을 취하는 것은 쉬운 해결방법입니다. 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 배열들을 하나의 buffer에 옮길 수 있습니다. 물론 이들을 결합하여 하나의 큰 배열로 만들수 있으며 glBufferData 함수를 사용하여 이 buffer를 채울 수 있습니다. 하지만 glBufferSubData 함수를 사용하면 이러한 목적에 아주 적합합니다.


  또한 이 변화를 반영하기 위해 vertex attribute pointer를 수정해야 합니다.


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 파라미터는 vertex attribute의 크기와 동일하다는 것을 알아두세요. 다음 vertex attribute 벡터가 바로 이 벡터의 3(혹은 2) 요소 다음에 존재하기 때문입니다.


  이는 vertex attribute 설정과 지정에대해서 또다른 접근법을 제공해줍니다. 이런 방법을 사용하는 것은 OpenGL에 즉각적인 효과는 없습니다.vertex attribute를 설정하는 것이 대부분 좀 더 체계화가 되어있는 방법입니다. 여러분이 어떤 방법을 사용할지는 여러분의 선호도와 응용 프로그램의 유형에 달려있습니다.

Buffer 복사

  여러분의 buffer가 데이터로 채워지기만하면 이 데이터들을 다른 buffer들과 공유할 수 있고 이 buffer의 내용을 다른 buffer에 복사할 수도 있을 것입니다. glCopyBufferSubData 함수는 한 buffer에서 다른 buffer로 데이터를 비교적 수월하게 복사할 수 있도록 해줍니다. 이 함수의 프로토타입은 다음과 같습니다.


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

  readtarget, writetarget 파라미터들은 우리가 붙여넣기할, 복사할 buffer target를 지정합니다. 예를 들어 buffer target을 따로 따로 지정하여 VERTEX_ARRAY_BUFFER buffer를 복사하여 VERTEX_ELEMENT_ARRAY_BUFFER buffer에 붙여 넣을 수 있습니다. 이러한 buffer target에 현재 바인딩되어 있는 buffer들은 영향을 받게 됩니다.


  하지만 읽고싶은 buffer와 작성하고 싶은 buffer 둘다 같은 vertex array buffer라면 어떨까요? 우리는 동시에 동일한 buffer target에 2개의 buffer를 바인딩할 수는 없습니다. 이 이유때문에 OpenGL은 2개의 추가적인 buffer target을 제공해줍니다. GL_COPY_READ_BUFFERGL_COPY_WRITE_BUFFER가 바로 그것이죠. 이 새로운 buffer target에 buffer들을 바인딩하고 readtarget, writetarget 파라미터에 넘겨줍니다.


  그러면 glCopyBufferSubData 함수는 주어진 size의 데이터를 readoffset으로부터 읽고 writetarget buffer에 writeoffset을 시작지점으로 작성하게 됩니다. 이 예제는 다음과 같습니다.


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만 바인딩하여 수행할 수도 있습니다.


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));  

  Buffer를 다루는 방법에 대한 추가적인 지식으로 우리는 이미 이들을 좀 더 흥미로운 방법으로 사용할 수 있습니다. OpenGL에 대해서 더 많이 알수록 새로운 buffer method들은 더 유용해질 것입니다. 다음 강좌에서는 uniform buffer objects에 대해서 다룰 것이고 이 과정에서 glBufferSubData 함수를 훌륭하게 사용할 것입니다.



출처 : https://learnopengl.com, Joey de Vries

반응형