일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 프메
- node
- 언리얼프로그래머
- 언리얼
- flask
- 카렌
- 레베카
- VUE
- JWT
- 스마일게이트
- EnhancedInput
- 마인크래프트뮤지컬
- 언리얼뮤지컬
- 파이썬서버
- 으
- Express
- Bootstrap4
- 게임개발
- 스터디
- Enhanced Input System
- 데이터베이스
- 미니프로젝트
- 디자드
- Ajax
- 정글사관학교
- R
- Unseen
- 알고풀자
- 프린세스메이커
- Jinja2
- Today
- Total
Today, I will
[OpenGL] opengl graphics pipeline의 이해와 hello_triangle 속 VAO와 VBO의 쓰임 본문
[OpenGL] opengl graphics pipeline의 이해와 hello_triangle 속 VAO와 VBO의 쓰임
Lv.Forest 2023. 10. 11. 16:29*컴퓨터 그래픽스 강의와 Learn Open GL Code를 참고하여 작성한 포스팅입니다.
opengl graphics pipeline
순서
(1) Vertex shader : transform verices : 각각의 버텍스를 어떻게 transformation할 것인지.
각각 버텍스에 적용(모델링, 뷰잉, 프로젝션, 뷰포트 transformation 등 행렬을 곱해주는 연산을 Vertex shader 에서 수행)
(2) shape assembly : form a primitive
(3) geometry shader : emit vertices to form new primitives
(4) pixelization : rasterization
(5) colorization : fragment shader: 컬러를 정하는 것. 각각의 픽셀에 대해서 픽셀의 컬러가 무엇인지 정해주는 일을 한다.
(6) Test and Blending
Vertex shader와 fragment shader는 반드시 따라야하는 것이다.
getting_started__2.1.hello_triangle
Vertex shader와 fragment shader
Vertex shader와 fragment shader를 사용자가 만들어야 한다.
원래는 개별적인 코드 파일로 만든다.
fs 예시
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
// texture samplers
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
// linearly interpolate between both textures (80% container, 20% awesomeface)
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
vs 예시
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}
hello_triangle에서는 아래 코드와 같이 char 포인터 변수로 잡아두어 string으로 읽고 있다.
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" // aPos 인풋을 그대로 출력함
"}\0"; // 즉, transformation이 없이 받은 인풋을 그대로 파이프라인에 흘려보낸다.
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" // r 1, g 0.5, b 0.2, a 1로 통일한 아웃풋
"}\n\0";
Vertex shader와 fragment shader 컴파일 및 링킹
open gl host code가 있고 쉐이더 코드가 있으면, 쉐이더를 open gl 쪽으로 불러와서 컴파일 및 링킹하는 작업을 open gl에서 해주어야 한다.
렌더링 루프에서 쉐이더를 써야하므로, 그 전에 shader를 load하고 complie하는 코드가 있다.
// build and compile our shader program
// vertex shader
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER); //glCreateShader
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //vertexShaderSource를 가져온다
glCompileShader(vertexShader); //CompileShader
// check for shader compile errors
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) // 컴파일한 것이 이상이 없는지 확인
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// fragment shader
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
쉐이더 프로그램에서 컴파일시킨 쉐이더들은 어떤 것을 링크하느냐에 따라 바꿔 갈아끼울 수 있도록 한다.
프로그램에서 사용할 쉐이더들을 계속 바꿀 수도 있기 때문이다.
그러나 본 코드에세는 버텍스 쉐이더와 프래그먼트 쉐이더를 쓰기 위한 전체 쉐이더 프로그램을 하나 만들고(glCreateProgram), glAttachShader, glLinkProgram를 순서대로 call하여 링크하여 쉐이더 프로그램에 위에서 빌드하고 컴파일한 쉐이더를 링크한다.
// link shaders
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader); // 링크했으므로 쓸 일이 없으므로 delete
glDeleteShader(fragmentShader);// 링크했으므로 쓸 일이 없으므로 delete
// 나중에 shaderProgram를 이용하면 Attach한 vertexShader와 fragmentShader가 들어있다.
전체 코드
int main()
{
// build and compile our shader program
// ------------------------------------
// vertex shader
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// fragment shader
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// link shaders
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// set up vertex data (and buffer(s)) and configure vertex attributes
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
glBindVertexArray(0);
// uncomment this call to draw in wireframe polygons.
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// render loop
while (!glfwWindowShouldClose(window))
{
// input
processInput(window);
// render
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0); // no need to unbind it every time
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
glfwSwapBuffers(window);
glfwPollEvents();
}
// optional: de-allocate all resources once they've outlived their purpose:
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
// glfw: terminate, clearing all previously allocated GLFW resources.
glfwTerminate();
return 0;
}
렌더링 루프
렌더링 루프에서 이전 코드와 차이나는 부분
// draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0); // no need to unbind it every time
(1) shaderProgram를 사용한다고 선택 glUseProgram
- 여기까지 쉐이더를 만들어서 붙인 쉐이더 프로그램을 사용한다고 명시하였으므로 쉐이더를 써서 렌더링할 준비가 됨
(2) glBindVertexArray(VAO)를 콜하여 VAO를 쓰기 위해서는 그 전에,
어떤 버텍스데이타를 쓸지 넘겨주기 : 그래픽스 파이프라인에 버텍스 데이타를 넣어주어야 하기 때문
- 삼각형을 그리기 위하여 꼭지점 3개 데이터를 전달해주어야 한다.
- VBO(vertex buffer object)에 삼각형 꼭지점 데이터를 넣는데, 지금은 넣는 데이터가 단순하지만 포지션, 컬러, 텍스쳐코디네이트 등 다양하고 복잡한 형태로 데이터가 구성될 수도 있다.
사진 출처 : VOB-CSDN 블로그에서 VBO, VAO, EBO의 역할 이해 OPenGL_opengl
VAO는 VBO에 어떤 식의 데이터가 들어있는지를 알고 있어야 한다.
VAO(Vertex Array Object) : VBO에 저장되어 있는 것들이 무엇을 의미하고 어떤 형태로 저장되어있는지 가지고 있다.
실제로 렌더링을 하기 위해 데이터를 넘겨줄 때 저장은 vbo, 렌더링을 위해 사용하는 것은 vao 인 것이다.
Prepare Vertex Data
수학적 xy 2차원 그래프와 똑같다.
좌표의 표현 : (x,y,z,w)
- 2차원 좌표는 z = 0, w = 1로 설정
- 가령 ( -0.5, 0.5 ) => ( -0.5, 0.5 , 0,1)
// set up vertex data (and buffer(s)) and configure vertex attributes
float vertices[] = { // 지금은 단순하게 포지션만 있는 케이스
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(VAO); // 바인딩 VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 바인딩 VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // VBO에 버텍스 데이터를 카피
//c의 memcopy와 비슷, 크기와 vertices의 포인터
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//VBO에 저장되어 있는 데이터가 어떤 형태라고 선언!
// Attrib이 하나 뿐이기 때문에 첫번째 인자 0(어떤 경우에는 POSITION, COLOR, TEXTCOORDS까지 있다)
// 3은 컴포넌트 개수(position이므로 x,y,z)
// GL_FLOAT는 데이터 타입
// GL_FALSE는 normalize 여부
// 3 * sizeof(float)는 stride: 첫번째 버텍스 시작으로부터 두번째 버텍스가기 위해 얼마나 넘어가야 하는지
// (void*)0는 첫번째 버텍스 시작점을 알려줌
glEnableVertexAttribArray(0);
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
glBindVertexArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//VBO에 저장되어 있는 데이터가 어떤 형태라고 선언!
Attrib이 하나 뿐이기 때문에 첫번째 인자 0
(아래 경우에는 POSITION, COLOR, TEXTCOORDS까지 있다)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (GLvoid*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (GLvoid*)(2*sizeof(GLfloat)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (GLvoid*)(5*sizeof(GLfloat));
컴포넌트는 데이터타입에 따라 달라진다(3차원 데이터는 3, 2차원 데이터는 2인 식으로)
(3) 마지막 드로잉(렌더링)
- glDrawArrays(GL_TRIANGLES, 0, 3) // 여기서 GL_TRIANGLES는 shape assembly(open gl 파이프라인 두번째)
- (2)에서 vao를 통해 vbo에 저장된 데이터를 알기 때문에 이 버텍스 데이터들로 삼각형을 만들고 싶다는 것을 알려줌
- 0은 처음 컴포넌트를 뜻함(0부터 사용)
- 3은 버텍스 데이터가 총 3개라는 뜻
'Computer Science > 그래픽스' 카테고리의 다른 글
[그래픽스] HDR(High Dynamic Range), HDRI (0) | 2024.01.29 |
---|---|
[그래픽스] ray tracing intersection with triangle (ray 방정식과 삼각형의 교점 찾기) (1) | 2023.12.15 |
[그래픽스] 그래픽스 파이프라인 개념과 GPU에서의 쉐이더를 통한 가속화 (0) | 2023.11.17 |
[그래픽스] 선형대수-행렬, 그래픽스 프로그래밍에서 자주 쓰는 연산 (1) | 2023.10.20 |
[OpenGL] Learn Open GL 기본 코드 hello_window_clear (1) | 2023.10.10 |