지난 글에서는 셰이더의 개념과 만드는 방법
그리고 셰이더 넘길 데이터가 담기는 버퍼를 만들어서 데이터와 연결하는 과정까지 정리하였다.
이번 글에서는 버퍼에 있는 데이터를 어떻게 셰이더로 넘길지 정의하는 VAO에 대해서 정리해보고자 한다.
Vertex Attribute Pointer
지난 시간에 만든 VBO ( Vertex Buffer Object ) 에는 셰이더 넘길 전체 데이터가 들어가 있었다.
float vertices[] = {
-0.5f, -0.5f, 0.0f, // vertex 1
0.5f, -0.5f, 0.0f, // vertex 2
0.0f, -0.5f, 0.0f, // vertex 3
-0.5f, 0.5f, 0.0f // vertex 4
};
가령 이 코드를 보면, 삼각형을 만드는데 사용할 기준 정점 4개의 정보를 이렇게 1차원 배열에 넣어두었다.
앞에서부터 3개씩 각 정점의 x, y, z 좌표를 의미한다.
그런데 2차원 배열이 아니라 1차원 배열에 담겨있는 이 정보를 3개씩 끊어서 써야한다는 걸 Open GL 이 알 수 있을까?
당연히 알 수 없다.
따라서 셰이더에 값을 넘길 때는 주어진 데이터를 3개씩 끊어서 넘겨야한다고 알려줄 필요가 있다.
이렇게 VBO에 있는 데이터를 어떻게 사용할지 알려주는 정보를 Vertex Attribute 라고 한다.
Attribute 에는 그림과 같이 Position, Stride, Offset 데이터가 들어가게된다.
위 데이터는 x, y, z 3개 데이터로 묶어서 사용하므로 stride는 4byte * 3 = 12 byte 이다. (4byte는 float 의 크기)
시작은 바로 0번째 데이터부터 시작하면 되므로, offset은 0이 된다.
속성 데이터를 만들때는 glVertexAttribPointer 함수를 사용하여 만들면 된다.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
위 내용을 코드로 옮기면 이렇게 된다.
각 인자를 살펴보면 아래와 같다.
( 데이터 구분 번호 = 속성의 인덱스, vertex 구성 요소의 수, vertex data type, 정규화 플래그, stride, offset )
데이터 구분번호는 말 그대로 구분하는 번호이다. 이 번호를 가지고 어떤 속성값을 사용할지 지정하게 된다.
vertex 구성요소의 수는, 하나의 vertex에 몇개 데이터가 들어가는지를 의미한다. (바이트 값이 아니다!)
vertex shader에 넘길 위치 데이터는 x, y, z 3개이므로 3을 적는다.
그리고 각 데이터는 float 형 데이터이므로, vertex data type 은 float 이다.
정규화 플래그는 각 데이터를 normalize 할 것인지, 그대로 쓸 것인지를 결정한다.
vertex마다 사용할 데이터의 간격을 의미하는 stride 는 float 형 3개씩 이므로 sizeof(float) * 3 을 지정한다.
마지막으로 시작 위치는 0부터 시작하므로 (void*) 0 을 지정한다.
(왜 void* 인지는 모르겠다.)
만든 속성은 glEnableVertexAttrbArray() 함수를 사용하여 VAO에 등록해준다.
인자값은 등록할 속성의 구분번호를 넘기면 된다.
(VAO에 대한 내용은 후술한다.)
이번엔 다른 예제를 봐보자.
이번엔 VBO에 위치값과 색상값이 같이 세트로 들어있다.
하지만 셰이더에는 위치값 3개 따로, 색상값 3개 따로 넘겨줘야한다.
이럴 땐 위치만 뽑아내는 속성 하나, 색상만 뽑아내는 속성 하나해서 2개 속성을 만들어주면 된다.
먼저 위치를 뽑아내는 것은, offset 0 부터 시작해서 3개 데이터를 가져온다.
다음 위치 데이터를 가져올 때는 전에 가져온 위치로부터 24byte만큼 떨어진 곳에서 가져온다.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
코드로는 이렇게 쓸 수 있다.
이번엔 색상값을 뽑아보자.
색상 데이터는 처음 offset에 12를 주어 12byte를 건너뛴 위치부터 3개씩 가져온다.
다음 색상 데이터는 6개를 건너뛰고 나오므로 24byte 를 건너뛰어야 하니 stride는 마찬가지로 24이다.
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)3*sizeof(float));
glEnableVertexAttribArray(1);
코드로 작성하면 위와 같다.
offset 데이터도 byte 단위로 넘겨야 하므로 sizeof(float) 함수를 사용하여 넘겼다.
이제 이 설정값을 저장하고있는 VAO에 대해 알아보자.
VAO, Vertex Array Object
VAO는 위에서 지정한 설정을 저장하고 있는 Object 이다.
우선 이 VAO도 그래픽카드 코어가 사용할 메모리 안에 저장되어야 하기 때문에 객체를 먼저 생성해야 한다.
unsigned int VAO;
glGenVertexArrays(1, &VAO);
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
위 코드처럼 작성하여 VAO 객체를 만든 뒤 현재 Context에 바인딩해준다.
// 2 copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
다음으로는 만들었던 VBO를 Context에 바인딩하고, 데이터도 설정해준다.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
그리고 이 데이터를 어떻게 사용할지, 속성 데이터를 만들어서 VAO에 담아준다.
이를 전체적인 구조도로 보면 위와 같다.
VAO 안에 attribute pointer 가 들어있고, 이 속성값을 VBO에 그대로 적용해서 현재 context 에서 사용할 데이터를 얻어온다음 셰이더에 넘기는 것이다.
실제로 화면에 그림을 그릴 때는 아래 순서로 그림을 그린다.
1. 셰이더 프로그램 설정
2. 셰이더에 넘길 입력 데이터 설정 (입력 VAO 바인드)
3. Drawing
while (!glfwWindowShouldClose(pWindow)) {
..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
...
}
이 순서에 해당하는 코드는 위와 같다.
VBO & VAO 사용한 삼각형 그리기 예제
#include <glad\glad.h>
#include <GLFW\glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* pWindow, int width, int height);
int main(int argc, char** argv)
{
unsigned int VBO;
unsigned int VAO;
float vertices[] = {
-0.5f, -0.5f, 0.0f, // 왼쪽 아래
0.5f, -0.5f, 0.0f, // 오른쪽 아래
0.0f, 0.5f, 0.0f // 위
};
const char* vertexShaderSourceCode =
"#version 330 core \n"
"layout (location = 0) in vec3 aPos; \n"
"void main() { \n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); \n"
"} \0";
const char* fragmentShaderSourceCode =
"#version 330 core \n"
"out vec4 FragColor; \n"
"void main() { \n"
" FragColor = vec4(1.0f, 0.5, 0.2f, 1.0f); \n"
"} \n";
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "practicee", NULL, NULL);
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
// VBO 생성 & 데이터 소스 연결
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Vertex Shader 생성 및 컴파일
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSourceCode, NULL);
glCompileShader(vertexShader);
// Fragment Shader 생성 및 컴파일
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSourceCode, NULL);
glCompileShader(fragmentShader);
// Shader Program 생성 및 셰이더 연결
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glDeleteShader(vertexShader);
// VAO 생성 & 속성값 주입
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
glEnableVertexAttribArray(0);
glViewport(0, 0, 800, 600);
while (!glfwWindowShouldClose(window)) {
glClearColor(.8f, .7f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* pWindow, int width, int height)
{
glViewport(0, 0, width, height);
}
'CS > HCI 윈도우즈프로그래밍' 카테고리의 다른 글
[OpenGL] 8. Shader (4) - GLSL (0) | 2024.04.09 |
---|---|
[OpenGL] 7. Shader (3) - EBO (Element Buffer Object) (0) | 2024.04.08 |
[OpenGL] 5. Shader (1) - Shader, VBO, Shader Program (0) | 2024.04.05 |
[OpenGL] 4. Graphics Pipeline (1) | 2024.04.04 |
[OpenGL] 3. GLFW 기본 예제 뜯어보기 (0) | 2024.04.04 |