본문 바로가기

Game/Graphics

LearnOpenGL - Advanced OpenGL : Advanced Data

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


Advanced Data


 OpenGL의 버퍼를 사용해 꽤 오랫동안 데이터를 저장했다. 텍스처를 통해 많은 양의 데이터를 쉐이더에 전달하는 버퍼 및 기타 흥미로운


메소드를 조작하는 더 흥미로운 방법이 있다. 이 튜토리얼에서는 보다 흥미로운 버퍼 함수와 텍스처 객체를 사용해 많은 양의 데이터를


저장하는 방법에 대해 설명한다.



 OpenGL의 버퍼는 특정 메모리와 그 이상을 관리하는 객체일 뿐이다. 버퍼를 특정 버퍼 대상에 바인딩 할 때 버퍼에 의미를 부여한다.


버퍼는 GL_ARRAY_BUFFER에 바인드 할 때 단지 정점 배열 버퍼일 뿐이지만 GL_ELEMENT_ARRAY_BUFFER에 쉽게 바인드 할 수 있다.


OpenGL은 내부적으로 타겟당 버퍼를 저장하고 타겟을 기반으로 버퍼를 다르게 처리한다.



 지금까지 우리는 메모리 객체를 할당하고 이 메모리에 데이터를 추가하는 glBufferData를 호출해 버퍼 객체가 관리하는 메모리를 채웠다.


데이터 인수로 NULL을 전달하면 함수는 메모리만 할당하고 채우지 않는다. 이것은 처음에 특정 양의 메모리를 예약하고 나중에 이 버퍼로


돌아와 조각별로 채우기를 원할 때 유용하다.



 하나의 함수 호출로 전체 버퍼를 채우는 대신 glBufferSubData를 호출해 버퍼의 특정 영역을 채울 수 있다. 


이 함수는 버퍼 타겟, 오프셋, 데이터 크기 및 실제 데이터를 인수로 기대한다. 


이 함수의 새로운 점은 이제 버퍼를 채우려는 위치를 지정하는 오프셋을 제공 할 수 있다는 것이다.


이것에 의해 버퍼 메모리의 특정 부분만을 삽입/갱신할 수 있다. 버퍼에 glBufferSubData를 호출하기 전에 glBufferData에 대한 호출이 


필요하도록 버퍼에 충분한 메모리가 있어야한다.

glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // Range: [24, 24 + sizeof(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);

 OpenGL에게 glUnmapBuffer를 통한 포인터 작업이 끝났음을 알게 되면 OpenGL은 사용자가 작업을 완료했다는 것을 알게 된다.


매핑을 해제하면 포인터가 유효하지 않게 되고 OpenGL이 버퍼에 데이터를 성공적으로 매핑 할 수 있으면 함수는 GL_TRUE를 반환한다.



 glMapBuffer를 사용하면 데이터를 임시 메모리에 먼저 저장하지 않고, 버퍼에 직접 매핑하는 것이 유용하다. 파일에서 직접 데이터를


읽고 버퍼의 메모리로 복사하는 것을 고려해라.





Batching vertex attributes


 glVertexAttribPointer를 이용해 정점 배열 버퍼 내용의 속성 레이아웃을 지정할 수 있었다. 정점 배열 버퍼 내에서 우리는 속성을 삽입했다.


즉, 각 정점에 대해 위치, 법선, 텍스처 좌표를 서로 옆에 배치했다. 이제는 버퍼에 대해 조금 더 알았으므로 다른 접근법을 취해보자.



 우리가 할 수 있는 일은 모든 벡터 데이터를 interleaving(메모리를 동시에 액세스하다)하는 대신 속성 유형별로 큰 덩어리로 배치하는 것이다.


인터리브 레이아웃 123123123123 대신 일괄 처리 방식 111122223333을 사용한다.



 파일에서 정점 데이터를 로드 할 때 일반적으로 위치 배열, 법선 배열, 텍스처 좌표 배열을 검색한다. 이러한 배열을 하나의 큰 인터리브 데이터


배열로 결합하려면 약간의 노력이 필요할 수 있다. 일괄 처리 방식을 사용하면 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);

 이 방법을 사용하면 먼저 속성 배열을 처리하지 않고 버퍼 전체에 속성 배열을 직접 전송할 수 있다. 하나의 커다란 배열로 결합해


glBufferSubData를 사용해 버퍼를 즉시 채울 수도 있지만, glBufferSubData를 사용하면 이와 같은 작업을 완벽하게 처리 할 수 있다.



 또한, 이러한 변경 사항을 반영하기 위해 정점 특성 포인터를 업데이트해야 한다:

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) 매개변수는 정점 속성의 크기와 같고, 다음 정점 속성 벡터는 3 (또는 2)개의 구성 요소 바로 뒤에 있다.



 이것은 정점 속성을 설정하고 지정하는 또 다른 접근법을 제공한다. 이 방법을 사용하면 OpenGL에 즉각적인 이점이 없으며


대부분 정점 속성을 설정하는데 보다 체계적인 방법이다. 귀하가 선호하는 접근법은 귀하의 선호도와 신청 유형에 기초한다.






Copying buffers


 버퍼가 데이터로 채워지면 해당 데이터를 다른 버퍼와 공유하거나 버퍼의 내용을 다른 버퍼로 복사 할 수 있다.


glCopyBufferSubData 함수는 상대적으로 쉽게 하나의 버퍼에서 다른 버퍼로 데이터를 복사 할 수 있게 해준다. 함수의 prototype은


다음과 같다:

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

 readtarget 및 writetarget 매개 변수는 복사하려는 버퍼 대상을 주고자한다. 예를 들어, VERTEX_ARRAY_BUFFER에서 


VERTEX_ELEMENT_ARRAY_BUFFER로 버퍼 타겟을 각각 읽기 및 쓰기 타겟으로 지정해 복사 할 수 있다. 이제 해당 버퍼 대상에 바인드된


버퍼가 영향을 받는다.



 그러나 두 정점 배열 버퍼인 두 개의 다른 버퍼에 데이터를 읽고 쓰고 싶다면 어떻게 해야 할까? 같은 버퍼 타겟에 동시에 2개의 버퍼를


바인드 할 수 없다. 이러한 이유로 OpenGL은 GL_COPY_READ_BUFFER 및 GL_COPY_WRITE_BUFFER라는 두 개의 버퍼 대상을 추가로 제공한다.


그런 다음 선택한 버퍼를 이 새로운 버퍼 타겟에 바인드하고 해당 타겟을 readtarget 및 writetarget 인수로 설정한다.



 그런 다음 glCopyBufferSubData는 지정된 readoffset에서 주어진 크기의 데이터를 읽고 writeoffset에서 writetarget 버퍼에 쓴다.


두 정점 배열 버퍼의 내용을 복사하는 예제는 다음과 같다:

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 버퍼를 새로운 버퍼 타겟 유형 중 하나에 바인딩하는 것만으로도 이 작업을 수행 할 수 있다:

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을 사용할수록 새로운 버퍼 메소드가 유용해진다. 균일 버퍼 객체에 대해 논의 할 다음 튜토리얼에서는


glBufferSubData를 잘 활용할 것이다.








** ** ** ** ** ** ** ** ** **


성능을 향상하기 위해서는 데이터를 쓰고 읽는 과정을 잘 알아야하는데 좋은 정보를 얻었다.


실제로 활용하기 위해서는 직접 연습해봐야 할 듯하다.


지금은 이러한 함수들이 있다는 것을 알고, 추후에 연습을 통해 내 것으로 만들겠다.