경북대 '컴퓨터그래픽스' 강의 백낙훈 교수님의 강의를 듣고 복습하기 위한 게시글입니다.
게시글의 내용은 강의 또는 구글링을 통해 공부하였습니다.
이미지 자료는 출처를 밝히거나 직접 그려 사용하였습니다
그래픽스 데이터는 어디에 저장되는가? 에서 부터 시작된다.
기존의 C/C++ 프로그램에서는 메인 메모리(RAM)에 저장된다.

메인 메모리에 있는 data가 data bus를 통해서 GPU로 흘러가야 vertex shader에서 처리된다.
1초에 30~60 frame이 처리되는데 매 frame 마다 이 과정이 진행되는 것이다.
문제는 똑같은 데이터를 이 루트대로 매번 다시 전달한다.
그래픽 프로그램에서는 위치가 변경되더라도, 사실상 같은 모양을 반복해서 그리는 경우가 많은데
그 데이터를 매번 새로 보내는 건 좋은 아이디어는 아니다.

GPU 처리를 위한 데이터를 그래픽 카드에 별도의 메모리, VRAM에 저장한다.
이 VRAM으로 훨씬 짧은 거리를 통해 효율적으로 데이터를 주고 받자는 개념이 VBO(Vertex Buffer Object)이다.

Illustration by 백낙훈 교수님
VBO 를 사용하기 전에는
attribute register에 RAM 에 들어있는 실제 시작 주소를 openGL함수를 통해 연결해서 사용한다.
C/C++ 프로그램에서 자동으로 데이터를 넣어주기 때문에 사용자가 주소(절대 주소)를 함부로 건들지 못한다.
그리고 전체 시스템 메모리에서 0번지가 따로 존재하고
데이터에 접근할 때, 해당 절대 주소를 사용한다.

Illustration by 백낙훈 교수님
VBO를 사용할 경우에는
attribute register에 VRAM 내의 buffer에 연결한다.
VRAM은 데이터 영역을 알고 있으면 데이터를 어디에 저장할지 직접 control 할 수 있다.
데이터에 접근할 때, 실제 주소로 접근하는게 아닌 해당 offset을 사용하여 접근한다.
일반적으로 main memory에 있는 array 대신에 buffer를 사용한다. (GL_ARRAY_BUFFER)
이것을 'target'이라고 한다.
void glGenBuffers(GLsizei n, GLuint* buffers);
* n개의 buffer를 생성하고, 그 ID number를 buffers 배열에 uint 형태로 반환한다.
void glBindBuffer(GLenum traget, GLuint buffer);
* buffer( 위에서 구한 ID number )를 target인 GL_ARRAY_BUFFER에 결합한다.
void glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
* RAM에 있는 data를 VRAM으로 전송한다. id number가 아닌 target이 인자이다.
glBufferData는 buffer 전체, glBufferSubData는 buffer 일부
glBufferData의 기능은 사실상 2가지인데,
첫번째는 버퍼의 크기를 결정한다. const GLvoid* data에 NULL을 입력하면 된다.
두번째는 버퍼 데이터의 copy이다. const GLvoid* data 메인 메모리의 데이터 영역을 알려주면 해당 주소부터 size만큼 copy한다.
예시 코드
glm::vec4 vertPos[] = { // 6 * 3 = 18 vertices
...
};
glm::vec4 vertColor[] = { // 6 * 3 = 18 colors
...
};
GLuint vbo[1];
void prepareVBO(void) {
// 1.
glGenBuffers(1, vbo);
// 2.
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
// 3.
glBufferData(GL_ARRAY_BUFFER, 2 * 18 * 4 * sizeof(GLfloat), NULL, GL_STATIC_DRAW);
// 4.
glBufferSubData(GL_ARRAY_BUFFER, 0, 18 * 4 * sizeof(GLfloat), vertPos);
// 5.
glBufferSubData(GL_ARRAY_BUFFER, 18 * 4 * sizeof(GLfloat), 18 * 4 * sizeof(GLfloat), vertColor);
}
- 1개의 버퍼를 생성하고 그 id number을 vbo 배열에 반환한다.
- target인 GL_ARRAY_BUFFER에 vbo[0]을 bind 시킨다.
- 현재 vertPos, vertColor는 float 형 vec4를 각각 18칸 가지고 있다. 그래서 총 sizeof(float) * 4 * 18 * 2 의 size를 같게 된다(크기 지정). 만약 NULL 부분에 NULL 대신에 메인 메모리의 데이터 영역을 알려주면 그 영역부터 size만큼 copy한다. GL_STATIC_DRAW는 대부분 한번 지정된 data를 바꿀 일이 얼마 없으므로 static으로 한다. 반대되는 것은 GL_DYNAMIC_DRAW가 있다.
- offset 0 부터, 18 * 4 * sizeof(float)만큼 vertPos의 데이터를 가져온다.
- 앞서 vertPos 데이터를 copy한 영역을 건너뛰고 offset 18 * 4 * sizeof(float)부터 18 * 4 * sizeof(float)만큼 vertColor의 데이터를 가져온다.
void drawFunc(void) {
...
GLuint locPos = glGetAttribLocation(prog, "aPos");
glEnableVertexAttribArray(locPos);
// 1.
glVertexAttribPointer(locPos, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(0));
GLuint locColor = glGetAttribLocation(prog, "aColor");
glEnableVertexAttribArray(locColor);
// 2.
glVertexAttribPointer(locColor, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(18 * 4 * sizeof(GLfloat)));
...
glDrawArrays(GL_TRIANGLES, 0, 18); // 18 vertices
// done
glFinish();
}
- bindbuffer가 되어 있기 때문에 (GLvoid*)(0)을 사용한다.
- 마찬가지로 앞의 vertPos 데이터를 건너뛰고 (GLvoid*)(18 * 4 * sizeof(GLfloat)) 부터 시작한다.
offset 은 사실 byte로 숫자로 되어있는데
glVertexAttribPointer() 함수는 원래 포인터 값을 받도록 되어있어서 (GLvoid*)와 같이 포인터로 캐스팅한다.
vertex에 관계된 정보를 묶어서 만들면 어떻게 쓸 수 있을까
struct vertex{
glm::vec4 pos; // position
glm::vec4 color; // color
}
glm::vec4 vertPosColor[] = { // 6 * 3 = 18 vertices + color
// face 0: v0-v1-v2, red
{ 0.0F, 0.5F, 0.0F, 1.0F }, { 1.0F, 0.3F, 0.3F, 1.0F, }, // v0 position, color
{ 0.5F, -0.3F, 0.0F, 1.0F }, { 1.0F, 0.3F, 0.3F, 1.0F, }, // v1 position, color
{ 0.0F, -0.3F, -0.5F, 1.0F }, { 1.0F, 0.3F, 0.3F, 1.0F, }, // v2
...
};
GLuint vbo[1];
void prepareVBO(void) {
glGenBuffers(1, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
// 1.
glBufferData(GL_ARRAY_BUFFER, 2 * 18 * 4 * sizeof(GLfloat), vertPosColor, GL_STATIC_DRAW);
}
위 코드와 같이 position 정보와 color 정보가 한 set 형식으로 저장되어있다.
void drawFunc(void) {
...
GLuint locPos = glGetAttribLocation(prog, "aPos");
glEnableVertexAttribArray(locPos);
// 1.
glVertexAttribPointer(locPos, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(0));
GLuint locColor = glGetAttribLocation(prog, "aColor");
glEnableVertexAttribArray(locColor);
// 2.
glVertexAttribPointer(locColor, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(4 * sizeof(GLfloat)));
...
glDrawArrays(GL_TRIANGLES, 0, 18); // 18 vertices
// done
glFinish();
}
여기서 잠깐 glVertexAttribPoitner 함수를 다시 보면,
void glVertexAttribPointer(GLuint index, GLint size, Glenum type,
GLboolean normalized, GLsizei stride, const GLvoid* pointer);
* 여기서 stride는 데이터들 사이의 거리를 뜻한다.
어떤 데이터가 있을 때 다음 데이터까지 전진해야되는 byte 단위의 숫자이다.
0을 넣으면 자동 계산되어 일반적으로 하나씩 이동한다.
이 stride를 조정하여 저장된 데이터에 접근해보자.

position과 position 정보, color와 color 정보 간의 거리는 4 * sizeof(GLfloat) 이라서
stride 는 4 * 8 bytes 가 된다.
stride를 잘 조정하여 필요한 정보에 접근할 수 있다.
IBO 란?
Index Buffer Objects로 VBO와 크게 다를 거 없이
단순히 glDrawElements() 함수를 사용할 때 Index array를 위한 버퍼이다.

이전 단계는 VBO와 마찬가지로 진행해주고,
Draw는 glDrawElements() 함수를 사용할 때 IBO를 사용한다 했으니 사용해주면 된다.
GLuint vbo[1];
GLuint ibo[1];
void prepareVBO(void) {
// VBO setting
glGenBuffers(1, vbo);
...
// IBO setting
glGenBuffers(1, ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 18 * sizeof(GLuint), indices, GL_STATIC_DRAW);
}
void drawFunc(void) {
...
glVertexAttribPointer(locPos, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(0));
...
glVertexAttribPointer(locColor, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(5 * 4 * sizeof(GLfloat)));
...
// draw
glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_INT, (GLvoid*)(0));
// done
glFinish();
}
크게 다를 것은 없고,
glBindBuffer, glBufferData 에서 target 인 GL_ARRAY_BUFFER 대신,
GL_ELEMENT_ARRAY_BUFFER로 사용해준다.
glDrawElements에서 IBO가 연결된 상황이니
(GLvoid*)(0) 은 IBO의 offset 0 에 접근한다.
'컴터 > OpenGL' 카테고리의 다른 글
[OpenGL] View Frustum, FOV(field of view) (2) | 2023.06.10 |
---|---|
[OpenGL] Look-At, Orthographic Projection (2) | 2023.06.10 |
[OpenGL] Back face Culling (0) | 2023.06.10 |
[OpenGL] Z-buffer method (0) | 2023.06.10 |
[OpenGL] Double Buffering (0) | 2023.06.10 |