눈팅하는 게임개발자 블로그
OpenGL Advanced 5-7 Advanced Data 본문
원문 사이트
learnopengl.com/Advanced-OpenGL/Advanced-Data
번역 사이트
Advanced 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의 메모리에 복사하는 경우 임시 메모리가 낭비될 일이 없다.
Batching Vertex attributes
glVertexAttribPointer 함수를 사용하여 vertex array buffer 내용의 attribute layout을 지정할 수 있었다.
Vertex Array buffer 내부에 attribute들을 끼워넣어왔었는데, 이는 위치, 법선, 텍스처 좌표들을 다른것들 옆에
위치시켰다는 의미이다. 이제 buffer에 대해 좀 더 알게 되었으니 다른 방법을 취할 수도 있다.
모든 벡터 데이터들을 끼워넣는 것이 아니라 attribute 유형마다 큰 덩어리로 묶을 수 있다는 것이다.
123123123이런식이 아닌 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를 이렇게 설정하는 것이 좀 더 체계화가 되어있는 방법이다.
어떤 방법을 사용할지는 취향에 따라 갈리며 응용 프로그램이 어떤 유형인가에 달려 있다.
Copying 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_BUFFER와 GL_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));
또한 eritetarget 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 함수를 훌륭하게 사용할 것이다.
'공부한거 > OpenGL' 카테고리의 다른 글
OpenGL Advanced 5-9 Geometry Shader (0) | 2020.12.11 |
---|---|
OpenGL Advanced 5-8 Advanced GLSL (0) | 2020.12.11 |
OpenGL Advanced 5-5 Framebuffers (0) | 2020.12.10 |
OpenGL Advanced 5-4 Face culling (0) | 2020.12.10 |
OpenGL Advanced 5-3 Blending (0) | 2020.12.09 |